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