]> granicus.if.org Git - shadow/blob - src/chfn.c
* src/groupmems.c: Check the return value of gr_update().
[shadow] / src / chfn.c
1 /*
2  * Copyright (c) 1989 - 1994, Julianne Frances Haugh
3  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4  * Copyright (c) 2001 - 2006, Tomasz Kłoczko
5  * Copyright (c) 2007 - 2008, Nicolas François
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the copyright holders or contributors may not be used to
17  *    endorse or promote products derived from this software without
18  *    specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include <config.h>
34
35 #ident "$Id$"
36
37 #include <fcntl.h>
38 #include <pwd.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <sys/types.h>
42 #ifdef WITH_SELINUX
43 #include <selinux/selinux.h>
44 #include <selinux/av_permissions.h>
45 #endif
46 #include "defines.h"
47 #include "exitcodes.h"
48 #include "getdef.h"
49 #include "nscd.h"
50 #ifdef USE_PAM
51 #include "pam_defs.h"
52 #endif
53 #include "prototypes.h"
54 #include "pwauth.h"
55 #include "pwio.h"
56 /*
57  * Global variables.
58  */
59 static char *Prog;
60 static char fullnm[BUFSIZ];
61 static char roomno[BUFSIZ];
62 static char workph[BUFSIZ];
63 static char homeph[BUFSIZ];
64 static char slop[BUFSIZ];
65 static bool amroot;
66 /* Flags */
67 static bool fflg = false;               /* -f - set full name                */
68 static bool rflg = false;               /* -r - set room number              */
69 static bool wflg = false;               /* -w - set work phone number        */
70 static bool hflg = false;               /* -h - set home phone number        */
71 static bool oflg = false;               /* -o - set other information        */
72 #ifdef USE_PAM
73 static pam_handle_t *pamh = NULL;
74 #endif
75 static bool pw_locked = false;
76
77 /*
78  * External identifiers
79  */
80
81 /* local function prototypes */
82 static void fail_exit (int code);
83 static void usage (void);
84 static bool may_change_field (int);
85 static void new_fields (void);
86 static char *copy_field (char *, char *, char *);
87 static void process_flags (int argc, char **argv);
88 static void check_perms (const struct passwd *pw);
89 static void update_gecos (const char *user, char *gecos);
90 static void get_old_fields (const char *gecos);
91
92 /*
93  * fail_exit - exit with an error and do some cleanup
94  */
95 static void fail_exit (int code)
96 {
97         if (pw_locked) {
98                 if (pw_unlock () == 0) {
99                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
100                         SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
101                         /* continue */
102                 }
103         }
104         pw_locked = false;
105
106         closelog ();
107
108         exit (code);
109 }
110
111 /*
112  * usage - print command line syntax and exit
113  */
114 static void usage (void)
115 {
116         if (amroot) {
117                 fprintf (stderr,
118                          _("Usage: %s [-f full_name] [-r room_no] "
119                            "[-w work_ph]\n"
120                            "\t[-h home_ph] [-o other] [user]\n"), Prog);
121         } else {
122                 fprintf (stderr,
123                          _("Usage: %s [-f full_name] [-r room_no] "
124                            "[-w work_ph] [-h home_ph]\n"), Prog);
125         }
126         exit (E_USAGE);
127 }
128
129 /*
130  * may_change_field - indicate if the user is allowed to change a given field
131  *                    of her gecos information
132  *
133  *      root can change any field.
134  *
135  *      field should be one of 'f', 'r', 'w', 'h'
136  *
137  *      Return true if the user can change the field and false otherwise.
138  */
139 static bool may_change_field (int field)
140 {
141         const char *cp;
142
143         /*
144          * CHFN_RESTRICT can now specify exactly which fields may be changed
145          * by regular users, by using any combination of the following
146          * letters:
147          *  f - full name
148          *  r - room number
149          *  w - work phone
150          *  h - home phone
151          *
152          * This makes it possible to disallow changing the room number
153          * information, for example - this feature was suggested by Maciej
154          * 'Tycoon' Majchrowski.
155          *
156          * For backward compatibility, "yes" is equivalent to "rwh",
157          * "no" is equivalent to "frwh". Only root can change anything
158          * if the string is empty or not defined at all.
159          */
160         if (amroot) {
161                 return true;
162         }
163
164         cp = getdef_str ("CHFN_RESTRICT");
165         if (NULL == cp) {
166                 cp = "";
167         } else if (strcmp (cp, "yes") == 0) {
168                 cp = "rwh";
169         } else if (strcmp (cp, "no") == 0) {
170                 cp = "frwh";
171         }
172
173         if (strchr (cp, field) != NULL) {
174                 return true;
175         }
176
177         return false;
178 }
179
180 /*
181  * new_fields - change the user's GECOS information interactively
182  *
183  * prompt the user for each of the four fields and fill in the fields from
184  * the user's response, or leave alone if nothing was entered.
185  */
186 static void new_fields (void)
187 {
188         puts (_("Enter the new value, or press ENTER for the default"));
189
190         if (may_change_field ('f')) {
191                 change_field (fullnm, sizeof fullnm, _("Full Name"));
192         } else {
193                 printf (_("\tFull Name: %s\n"), fullnm);
194         }
195
196         if (may_change_field ('r')) {
197                 change_field (roomno, sizeof roomno, _("Room Number"));
198         } else {
199                 printf (_("\tRoom Number: %s\n"), roomno);
200         }
201
202         if (may_change_field ('w')) {
203                 change_field (workph, sizeof workph, _("Work Phone"));
204         } else {
205                 printf (_("\tWork Phone: %s\n"), workph);
206         }
207
208         if (may_change_field ('h')) {
209                 change_field (homeph, sizeof homeph, _("Home Phone"));
210         } else {
211                 printf (_("\tHome Phone: %s\n"), homeph);
212         }
213
214         if (amroot) {
215                 change_field (slop, sizeof slop, _("Other"));
216         }
217 }
218
219 /*
220  * copy_field - get the next field from the gecos field
221  *
222  * copy_field copies the next field from the gecos field, returning a
223  * pointer to the field which follows, or NULL if there are no more fields.
224  *
225  *      in - the current GECOS field
226  *      out - where to copy the field to
227  *      extra - fields with '=' get copied here
228  */
229 static char *copy_field (char *in, char *out, char *extra)
230 {
231         char *cp = NULL;
232
233         while (NULL != in) {
234                 cp = strchr (in, ',');
235                 if (NULL != cp) {
236                         *cp++ = '\0';
237                 }
238
239                 if (strchr (in, '=') == NULL) {
240                         break;
241                 }
242
243                 if (NULL != extra) {
244                         if ('\0' != extra[0]) {
245                                 strcat (extra, ",");
246                         }
247
248                         strcat (extra, in);
249                 }
250                 in = cp;
251         }
252         if ((NULL != in) && (NULL != out)) {
253                 strcpy (out, in);
254         }
255
256         return cp;
257 }
258
259 /*
260  * process_flags - parse the command line options
261  *
262  *      It will not return if an error is encountered.
263  */
264 static void process_flags (int argc, char **argv)
265 {
266         int flag;               /* flag currently being processed    */
267
268         /* 
269          * The remaining arguments will be processed one by one and executed
270          * by this command. The name is the last argument if it does not
271          * begin with a "-", otherwise the name is determined from the
272          * environment and must agree with the real UID. Also, the UID will
273          * be checked for any commands which are restricted to root only.
274          */
275         while ((flag = getopt (argc, argv, "f:r:w:h:o:")) != EOF) {
276                 switch (flag) {
277                 case 'f':
278                         if (!may_change_field ('f')) {
279                                 fprintf (stderr,
280                                          _("%s: Permission denied.\n"), Prog);
281                                 exit (E_NOPERM);
282                         }
283                         fflg = true;
284                         STRFCPY (fullnm, optarg);
285                         break;
286                 case 'h':
287                         if (!may_change_field ('h')) {
288                                 fprintf (stderr,
289                                          _("%s: Permission denied.\n"), Prog);
290                                 exit (E_NOPERM);
291                         }
292                         hflg = true;
293                         STRFCPY (homeph, optarg);
294                         break;
295                 case 'r':
296                         if (!may_change_field ('r')) {
297                                 fprintf (stderr,
298                                          _("%s: Permission denied.\n"), Prog);
299                                 exit (E_NOPERM);
300                         }
301                         rflg = true;
302                         STRFCPY (roomno, optarg);
303                         break;
304                 case 'o':
305                         if (!amroot) {
306                                 fprintf (stderr,
307                                          _("%s: Permission denied.\n"), Prog);
308                                 exit (E_NOPERM);
309                         }
310                         oflg = true;
311                         STRFCPY (slop, optarg);
312                         break;
313                 case 'w':
314                         if (!may_change_field ('w')) {
315                                 fprintf (stderr,
316                                          _("%s: Permission denied.\n"), Prog);
317                                 exit (E_NOPERM);
318                         }
319                         wflg = true;
320                         STRFCPY (workph, optarg);
321                         break;
322                 default:
323                         usage ();
324                 }
325         }
326 }
327
328 /*
329  * check_perms - check if the caller is allowed to add a group
330  *
331  *      Non-root users are only allowed to change their gecos field.
332  *      (see also may_change_field())
333  *
334  *      Non-root users must be authenticated.
335  *
336  *      It will not return if the user is not allowed.
337  */
338 static void check_perms (const struct passwd *pw)
339 {
340 #ifdef USE_PAM
341         int retval;
342         struct passwd *pampw;
343 #endif
344
345         /*
346          * Non-privileged users are only allowed to change the gecos field
347          * if the UID of the user matches the current real UID.
348          */
349         if (!amroot && pw->pw_uid != getuid ()) {
350                 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
351                 closelog ();
352                 exit (E_NOPERM);
353         }
354 #ifdef WITH_SELINUX
355         /*
356          * If the UID of the user does not match the current real UID,
357          * check if the change is allowed by SELinux policy.
358          */
359         if ((pw->pw_uid != getuid ())
360             && (is_selinux_enabled () > 0)
361             && (selinux_check_passwd_access (PASSWD__CHFN) != 0)) {
362                 fprintf (stderr, _("%s: Permission denied.\n"), Prog);
363                 closelog ();
364                 exit (E_NOPERM);
365         }
366 #endif
367
368 #ifndef USE_PAM
369         /*
370          * Non-privileged users are optionally authenticated (must enter the
371          * password of the user whose information is being changed) before
372          * any changes can be made. Idea from util-linux chfn/chsh. 
373          * --marekm
374          */
375         if (!amroot && getdef_bool ("CHFN_AUTH")) {
376                 passwd_check (pw->pw_name, pw->pw_passwd, "chfn");
377         }
378
379 #else                           /* !USE_PAM */
380         retval = PAM_SUCCESS;
381
382         pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
383         if (pampw == NULL) {
384                 retval = PAM_USER_UNKNOWN;
385         }
386
387         if (retval == PAM_SUCCESS) {
388                 retval = pam_start ("chfn", pampw->pw_name, &conv, &pamh);
389         }
390
391         if (retval == PAM_SUCCESS) {
392                 retval = pam_authenticate (pamh, 0);
393                 if (retval != PAM_SUCCESS) {
394                         pam_end (pamh, retval);
395                 }
396         }
397
398         if (retval == PAM_SUCCESS) {
399                 retval = pam_acct_mgmt (pamh, 0);
400                 if (retval != PAM_SUCCESS) {
401                         pam_end (pamh, retval);
402                 }
403         }
404
405         if (retval != PAM_SUCCESS) {
406                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
407                 exit (E_NOPERM);
408         }
409 #endif                          /* USE_PAM */
410 }
411
412 /*
413  * update_gecos - update the gecos fields in the password database
414  *
415  *      Commit the user's entry after changing her gecos field.
416  */
417 static void update_gecos (const char *user, char *gecos)
418 {
419         const struct passwd *pw;        /* The user's password file entry */
420         struct passwd pwent;            /* modified password file entry */
421
422         /*
423          * Before going any further, raise the ulimit to prevent colliding
424          * into a lowered ulimit, and set the real UID to root to protect
425          * against unexpected signals. Any keyboard signals are set to be
426          * ignored.
427          */
428         if (setuid (0) != 0) {
429                 fputs (_("Cannot change ID to root.\n"), stderr);
430                 SYSLOG ((LOG_ERR, "can't setuid(0)"));
431                 fail_exit (E_NOPERM);
432         }
433         pwd_init ();
434
435         /*
436          * The passwd entry is now ready to be committed back to the
437          * password file. Get a lock on the file and open it.
438          */
439         if (pw_lock () == 0) {
440                 fprintf (stderr,
441                          _("%s: cannot lock %s; try again later.\n"),
442                          Prog, pw_dbname ());
443                 fail_exit (E_NOPERM);
444         }
445         pw_locked = true;
446         if (pw_open (O_RDWR) == 0) {
447                 fprintf (stderr,
448                          _("%s: cannot open %s\n"), Prog, pw_dbname ());
449                 fail_exit (E_NOPERM);
450         }
451
452         /*
453          * Get the entry to update using pw_locate() - we want the real one
454          * from /etc/passwd, not the one from getpwnam() which could contain
455          * the shadow password if (despite the warnings) someone enables
456          * AUTOSHADOW (or SHADOW_COMPAT in libc).  --marekm
457          */
458         pw = pw_locate (user);
459         if (NULL == pw) {
460                 fprintf (stderr,
461                          _("%s: user '%s' does not exist in %s\n"),
462                          Prog, user, pw_dbname ());
463                 fail_exit (E_NOPERM);
464         }
465
466         /*
467          * Make a copy of the entry, then change the gecos field. The other
468          * fields remain unchanged.
469          */
470         pwent = *pw;
471         pwent.pw_gecos = gecos;
472
473         /*
474          * Update the passwd file entry. If there is a DBM file, update that
475          * entry as well.
476          */
477         if (pw_update (&pwent) == 0) {
478                 fprintf (stderr,
479                          _("%s: failed to prepare the new %s entry\n"),
480                          Prog, pw_dbname ());
481                 fail_exit (E_NOPERM);
482         }
483
484         /*
485          * Changes have all been made, so commit them and unlock the file.
486          */
487         if (pw_close () == 0) {
488                 fprintf (stderr, _("%s: failure while writing changes to %s\n"), Prog, pw_dbname ());
489                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
490                 fail_exit (E_NOPERM);
491         }
492         if (pw_unlock () == 0) {
493                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
494                 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
495                 /* continue */
496         }
497         pw_locked = false;
498 }
499
500 /*
501  * get_old_fields - parse the old gecos and use the old value for the fields
502  *                  which are not set on the command line
503  */
504 static void get_old_fields (const char *gecos)
505 {
506         char *cp;               /* temporary character pointer       */
507         char old_gecos[BUFSIZ]; /* buffer for old GECOS fields       */
508         STRFCPY (old_gecos, gecos);
509
510         /*
511          * Now get the full name. It is the first comma separated field in
512          * the GECOS field.
513          */
514         cp = copy_field (old_gecos, fflg ? (char *) 0 : fullnm, slop);
515
516         /*
517          * Now get the room number. It is the next comma separated field,
518          * if there is indeed one.
519          */
520         if (NULL != cp) {
521                 cp = copy_field (cp, rflg ? (char *) 0 : roomno, slop);
522         }
523
524         /*
525          * Now get the work phone number. It is the third field.
526          */
527         if (NULL != cp) {
528                 cp = copy_field (cp, wflg ? (char *) 0 : workph, slop);
529         }
530
531         /*
532          * Now get the home phone number. It is the fourth field.
533          */
534         if (NULL != cp) {
535                 cp = copy_field (cp, hflg ? (char *) 0 : homeph, slop);
536         }
537
538         /*
539          * Anything left over is "slop".
540          */
541         if ((NULL != cp) && !oflg) {
542                 if ('\0' != slop[0]) {
543                         strcat (slop, ",");
544                 }
545
546                 strcat (slop, cp);
547         }
548 }
549
550 /*
551  * check_fields - check all of the fields for valid information
552  *
553  *      It will not return if a field is not valid.
554  */
555 static void check_fields (void)
556 {
557         int err;
558         err = valid_field (fullnm, ":,=");
559         if (err > 0) {
560                 fprintf (stderr, _("%s: name with non-ASCII characters: '%s'\n"), Prog, fullnm);
561         } else if (err < 0) {
562                 fprintf (stderr, _("%s: invalid name: '%s'\n"), Prog, fullnm);
563                 fail_exit (E_NOPERM);
564         }
565         err = valid_field (roomno, ":,=");
566         if (err > 0) {
567                 fprintf (stderr, _("%s: room number with non-ASCII characters: '%s'\n"), Prog, roomno);
568         } else if (err < 0) {
569                 fprintf (stderr, _("%s: invalid room number: '%s'\n"),
570                          Prog, roomno);
571                 fail_exit (E_NOPERM);
572         }
573         if (valid_field (workph, ":,=") != 0) {
574                 fprintf (stderr, _("%s: invalid work phone: '%s'\n"),
575                          Prog, workph);
576                 fail_exit (E_NOPERM);
577         }
578         if (valid_field (homeph, ":,=") != 0) {
579                 fprintf (stderr, _("%s: invalid home phone: '%s'\n"),
580                          Prog, homeph);
581                 fail_exit (E_NOPERM);
582         }
583         err = valid_field (slop, ":");
584         if (err > 0) {
585                 fprintf (stderr, _("%s: '%s' contains non-ASCII characters\n"), Prog, slop);
586         } else if (err < 0) {
587                 fprintf (stderr,
588                          _("%s: '%s' contains illegal characters\n"),
589                          Prog, slop);
590                 fail_exit (E_NOPERM);
591         }
592 }
593
594 /*
595  * chfn - change a user's password file information
596  *
597  *      This command controls the GECOS field information in the password
598  *      file entry.
599  *
600  *      The valid options are
601  *
602  *      -f      full name
603  *      -r      room number
604  *      -w      work phone number
605  *      -h      home phone number
606  *      -o      other information (*)
607  *
608  *      (*) requires root permission to execute.
609  */
610 int main (int argc, char **argv)
611 {
612         const struct passwd *pw;        /* password file entry               */
613         char new_gecos[BUFSIZ]; /* buffer for new GECOS fields       */
614         char *user;
615
616         sanitize_env ();
617         (void) setlocale (LC_ALL, "");
618         (void) bindtextdomain (PACKAGE, LOCALEDIR);
619         (void) textdomain (PACKAGE);
620
621         /*
622          * This command behaves different for root and non-root
623          * users.
624          */
625         amroot = (getuid () == 0);
626
627         /*
628          * Get the program name. The program name is used as a
629          * prefix to most error messages.
630          */
631         Prog = Basename (argv[0]);
632
633         OPENLOG ("chfn");
634
635         /* parse the command line options */
636         process_flags (argc, argv);
637
638         /*
639          * Get the name of the user to check. It is either the command line
640          * name, or the name getlogin() returns.
641          */
642         if (optind < argc) {
643                 user = argv[optind];
644                 pw = xgetpwnam (user);
645                 if (NULL == pw) {
646                         fprintf (stderr, _("%s: user '%s' does not exist\n"), Prog,
647                                  user);
648                         fail_exit (E_NOPERM);
649                 }
650         } else {
651                 pw = get_my_pwent ();
652                 if (NULL == pw) {
653                         fprintf (stderr,
654                                  _("%s: Cannot determine your user name.\n"),
655                                  Prog);
656                         SYSLOG ((LOG_WARN, "Cannot determine the user name of the caller (UID %lu)",
657                                  (unsigned long) getuid ()));
658                         fail_exit (E_NOPERM);
659                 }
660                 user = xstrdup (pw->pw_name);
661         }
662
663 #ifdef  USE_NIS
664         /*
665          * Now we make sure this is a LOCAL password entry for this user ...
666          */
667         if (__ispwNIS ()) {
668                 char *nis_domain;
669                 char *nis_master;
670
671                 fprintf (stderr,
672                          _("%s: cannot change user '%s' on NIS client.\n"),
673                          Prog, user);
674
675                 if (!yp_get_default_domain (&nis_domain) &&
676                     !yp_master (nis_domain, "passwd.byname", &nis_master)) {
677                         fprintf (stderr,
678                                  _
679                                  ("%s: '%s' is the NIS master for this client.\n"),
680                                  Prog, nis_master);
681                 }
682                 fail_exit (E_NOPERM);
683         }
684 #endif
685
686         /* Check that the caller is allowed to change the gecos of the
687          * specified user */
688         check_perms (pw);
689
690         /* If some fields were not set on the command line, load the value from
691          * the old gecos fields. */
692         get_old_fields (pw->pw_gecos);
693
694         /*
695          * If none of the fields were changed from the command line, let the
696          * user interactively change them.
697          */
698         if (!fflg && !rflg && !wflg && !hflg && !oflg) {
699                 printf (_("Changing the user information for %s\n"), user);
700                 new_fields ();
701         }
702
703         /*
704          * Check all of the fields for valid information
705          */
706         check_fields ();
707
708         /*
709          * Build the new GECOS field by plastering all the pieces together,
710          * if they will fit ...
711          */
712         if ((strlen (fullnm) + strlen (roomno) + strlen (workph) +
713              strlen (homeph) + strlen (slop)) > (unsigned int) 80) {
714                 fprintf (stderr, _("%s: fields too long\n"), Prog);
715                 fail_exit (E_NOPERM);
716         }
717         snprintf (new_gecos, sizeof new_gecos, "%s,%s,%s,%s%s%s",
718                   fullnm, roomno, workph, homeph,
719                   ('\0' != slop[0]) ? "," : "", slop);
720
721         /* Rewrite the user's gecos in the passwd file */
722         update_gecos (user, new_gecos);
723
724         SYSLOG ((LOG_INFO, "changed user '%s' information", user));
725
726         nscd_flush_cache ("passwd");
727
728 #ifdef USE_PAM
729         (void) pam_end (pamh, PAM_SUCCESS);
730 #endif                          /* USE_PAM */
731
732         closelog ();
733         exit (E_SUCCESS);
734 }
735