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