]> granicus.if.org Git - shadow/blob - src/usermod.c
Additional PAM cleanup:
[shadow] / src / usermod.c
1 /*
2  * Copyright (c) 1991 - 1994, Julianne Frances Haugh
3  * Copyright (c) 1996 - 2000, Marek Michałkiewicz
4  * Copyright (c) 2000 - 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 <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <getopt.h>
41 #include <grp.h>
42 #include <lastlog.h>
43 #include <pwd.h>
44 #ifdef ACCT_TOOLS_SETUID
45 #ifdef USE_PAM
46 #include "pam_defs.h"
47 #endif                          /* USE_PAM */
48 #endif                          /* ACCT_TOOLS_SETUID */
49 #include <stdio.h>
50 #include <sys/stat.h>
51 #include <sys/types.h>
52 #include <time.h>
53 #include "chkname.h"
54 #include "defines.h"
55 #include "faillog.h"
56 #include "getdef.h"
57 #include "groupio.h"
58 #include "nscd.h"
59 #include "prototypes.h"
60 #include "pwauth.h"
61 #include "pwio.h"
62 #ifdef  SHADOWGRP
63 #include "sgroupio.h"
64 #endif
65 #include "shadowio.h"
66 /*
67  * exit status values
68  * for E_GRP_UPDATE and E_NOSPACE (not used yet), other update requests
69  * will be implemented (as documented in the Solaris 2.x man page).
70  */
71 #define E_SUCCESS       0       /* success */
72 #define E_PW_UPDATE     1       /* can't update password file */
73 #define E_USAGE         2       /* invalid command syntax */
74 #define E_BAD_ARG       3       /* invalid argument to option */
75 #define E_UID_IN_USE    4       /* UID already in use (and no -o) */
76 /* #define E_BAD_PWFILE 5          passwd file contains errors */
77 #define E_NOTFOUND      6       /* specified user/group doesn't exist */
78 #define E_USER_BUSY     8       /* user to modify is logged in */
79 #define E_NAME_IN_USE   9       /* username already in use */
80 #define E_GRP_UPDATE    10      /* can't update group file */
81 /* #define E_NOSPACE    11         insufficient space to move home dir */
82 #define E_HOMEDIR       12      /* unable to complete home dir move */
83 #define VALID(s)        (strcspn (s, ":\n") == strlen (s))
84 /*
85  * Global variables
86  */
87 char *Prog;
88
89 static char *user_name;
90 static char *user_newname;
91 static char *user_pass;
92 static uid_t user_id;
93 static uid_t user_newid;
94 static gid_t user_gid;
95 static gid_t user_newgid;
96 static char *user_comment;
97 static char *user_newcomment;
98 static char *user_home;
99 static char *user_newhome;
100 static char *user_shell;
101 static char *user_newshell;
102 static long user_expire;
103 static long user_newexpire;
104 static long user_inactive;
105 static long user_newinactive;
106 static long sys_ngroups;
107 static char **user_groups;      /* NULL-terminated list */
108
109 static bool
110     aflg = false,               /* append to existing secondary group set */
111     cflg = false,               /* new comment (GECOS) field */
112     dflg = false,               /* new home directory */
113     eflg = false,               /* days since 1970-01-01 when account becomes expired */
114     fflg = false,               /* days until account with expired password is locked */
115     gflg = false,               /* new primary group ID */
116     Gflg = false,               /* new secondary group set */
117     Lflg = false,               /* lock the password */
118     lflg = false,               /* new user name */
119     mflg = false,               /* create user's home directory if it doesn't exist */
120     oflg = false,               /* permit non-unique user ID to be specified with -u */
121     pflg = false,               /* new encrypted password */
122     sflg = false,               /* new shell program */
123     uflg = false,               /* specify new user ID */
124     Uflg = false;               /* unlock the password */
125
126 static bool is_shadow_pwd;
127
128 #ifdef SHADOWGRP
129 static bool is_shadow_grp;
130 #endif
131
132 static bool pw_locked  = false;
133 static bool spw_locked = false;
134 static bool gr_locked  = false;
135 #ifdef SHADOWGRP
136 static bool sgr_locked = false;
137 #endif
138
139
140 /* local function prototypes */
141 static void date_to_str (char *buf, size_t maxsize,
142                          long int date, const char *negativ);
143 static int get_groups (char *);
144 static void usage (void);
145 static void new_pwent (struct passwd *);
146
147 static void new_spent (struct spwd *);
148 static void fail_exit (int);
149 static void update_group (void);
150
151 #ifdef SHADOWGRP
152 static void update_gshadow (void);
153 #endif
154 static void grp_update (void);
155
156 static long get_number (const char *);
157 static uid_t get_id (const char *);
158 static void process_flags (int, char **);
159 static void close_files (void);
160 static void open_files (void);
161 static void usr_update (void);
162 static void move_home (void);
163 static void update_lastlog (void);
164 static void update_faillog (void);
165
166 #ifndef NO_MOVE_MAILBOX
167 static void move_mailbox (void);
168 #endif
169
170 static void date_to_str (char *buf, size_t maxsize,
171                          long int date, const char *negativ)
172 {
173         struct tm *tp;
174
175         if ((negativ != NULL) && (date < 0)) {
176                 strncpy (buf, negativ, maxsize);
177         } else {
178                 time_t t = (time_t) date;
179                 tp = gmtime (&t);
180 #ifdef HAVE_STRFTIME
181                 strftime (buf, maxsize, "%Y-%m-%d", tp);
182 #else
183                 snprintf (buf, maxsize, "%04d-%02d-%02d",
184                           tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday);
185 #endif                          /* HAVE_STRFTIME */
186         }
187         buf[maxsize - 1] = '\0';
188 }
189 /*
190  * Had to move this over from useradd.c since we have groups named
191  * "56k-family"... ergh.
192  * --Pac.
193  */
194 static struct group *getgr_nam_gid (const char *grname)
195 {
196         long val;
197         char *errptr;
198
199         val = strtol (grname, &errptr, 10);
200         if (*grname != '\0' && *errptr == '\0' && errno != ERANGE && val >= 0) {
201                 return xgetgrgid ((gid_t) val);
202         }
203         return xgetgrnam (grname);
204 }
205
206 /*
207  * get_groups - convert a list of group names to an array of group IDs
208  *
209  *      get_groups() takes a comma-separated list of group names and
210  *      converts it to a NULL-terminated array. Any unknown group names are
211  *      reported as errors.
212  */
213 static int get_groups (char *list)
214 {
215         char *cp;
216         const struct group *grp;
217         int errors = 0;
218         int ngroups = 0;
219
220         /*
221          * Initialize the list to be empty
222          */
223         user_groups[0] = (char *) 0;
224
225         if ('\0' == *list) {
226                 return 0;
227         }
228
229         /*
230          * So long as there is some data to be converted, strip off each
231          * name and look it up. A mix of numerical and string values for
232          * group identifiers is permitted.
233          */
234         do {
235                 /*
236                  * Strip off a single name from the list
237                  */
238                 cp = strchr (list, ',');
239                 if (NULL != cp) {
240                         *cp = '\0';
241                         cp++;
242                 }
243
244                 /*
245                  * Names starting with digits are treated as numerical GID
246                  * values, otherwise the string is looked up as is.
247                  */
248                 grp = getgr_nam_gid (list);
249
250                 /*
251                  * There must be a match, either by GID value or by
252                  * string name.
253                  */
254                 if (NULL == grp) {
255                         fprintf (stderr, _("%s: group '%s' does not exist\n"),
256                                  Prog, list);
257                         errors++;
258                 }
259                 list = cp;
260
261                 /*
262                  * If the group doesn't exist, don't dump core. Instead,
263                  * try the next one.  --marekm
264                  */
265                 if (NULL == grp) {
266                         continue;
267                 }
268
269 #ifdef  USE_NIS
270                 /*
271                  * Don't add this group if they are an NIS group. Tell the
272                  * user to go to the server for this group.
273                  */
274                 if (__isgrNIS ()) {
275                         fprintf (stderr,
276                                  _("%s: group '%s' is a NIS group.\n"),
277                                  Prog, grp->gr_name);
278                         continue;
279                 }
280 #endif
281
282                 if (ngroups == sys_ngroups) {
283                         fprintf (stderr,
284                                  _("%s: too many groups specified (max %d).\n"),
285                                  Prog, ngroups);
286                         break;
287                 }
288
289                 /*
290                  * Add the group name to the user's list of groups.
291                  */
292                 user_groups[ngroups++] = xstrdup (grp->gr_name);
293         } while (NULL != list);
294
295         user_groups[ngroups] = (char *) 0;
296
297         /*
298          * Any errors in finding group names are fatal
299          */
300         if (0 != errors) {
301                 return -1;
302         }
303
304         return 0;
305 }
306
307 /*
308  * usage - display usage message and exit
309  */
310 static void usage (void)
311 {
312         fputs (_("Usage: usermod [options] LOGIN\n"
313                  "\n"
314                  "Options:\n"
315                  "  -c, --comment COMMENT         new value of the GECOS field\n"
316                  "  -d, --home HOME_DIR           new home directory for the user account\n"
317                  "  -e, --expiredate EXPIRE_DATE  set account expiration date to EXPIRE_DATE\n"
318                  "  -f, --inactive INACTIVE       set password inactive after expiration\n"
319                  "                                to INACTIVE\n"
320                  "  -g, --gid GROUP               force use GROUP as new primary group\n"
321                  "  -G, --groups GROUPS           new list of supplementary GROUPS\n"
322                  "  -a, --append                  append the user to the supplemental GROUPS\n"
323                  "                                mentioned by the -G option without removing\n"
324                  "                                him/her from other groups\n"
325                  "  -h, --help                    display this help message and exit\n"
326                  "  -l, --login NEW_LOGIN         new value of the login name\n"
327                  "  -L, --lock                    lock the user account\n"
328                  "  -m, --move-home               move contents of the home directory to the\n"
329                  "                                new location (use only with -d)\n"
330                  "  -o, --non-unique              allow using duplicate (non-unique) UID\n"
331                  "  -p, --password PASSWORD       use encrypted password for the new password\n"
332                  "  -s, --shell SHELL             new login shell for the user account\n"
333                  "  -u, --uid UID                 new UID for the user account\n"
334                  "  -U, --unlock                  unlock the user account\n"
335                  "\n"), stderr);
336         exit (E_USAGE);
337 }
338
339 /*
340  * update encrypted password string (for both shadow and non-shadow
341  * passwords)
342  */
343 static char *new_pw_passwd (char *pw_pass)
344 {
345         if (Lflg && ('!' != pw_pass[0])) {
346                 char *buf = xmalloc (strlen (pw_pass) + 2);
347
348 #ifdef WITH_AUDIT
349                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
350                               "updating passwd",
351                               user_newname, (unsigned int) user_newid, 0);
352 #endif
353                 SYSLOG ((LOG_INFO, "lock user '%s' password", user_newname));
354                 strcpy (buf, "!");
355                 strcat (buf, pw_pass);
356                 pw_pass = buf;
357         } else if (Uflg && pw_pass[0] == '!') {
358                 char *s;
359
360                 if (pw_pass[1] == '\0') {
361                         fprintf (stderr,
362                                  _("%s: unlocking the user's password would result in a passwordless account.\n"
363                                    "You should set a password with usermod -p to unlock this user's password.\n"),
364                                  Prog);
365                         return pw_pass;
366                 }
367
368 #ifdef WITH_AUDIT
369                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
370                               "updating password",
371                               user_newname, (unsigned int) user_newid, 0);
372 #endif
373                 SYSLOG ((LOG_INFO, "unlock user '%s' password", user_newname));
374                 s = pw_pass;
375                 while ('\0' != *s) {
376                         *s = *(s + 1);
377                         s++;
378                 }
379         } else if (pflg) {
380 #ifdef WITH_AUDIT
381                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
382                               "changing password",
383                               user_newname, (unsigned int) user_newid, 1);
384 #endif
385                 SYSLOG ((LOG_INFO, "change user '%s' password", user_newname));
386                 pw_pass = xstrdup (user_pass);
387         }
388         return pw_pass;
389 }
390
391 /*
392  * new_pwent - initialize the values in a password file entry
393  *
394  *      new_pwent() takes all of the values that have been entered and fills
395  *      in a (struct passwd) with them.
396  */
397 static void new_pwent (struct passwd *pwent)
398 {
399         if (lflg) {
400 #ifdef WITH_AUDIT
401                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
402                               "changing name",
403                               user_newname, (unsigned int) user_newid, 1);
404 #endif
405                 SYSLOG ((LOG_INFO, "change user name '%s' to '%s'",
406                          pwent->pw_name, user_newname));
407                 pwent->pw_name = xstrdup (user_newname);
408         }
409         if (!is_shadow_pwd) {
410                 pwent->pw_passwd =
411                     new_pw_passwd (pwent->pw_passwd);
412         }
413
414         if (uflg) {
415 #ifdef WITH_AUDIT
416                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
417                               "changing uid",
418                               user_newname, (unsigned int) user_newid, 1);
419 #endif
420                 SYSLOG ((LOG_INFO,
421                          "change user '%s' UID from '%d' to '%d'",
422                          pwent->pw_name, pwent->pw_uid, user_newid));
423                 pwent->pw_uid = user_newid;
424         }
425         if (gflg) {
426 #ifdef WITH_AUDIT
427                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
428                               "changing primary group",
429                               user_newname, (unsigned int) user_newid, 1);
430 #endif
431                 SYSLOG ((LOG_INFO,
432                          "change user '%s' GID from '%d' to '%d'",
433                          pwent->pw_name, pwent->pw_gid, user_newgid));
434                 pwent->pw_gid = user_newgid;
435         }
436         if (cflg) {
437 #ifdef WITH_AUDIT
438                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
439                               "changing comment",
440                               user_newname, (unsigned int) user_newid, 1);
441 #endif
442                 pwent->pw_gecos = user_newcomment;
443         }
444
445         if (dflg) {
446 #ifdef WITH_AUDIT
447                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
448                               "changing home directory",
449                               user_newname, (unsigned int) user_newid, 1);
450 #endif
451                 SYSLOG ((LOG_INFO,
452                          "change user '%s' home from '%s' to '%s'",
453                          pwent->pw_name, pwent->pw_dir, user_newhome));
454                 pwent->pw_dir = user_newhome;
455         }
456         if (sflg) {
457 #ifdef WITH_AUDIT
458                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
459                               "changing user shell",
460                               user_newname, (unsigned int) user_newid, 1);
461 #endif
462                 SYSLOG ((LOG_INFO, "change user '%s' shell from '%s' to '%s'",
463                          pwent->pw_name, pwent->pw_shell, user_newshell));
464                 pwent->pw_shell = user_newshell;
465         }
466 }
467
468 /*
469  * new_spent - initialize the values in a shadow password file entry
470  *
471  *      new_spent() takes all of the values that have been entered and fills
472  *      in a (struct spwd) with them.
473  */
474 static void new_spent (struct spwd *spent)
475 {
476         if (lflg) {
477                 spent->sp_namp = xstrdup (user_newname);
478         }
479
480         if (fflg) {
481 #ifdef WITH_AUDIT
482                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
483                               "changing inactive days",
484                               user_newname, (unsigned int) user_newid, 1);
485 #endif
486                 SYSLOG ((LOG_INFO,
487                          "change user '%s' inactive from '%ld' to '%ld'",
488                          spent->sp_namp, spent->sp_inact, user_newinactive));
489                 spent->sp_inact = user_newinactive;
490         }
491         if (eflg) {
492                 /* log dates rather than numbers of days. */
493                 char new_exp[16], old_exp[16];
494                 date_to_str (new_exp, sizeof(new_exp),
495                              user_newexpire * DAY, "never");
496                 date_to_str (old_exp, sizeof(old_exp),
497                              user_expire * DAY, "never");
498 #ifdef WITH_AUDIT
499                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
500                               "changing expiration date",
501                               user_newname, (unsigned int) user_newid, 1);
502 #endif
503                 SYSLOG ((LOG_INFO,
504                          "change user '%s' expiration from '%s' to '%s'",
505                          spent->sp_namp, old_exp, new_exp));
506                 spent->sp_expire = user_newexpire;
507         }
508         spent->sp_pwdp = new_pw_passwd (spent->sp_pwdp);
509         if (pflg) {
510                 spent->sp_lstchg = (long) time ((time_t *) 0) / SCALE;
511         }
512 }
513
514 /*
515  * fail_exit - exit with an error code after unlocking files
516  */
517 static void fail_exit (int code)
518 {
519         if (gr_locked) {
520                 if (gr_unlock () == 0) {
521                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
522                         SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
523                         /* continue */
524                 }
525         }
526 #ifdef  SHADOWGRP
527         if (sgr_locked) {
528                 if (sgr_unlock () == 0) {
529                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
530                         SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
531                         /* continue */
532                 }
533         }
534 #endif
535         if (spw_locked) {
536                 if (spw_unlock () == 0) {
537                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
538                         SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
539                         /* continue */
540                 }
541         }
542         if (pw_locked) {
543                 if (pw_unlock () == 0) {
544                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
545                         SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
546                         /* continue */
547                 }
548         }
549
550 #ifdef WITH_AUDIT
551         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
552                       "modifying account",
553                       user_name, AUDIT_NO_ID, 0);
554 #endif
555         exit (code);
556 }
557
558
559 static void update_group (void)
560 {
561         bool is_member;
562         bool was_member;
563         bool changed;
564         const struct group *grp;
565         struct group *ngrp;
566
567         changed = false;
568
569         /*
570          * Scan through the entire group file looking for the groups that
571          * the user is a member of.
572          */
573         while ((grp = gr_next ()) != NULL) {
574                 /*
575                  * See if the user specified this group as one of their
576                  * concurrent groups.
577                  */
578                 was_member = is_on_list (grp->gr_mem, user_name);
579                 is_member = Gflg && (   (was_member && aflg)
580                                      || is_on_list (user_groups, grp->gr_name));
581
582                 if (!was_member && !is_member) {
583                         continue;
584                 }
585
586                 ngrp = __gr_dup (grp);
587                 if (NULL == ngrp) {
588                         fprintf (stderr,
589                                  _("%s: Out of memory. Cannot update %s.\n"),
590                                  Prog, gr_dbname ());
591                         fail_exit (E_GRP_UPDATE);
592                 }
593
594                 if (was_member && (!Gflg || is_member)) {
595                         if (lflg) {
596                                 ngrp->gr_mem = del_list (ngrp->gr_mem,
597                                                          user_name);
598                                 ngrp->gr_mem = add_list (ngrp->gr_mem,
599                                                          user_newname);
600                                 changed = true;
601 #ifdef WITH_AUDIT
602                                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
603                                               "changing group member",
604                                               user_newname, AUDIT_NO_ID, 1);
605 #endif
606                                 SYSLOG ((LOG_INFO,
607                                          "change '%s' to '%s' in group '%s'",
608                                          user_name, user_newname,
609                                          ngrp->gr_name));
610                         }
611                 } else if (was_member && !aflg && Gflg && !is_member) {
612                         ngrp->gr_mem = del_list (ngrp->gr_mem, user_name);
613                         changed = true;
614 #ifdef WITH_AUDIT
615                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
616                                       "removing group member",
617                                       user_name, AUDIT_NO_ID, 1);
618 #endif
619                         SYSLOG ((LOG_INFO, "delete '%s' from group '%s'",
620                                  user_name, ngrp->gr_name));
621                 } else if (!was_member && Gflg && is_member) {
622                         ngrp->gr_mem = add_list (ngrp->gr_mem, user_newname);
623                         changed = true;
624 #ifdef WITH_AUDIT
625                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
626                                       "adding user to group",
627                                       user_name, AUDIT_NO_ID, 1);
628 #endif
629                         SYSLOG ((LOG_INFO, "add '%s' to group '%s'",
630                                  user_newname, ngrp->gr_name));
631                 }
632                 if (!changed) {
633                         continue;
634                 }
635
636                 changed = false;
637                 if (gr_update (ngrp) == 0) {
638                         fprintf (stderr,
639                                  _("%s: failed to prepare the new %s entry '%s'\n"),
640                                  Prog, gr_dbname (), ngrp->gr_name);
641                         SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'", gr_dbname (), ngrp->gr_name));
642                         fail_exit (E_GRP_UPDATE);
643                 }
644         }
645 }
646
647 #ifdef SHADOWGRP
648 static void update_gshadow (void)
649 {
650         bool is_member;
651         bool was_member;
652         bool was_admin;
653         bool changed;
654         const struct sgrp *sgrp;
655         struct sgrp *nsgrp;
656
657         changed = false;
658
659         /*
660          * Scan through the entire shadow group file looking for the groups
661          * that the user is a member of.
662          */
663         while ((sgrp = sgr_next ()) != NULL) {
664
665                 /*
666                  * See if the user was a member of this group
667                  */
668                 was_member = is_on_list (sgrp->sg_mem, user_name);
669
670                 /*
671                  * See if the user was an administrator of this group
672                  */
673                 was_admin = is_on_list (sgrp->sg_adm, user_name);
674
675                 /*
676                  * See if the user specified this group as one of their
677                  * concurrent groups.
678                  */
679                 is_member = Gflg && is_on_list (user_groups, sgrp->sg_name);
680                 is_member = Gflg && (   (was_member && aflg)
681                                      || is_on_list (user_groups, sgrp->sg_name));
682
683                 if (!was_member && !was_admin && !is_member) {
684                         continue;
685                 }
686
687                 nsgrp = __sgr_dup (sgrp);
688                 if (NULL == nsgrp) {
689                         fprintf (stderr,
690                                  _("%s: Out of memory. Cannot update %s.\n"),
691                                  Prog, sgr_dbname ());
692                         fail_exit (E_GRP_UPDATE);
693                 }
694
695                 if (was_admin && lflg) {
696                         nsgrp->sg_adm = del_list (nsgrp->sg_adm, user_name);
697                         nsgrp->sg_adm = add_list (nsgrp->sg_adm, user_newname);
698                         changed = true;
699 #ifdef WITH_AUDIT
700                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
701                                       "changing admin name in shadow group",
702                                       user_name, AUDIT_NO_ID, 1);
703 #endif
704                         SYSLOG ((LOG_INFO,
705                                  "change admin '%s' to '%s' in shadow group '%s'",
706                                  user_name, user_newname, nsgrp->sg_name));
707                 }
708                 if (was_member && (!Gflg || is_member)) {
709                         if (lflg) {
710                                 nsgrp->sg_mem = del_list (nsgrp->sg_mem,
711                                                           user_name);
712                                 nsgrp->sg_mem = add_list (nsgrp->sg_mem,
713                                                           user_newname);
714                                 changed = true;
715 #ifdef WITH_AUDIT
716                                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
717                                               "changing member in shadow group",
718                                               user_name, AUDIT_NO_ID, 1);
719 #endif
720                                 SYSLOG ((LOG_INFO,
721                                          "change '%s' to '%s' in shadow group '%s'",
722                                          user_name, user_newname,
723                                          nsgrp->sg_name));
724                         }
725                 } else if (was_member && !aflg && Gflg && !is_member) {
726                         nsgrp->sg_mem = del_list (nsgrp->sg_mem, user_name);
727                         changed = true;
728 #ifdef WITH_AUDIT
729                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
730                                       "removing user from shadow group",
731                                       user_name, AUDIT_NO_ID, 1);
732 #endif
733                         SYSLOG ((LOG_INFO,
734                                  "delete '%s' from shadow group '%s'",
735                                  user_name, nsgrp->sg_name));
736                 } else if (!was_member && Gflg && is_member) {
737                         nsgrp->sg_mem = add_list (nsgrp->sg_mem, user_newname);
738                         changed = true;
739 #ifdef WITH_AUDIT
740                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
741                                       "adding user to shadow group",
742                                       user_newname, AUDIT_NO_ID, 1);
743 #endif
744                         SYSLOG ((LOG_INFO, "add '%s' to shadow group '%s'",
745                                  user_newname, nsgrp->sg_name));
746                 }
747                 if (!changed) {
748                         continue;
749                 }
750
751                 changed = false;
752
753                 /* 
754                  * Update the group entry to reflect the changes.
755                  */
756                 if (sgr_update (nsgrp) == 0) {
757                         fprintf (stderr,
758                                  _("%s: failed to prepare the new %s entry '%s'\n"),
759                                  Prog, sgr_dbname (), nsgrp->sg_name);
760                         SYSLOG ((LOG_WARN, "failed to prepare the new %s entry '%s'",
761                                  sgr_dbname (), nsgrp->sg_name));
762                         fail_exit (E_GRP_UPDATE);
763                 }
764         }
765 }
766 #endif                          /* SHADOWGRP */
767
768 /*
769  * grp_update - add user to secondary group set
770  *
771  *      grp_update() takes the secondary group set given in user_groups and
772  *      adds the user to each group given by that set.
773  */
774 static void grp_update (void)
775 {
776         update_group ();
777 #ifdef SHADOWGRP
778         if (is_shadow_grp) {
779                 update_gshadow ();
780         }
781 #endif
782 }
783
784 static long get_number (const char *numstr)
785 {
786         long val;
787         char *errptr;
788
789         val = strtol (numstr, &errptr, 10);
790         if (('\0' != *errptr) || (ERANGE == errno)) {
791                 fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog,
792                          numstr);
793                 exit (E_BAD_ARG);
794         }
795         return val;
796 }
797
798 static uid_t get_id (const char *uidstr)
799 {
800         long val;
801         char *errptr;
802
803         val = strtol (uidstr, &errptr, 10);
804         if (('\0' != *errptr) || (ERANGE == errno) || (val < 0)) {
805                 fprintf (stderr, _("%s: invalid numeric argument '%s'\n"), Prog,
806                          uidstr);
807                 exit (E_BAD_ARG);
808         }
809         return (uid_t) val;
810 }
811
812 /*
813  * process_flags - perform command line argument setting
814  *
815  *      process_flags() interprets the command line arguments and sets the
816  *      values that the user will be created with accordingly. The values
817  *      are checked for sanity.
818  */
819 static void process_flags (int argc, char **argv)
820 {
821         const struct group *grp;
822
823         bool anyflag = false;
824
825         if ((1 == argc) || ('-' == argv[argc - 1][0])) {
826                 usage ();
827         }
828
829         {
830                 const struct passwd *pwd;
831                 /* local, no need for xgetpwnam */
832                 pwd = getpwnam (argv[argc - 1]);
833                 if (NULL == pwd) {
834                         fprintf (stderr, _("%s: user '%s' does not exist\n"),
835                                  Prog, argv[argc - 1]);
836                         exit (E_NOTFOUND);
837                 }
838
839                 user_name = argv[argc - 1];
840                 user_id = pwd->pw_uid;
841                 user_gid = pwd->pw_gid;
842                 user_comment = xstrdup (pwd->pw_gecos);
843                 user_home = xstrdup (pwd->pw_dir);
844                 user_shell = xstrdup (pwd->pw_shell);
845         }
846         user_newname = user_name;
847         user_newid = user_id;
848         user_newgid = user_gid;
849         user_newcomment = user_comment;
850         user_newhome = user_home;
851         user_newshell = user_shell;
852
853 #ifdef  USE_NIS
854         /*
855          * Now make sure it isn't an NIS user.
856          */
857         if (__ispwNIS ()) {
858                 char *nis_domain;
859                 char *nis_master;
860
861                 fprintf (stderr, _("%s: user %s is a NIS user\n"),
862                          Prog, user_name);
863
864                 if (   !yp_get_default_domain (&nis_domain)
865                     && !yp_master (nis_domain, "passwd.byname", &nis_master)) {
866                         fprintf (stderr, _("%s: %s is the NIS master\n"),
867                                  Prog, nis_master);
868                 }
869                 exit (E_NOTFOUND);
870         }
871 #endif
872
873         {
874                 const struct spwd *spwd = NULL;
875                 /* local, no need for xgetspnam */
876                 if (is_shadow_pwd && ((spwd = getspnam (user_name)) != NULL)) {
877                         user_expire = spwd->sp_expire;
878                         user_inactive = spwd->sp_inact;
879                         user_newexpire = user_expire;
880                         user_newinactive = user_inactive;
881                 }
882         }
883
884         {
885                 /*
886                  * Parse the command line options.
887                  */
888                 int c;
889                 static struct option long_options[] = {
890                         {"append", no_argument, NULL, 'a'},
891                         {"comment", required_argument, NULL, 'c'},
892                         {"home", required_argument, NULL, 'd'},
893                         {"expiredate", required_argument, NULL, 'e'},
894                         {"inactive", required_argument, NULL, 'f'},
895                         {"gid", required_argument, NULL, 'g'},
896                         {"groups", required_argument, NULL, 'G'},
897                         {"help", no_argument, NULL, 'h'},
898                         {"login", required_argument, NULL, 'l'},
899                         {"lock", no_argument, NULL, 'L'},
900                         {"move-home", no_argument, NULL, 'm'},
901                         {"non-unique", no_argument, NULL, 'o'},
902                         {"password", required_argument, NULL, 'p'},
903                         {"shell", required_argument, NULL, 's'},
904                         {"uid", required_argument, NULL, 'u'},
905                         {"unlock", no_argument, NULL, 'U'},
906                         {NULL, 0, NULL, '\0'}
907                 };
908                 while ((c =
909                         getopt_long (argc, argv, "ac:d:e:f:g:G:hl:Lmop:s:u:U",
910                                      long_options, NULL)) != -1) {
911                         switch (c) {
912                         case 'a':
913                                 aflg = true;
914                                 break;
915                         case 'c':
916                                 if (!VALID (optarg)) {
917                                         fprintf (stderr,
918                                                  _("%s: invalid field '%s'\n"),
919                                                  Prog, optarg);
920                                         exit (E_BAD_ARG);
921                                 }
922                                 user_newcomment = optarg;
923                                 cflg = true;
924                                 break;
925                         case 'd':
926                                 if (!VALID (optarg)) {
927                                         fprintf (stderr,
928                                                  _("%s: invalid field '%s'\n"),
929                                                  Prog, optarg);
930                                         exit (E_BAD_ARG);
931                                 }
932                                 dflg = true;
933                                 user_newhome = optarg;
934                                 break;
935                         case 'e':
936                                 if ('\0' != *optarg) {
937                                         user_newexpire = strtoday (optarg);
938                                         if (user_newexpire == -1) {
939                                                 fprintf (stderr,
940                                                          _
941                                                          ("%s: invalid date '%s'\n"),
942                                                          Prog, optarg);
943                                                 exit (E_BAD_ARG);
944                                         }
945                                         user_newexpire *= DAY / SCALE;
946                                 } else {
947                                         user_newexpire = -1;
948                                 }
949                                 eflg = true;
950                                 break;
951                         case 'f':
952                                 user_newinactive = get_number (optarg);
953                                 fflg = true;
954                                 break;
955                         case 'g':
956                                 grp = getgr_nam_gid (optarg);
957                                 if (NULL == grp) {
958                                         fprintf (stderr,
959                                                  _("%s: group '%s' does not exist\n"),
960                                                  Prog, optarg);
961                                         exit (E_NOTFOUND);
962                                 }
963                                 user_newgid = grp->gr_gid;
964                                 gflg = true;
965                                 break;
966                         case 'G':
967                                 if (get_groups (optarg) != 0) {
968                                         exit (E_NOTFOUND);
969                                 }
970                                 Gflg = true;
971                                 break;
972                         case 'l':
973                                 if (!is_valid_user_name (optarg)) {
974                                         fprintf (stderr,
975                                                  _("%s: invalid field '%s'\n"),
976                                                  Prog, optarg);
977                                         exit (E_BAD_ARG);
978                                 }
979                                 lflg = true;
980                                 user_newname = optarg;
981                                 break;
982                         case 'L':
983                                 Lflg = true;
984                                 break;
985                         case 'm':
986                                 mflg = true;
987                                 break;
988                         case 'o':
989                                 oflg = true;
990                                 break;
991                         case 'p':
992                                 user_pass = optarg;
993                                 pflg = true;
994                                 break;
995                         case 's':
996                                 if (!VALID (optarg)) {
997                                         fprintf (stderr,
998                                                  _("%s: invalid field '%s'\n"),
999                                                  Prog, optarg);
1000                                         exit (E_BAD_ARG);
1001                                 }
1002                                 user_newshell = optarg;
1003                                 sflg = true;
1004                                 break;
1005                         case 'u':
1006                                 user_newid = get_id (optarg);
1007                                 uflg = true;
1008                                 break;
1009                         case 'U':
1010                                 Uflg = true;
1011                                 break;
1012                         default:
1013                                 usage ();
1014                         }
1015                         anyflag = true;
1016                 }
1017         }
1018
1019         if (!anyflag) {
1020                 fprintf (stderr, _("%s: no flags given\n"), Prog);
1021                 exit (E_USAGE);
1022         }
1023
1024         if (user_newid == user_id) {
1025                 uflg = false;
1026                 oflg = false;
1027         }
1028         if (user_newgid == user_gid) {
1029                 gflg = false;
1030         }
1031         if (strcmp (user_newshell, user_shell) == 0) {
1032                 sflg = false;
1033         }
1034         if (strcmp (user_newname, user_name) == 0) {
1035                 lflg = false;
1036         }
1037         if (user_newinactive == user_inactive) {
1038                 fflg = false;
1039         }
1040         if (user_newexpire == user_expire) {
1041                 eflg = false;
1042         }
1043         if (strcmp (user_newhome, user_home) == 0) {
1044                 dflg = false;
1045                 mflg = false;
1046         }
1047         if (strcmp (user_newcomment, user_comment) == 0) {
1048                 cflg = false;
1049         }
1050
1051         if (!(Uflg || uflg || sflg || pflg || oflg || mflg || Lflg ||
1052               lflg || Gflg || gflg || fflg || eflg || dflg || cflg)) {
1053                 fprintf (stderr, _("%s: no changes\n"), Prog);
1054                 exit (E_SUCCESS);
1055         }
1056
1057         if (!is_shadow_pwd && (eflg || fflg)) {
1058                 fprintf (stderr,
1059                          _
1060                          ("%s: shadow passwords required for -e and -f\n"),
1061                          Prog);
1062                 exit (E_USAGE);
1063         }
1064
1065         if (optind != argc - 1) {
1066                 usage ();
1067         }
1068
1069         if (aflg && (!Gflg)) {
1070                 fprintf (stderr,
1071                          _("%s: %s flag is only allowed with the %s flag\n"),
1072                          Prog, "-a", "-G");
1073                 usage ();
1074                 exit (E_USAGE);
1075         }
1076
1077         if ((Lflg && (pflg || Uflg)) || (pflg && Uflg)) {
1078                 fprintf (stderr,
1079                          _("%s: the -L, -p, and -U flags are exclusive\n"),
1080                          Prog);
1081                 usage ();
1082                 exit (E_USAGE);
1083         }
1084
1085         if (oflg && !uflg) {
1086                 fprintf (stderr,
1087                          _("%s: %s flag is only allowed with the %s flag\n"),
1088                          Prog, "-o", "-u");
1089                 usage ();
1090                 exit (E_USAGE);
1091         }
1092
1093         if (mflg && !dflg) {
1094                 fprintf (stderr,
1095                          _("%s: %s flag is only allowed with the %s flag\n"),
1096                          Prog, "-m", "-d");
1097                 usage ();
1098                 exit (E_USAGE);
1099         }
1100
1101         /* local, no need for xgetpwnam */
1102         if (lflg && (getpwnam (user_newname) != NULL)) {
1103                 fprintf (stderr, _("%s: user '%s' already exists\n"), Prog, user_newname);
1104                 exit (E_NAME_IN_USE);
1105         }
1106
1107         /* local, no need for xgetpwuid */
1108         if (uflg && !oflg && (getpwuid (user_newid) != NULL)) {
1109                 fprintf (stderr, _("%s: UID '%lu' already exists\n"),
1110                          Prog, (unsigned long) user_newid);
1111                 exit (E_UID_IN_USE);
1112         }
1113 }
1114
1115 /*
1116  * close_files - close all of the files that were opened
1117  *
1118  *      close_files() closes all of the files that were opened for this new
1119  *      user. This causes any modified entries to be written out.
1120  */
1121 static void close_files (void)
1122 {
1123         if (pw_close () == 0) {
1124                 fprintf (stderr,
1125                          _("%s: failure while writing changes to %s\n"),
1126                          Prog, pw_dbname ());
1127                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", pw_dbname ()));
1128                 fail_exit (E_PW_UPDATE);
1129         }
1130         if (is_shadow_pwd && (spw_close () == 0)) {
1131                 fprintf (stderr,
1132                          _("%s: failure while writing changes to %s\n"),
1133                          Prog, spw_dbname ());
1134                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", spw_dbname ()));
1135                 fail_exit (E_PW_UPDATE);
1136         }
1137
1138         if (Gflg || lflg) {
1139                 if (gr_close () == 0) {
1140                         fprintf (stderr,
1141                                  _("%s: failure while writing changes to %s\n"),
1142                                  Prog, gr_dbname ());
1143                         SYSLOG ((LOG_ERR, "failure while writing changes to %s", gr_dbname ()));
1144                         fail_exit (E_GRP_UPDATE);
1145                 }
1146 #ifdef SHADOWGRP
1147                 if (is_shadow_grp) {
1148                         if (sgr_close () == 0) {
1149                                 fprintf (stderr,
1150                                          _("%s: failure while writing changes to %s\n"),
1151                                          Prog, sgr_dbname ());
1152                                 SYSLOG ((LOG_ERR, "failure while writing changes to %s", sgr_dbname ()));
1153                                 fail_exit (E_GRP_UPDATE);
1154                         }
1155                         if (sgr_unlock () == 0) {
1156                                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, sgr_dbname ());
1157                                 SYSLOG ((LOG_ERR, "failed to unlock %s", sgr_dbname ()));
1158                                 /* continue */
1159                         }
1160                 }
1161 #endif
1162                 if (gr_unlock () == 0) {
1163                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, gr_dbname ());
1164                         SYSLOG ((LOG_ERR, "failed to unlock %s", gr_dbname ()));
1165                         /* continue */
1166                 }
1167         }
1168
1169         if (is_shadow_pwd) {
1170                 if (spw_unlock () == 0) {
1171                         fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, spw_dbname ());
1172                         SYSLOG ((LOG_ERR, "failed to unlock %s", spw_dbname ()));
1173                         /* continue */
1174                 }
1175         }
1176         if (pw_unlock () == 0) {
1177                 fprintf (stderr, _("%s: failed to unlock %s\n"), Prog, pw_dbname ());
1178                 SYSLOG ((LOG_ERR, "failed to unlock %s", pw_dbname ()));
1179                 /* continue */
1180         }
1181
1182         pw_locked = false;
1183         spw_locked = false;
1184         gr_locked = false;
1185 #ifdef  SHADOWGRP
1186         sgr_locked = false;
1187 #endif
1188
1189         /*
1190          * Close the DBM and/or flat files
1191          */
1192         endpwent ();
1193         endspent ();
1194         endgrent ();
1195 #ifdef  SHADOWGRP
1196         endsgent ();
1197 #endif
1198 }
1199
1200 /*
1201  * open_files - lock and open the password files
1202  *
1203  *      open_files() opens the two password files.
1204  */
1205 static void open_files (void)
1206 {
1207         if (pw_lock () == 0) {
1208                 fprintf (stderr,
1209                          _("%s: cannot lock %s; try again later.\n"),
1210                          Prog, pw_dbname ());
1211                 fail_exit (E_PW_UPDATE);
1212         }
1213         pw_locked = true;
1214         if (pw_open (O_RDWR) == 0) {
1215                 fprintf (stderr,
1216                          _("%s: cannot open %s\n"), Prog, pw_dbname ());
1217                 fail_exit (E_PW_UPDATE);
1218         }
1219         if (is_shadow_pwd && (spw_lock () == 0)) {
1220                 fprintf (stderr,
1221                          _("%s: cannot lock %s; try again later.\n"),
1222                          Prog, spw_dbname ());
1223                 fail_exit (E_PW_UPDATE);
1224         }
1225         spw_locked = true;
1226         if (is_shadow_pwd && (spw_open (O_RDWR) == 0)) {
1227                 fprintf (stderr,
1228                          _("%s: cannot open %s\n"), Prog, spw_dbname ());
1229                 fail_exit (E_PW_UPDATE);
1230         }
1231
1232         if (Gflg || lflg) {
1233                 /*
1234                  * Lock and open the group file. This will load all of the
1235                  * group entries.
1236                  */
1237                 if (gr_lock () == 0) {
1238                         fprintf (stderr,
1239                                  _("%s: cannot lock %s; try again later.\n"),
1240                                  Prog, gr_dbname ());
1241                         fail_exit (E_GRP_UPDATE);
1242                 }
1243                 gr_locked = true;
1244                 if (gr_open (O_RDWR) == 0) {
1245                         fprintf (stderr,
1246                                  _("%s: cannot open %s\n"), Prog, gr_dbname ());
1247                         fail_exit (E_GRP_UPDATE);
1248                 }
1249 #ifdef SHADOWGRP
1250                 if (is_shadow_grp && (sgr_lock () == 0)) {
1251                         fprintf (stderr,
1252                                  _("%s: cannot lock %s; try again later.\n"),
1253                                  Prog, sgr_dbname ());
1254                         fail_exit (E_GRP_UPDATE);
1255                 }
1256                 sgr_locked = true;
1257                 if (is_shadow_grp && (sgr_open (O_RDWR) == 0)) {
1258                         fprintf (stderr,
1259                                  _("%s: cannot open %s\n"), Prog, sgr_dbname ());
1260                         fail_exit (E_GRP_UPDATE);
1261                 }
1262 #endif
1263         }
1264 }
1265
1266 /*
1267  * usr_update - create the user entries
1268  *
1269  *      usr_update() creates the password file entries for this user and
1270  *      will update the group entries if required.
1271  */
1272 static void usr_update (void)
1273 {
1274         struct passwd pwent;
1275         const struct passwd *pwd;
1276
1277         struct spwd spent;
1278         const struct spwd *spwd = NULL;
1279
1280         /*
1281          * Locate the entry in /etc/passwd, which MUST exist.
1282          */
1283         pwd = pw_locate (user_name);
1284         if (NULL == pwd) {
1285                 fprintf (stderr, _("%s: user '%s' does not exist in %s\n"),
1286                          Prog, user_name, pw_dbname ());
1287                 fail_exit (E_NOTFOUND);
1288         }
1289         pwent = *pwd;
1290         new_pwent (&pwent);
1291
1292
1293         /* 
1294          * Locate the entry in /etc/shadow. It doesn't have to exist, and
1295          * won't be created if it doesn't.
1296          */
1297         if (is_shadow_pwd && ((spwd = spw_locate (user_name)) != NULL)) {
1298                 spent = *spwd;
1299                 new_spent (&spent);
1300         }
1301
1302         if (lflg || uflg || gflg || cflg || dflg || sflg || pflg
1303             || Lflg || Uflg) {
1304                 if (pw_update (&pwent) == 0) {
1305                         fprintf (stderr,
1306                                  _("%s: failed to prepare the new %s entry '%s'\n"),
1307                                  Prog, pw_dbname (), pwent.pw_name);
1308                         fail_exit (E_PW_UPDATE);
1309                 }
1310                 if (lflg && (pw_remove (user_name) == 0)) {
1311                         fprintf (stderr,
1312                                  _("%s: cannot remove entry '%s' from %s\n"),
1313                                  Prog, user_name, pw_dbname ());
1314                         fail_exit (E_PW_UPDATE);
1315                 }
1316         }
1317         if ((NULL != spwd) && (lflg || eflg || fflg || pflg || Lflg || Uflg)) {
1318                 if (spw_update (&spent) == 0) {
1319                         fprintf (stderr,
1320                                  _("%s: failed to prepare the new %s entry '%s'\n"),
1321                                  Prog, spw_dbname (), spent.sp_namp);
1322                         fail_exit (E_PW_UPDATE);
1323                 }
1324                 if (lflg && (spw_remove (user_name) == 0)) {
1325                         fprintf (stderr,
1326                                  _("%s: cannot remove entry '%s' from %s\n"),
1327                                  Prog, user_name, spw_dbname ());
1328                         fail_exit (E_PW_UPDATE);
1329                 }
1330         }
1331 }
1332
1333 /*
1334  * move_home - move the user's home directory
1335  *
1336  *      move_home() moves the user's home directory to a new location. The
1337  *      files will be copied if the directory cannot simply be renamed.
1338  */
1339 static void move_home (void)
1340 {
1341         struct stat sb;
1342
1343         if (mflg && (stat (user_home, &sb) == 0)) {
1344                 /*
1345                  * Don't try to move it if it is not a directory
1346                  * (but /dev/null for example).  --marekm
1347                  */
1348                 if (!S_ISDIR (sb.st_mode)) {
1349                         return;
1350                 }
1351
1352                 if (access (user_newhome, F_OK) == 0) {
1353                         fprintf (stderr, _("%s: directory %s exists\n"),
1354                                  Prog, user_newhome);
1355                         fail_exit (E_HOMEDIR);
1356                 } else if (rename (user_home, user_newhome) != 0) {
1357                         if (errno == EXDEV) {
1358                                 if (mkdir (user_newhome, sb.st_mode & 0777) != 0) {
1359                                         fprintf (stderr,
1360                                                  _("%s: can't create %s\n"),
1361                                                  Prog, user_newhome);
1362                                 }
1363                                 if (chown (user_newhome, sb.st_uid, sb.st_gid) != 0) {
1364                                         fprintf (stderr,
1365                                                  _("%s: can't chown %s\n"),
1366                                                  Prog, user_newhome);
1367                                         rmdir (user_newhome);
1368                                         fail_exit (E_HOMEDIR);
1369                                 }
1370                                 if (copy_tree (user_home, user_newhome,
1371                                                uflg ? (long int)user_newid : -1,
1372                                                gflg ? (long int)user_newgid : -1) == 0) {
1373                                         if (remove_tree (user_home) != 0) {
1374                                                 fprintf (stderr,
1375                                                          _
1376                                                          ("%s: warning: failed to completely remove old home directory %s"),
1377                                                          Prog, user_home);
1378                                         }
1379 #ifdef WITH_AUDIT
1380                                         audit_logger (AUDIT_USER_CHAUTHTOK,
1381                                                       Prog,
1382                                                       "moving home directory",
1383                                                       user_newname,
1384                                                       (unsigned int) user_newid,
1385                                                       1);
1386 #endif
1387                                         return;
1388                                 }
1389
1390                                 /* TODO: do some cleanup if the copy
1391                                  *       was started */
1392                                 (void) remove_tree (user_newhome);
1393                         }
1394                         fprintf (stderr,
1395                                  _("%s: cannot rename directory %s to %s\n"),
1396                                  Prog, user_home, user_newhome);
1397                         fail_exit (E_HOMEDIR);
1398                 }
1399 #ifdef WITH_AUDIT
1400                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
1401                               "moving home directory",
1402                               user_newname, (unsigned int) user_newid, 1);
1403 #endif
1404         }
1405         if (uflg || gflg) {
1406 #ifdef WITH_AUDIT
1407                 audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
1408                               "changing home directory owner",
1409                               user_newname, (unsigned int) user_newid, 1);
1410 #endif
1411                 chown (dflg ? user_newhome : user_home,
1412                        uflg ? user_newid : user_id,
1413                        gflg ? user_newgid : user_gid);
1414         }
1415 }
1416
1417 /*
1418  * update_lastlog - update the lastlog file
1419  *
1420  * Relocate the "lastlog" entries for the user. The old entry is
1421  * left alone in case the UID was shared. It doesn't hurt anything
1422  * to just leave it be.
1423  */
1424 static void update_lastlog (void)
1425 {
1426         struct lastlog ll;
1427         int fd;
1428         off_t off_uid = (off_t) user_id * sizeof ll;
1429         off_t off_newuid = (off_t) user_newid * sizeof ll;
1430
1431         if (access (LASTLOG_FILE, F_OK) != 0) {
1432                 return;
1433         }
1434
1435         fd = open (LASTLOG_FILE, O_RDWR);
1436
1437         if (-1 == fd) {
1438                 fprintf (stderr,
1439                          _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
1440                          Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
1441                 return;
1442         }
1443
1444         if (   (lseek (fd, off_uid, SEEK_SET) == off_uid)
1445             && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
1446                 /* Copy the old entry to its new location */
1447                 if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
1448                     || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
1449                     || (close (fd) != 0)) {
1450                         fprintf (stderr,
1451                                  _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
1452                                  Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
1453                 }
1454         } else {
1455                 /* Assume lseek or read failed because there is
1456                  * no entry for the old UID */
1457
1458                 /* Check if the new UID already has an entry */
1459                 if (   (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
1460                     && (read (fd, &ll, sizeof ll) == (ssize_t) sizeof ll)) {
1461                         /* Reset the new uid's lastlog entry */
1462                         memzero (&ll, sizeof (ll));
1463                         if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
1464                             || (write (fd, &ll, sizeof ll) != (ssize_t) sizeof ll)
1465                             || (close (fd) != 0)) {
1466                                 fprintf (stderr,
1467                                          _("%s: failed to copy the lastlog entry of user %lu to user %lu: %s\n"),
1468                                          Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
1469                         }
1470                 } else {
1471                         (void) close (fd);
1472                 }
1473         }
1474 }
1475
1476 /*
1477  * update_faillog - update the faillog file
1478  *
1479  * Relocate the "faillog" entries for the user. The old entry is
1480  * left alone in case the UID was shared. It doesn't hurt anything
1481  * to just leave it be.
1482  */
1483 static void update_faillog (void)
1484 {
1485         struct faillog fl;
1486         int fd;
1487         off_t off_uid = (off_t) user_id * sizeof fl;
1488         off_t off_newuid = (off_t) user_newid * sizeof fl;
1489
1490         if (access (FAILLOG_FILE, F_OK) != 0) {
1491                 return;
1492         }
1493
1494         fd = open (FAILLOG_FILE, O_RDWR);
1495
1496         if (-1 == fd) {
1497                 fprintf (stderr,
1498                          _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
1499                          Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
1500                 return;
1501         }
1502
1503         if (   (lseek (fd, off_uid, SEEK_SET) == off_uid)
1504             && (read (fd, (char *) &fl, sizeof fl) == (ssize_t) sizeof fl)) {
1505                 /* Copy the old entry to its new location */
1506                 if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
1507                     || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
1508                     || (close (fd) != 0)) {
1509                         fprintf (stderr,
1510                                  _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
1511                                  Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
1512                 }
1513         } else {
1514                 /* Assume lseek or read failed because there is
1515                  * no entry for the old UID */
1516
1517                 /* Check if the new UID already has an entry */
1518                 if (   (lseek (fd, off_newuid, SEEK_SET) == off_newuid)
1519                     && (read (fd, &fl, sizeof fl) == (ssize_t) sizeof fl)) {
1520                         /* Reset the new uid's lastlog entry */
1521                         memzero (&fl, sizeof (fl));
1522                         if (   (lseek (fd, off_newuid, SEEK_SET) != off_newuid)
1523                             || (write (fd, &fl, sizeof fl) != (ssize_t) sizeof fl)
1524                             || (close (fd) != 0)) {
1525                                 fprintf (stderr,
1526                                          _("%s: failed to copy the faillog entry of user %lu to user %lu: %s\n"),
1527                                          Prog, (unsigned long) user_id, (unsigned long) user_newid, strerror (errno));
1528                         }
1529                 } else {
1530                         (void) close (fd);
1531                 }
1532         }
1533 }
1534
1535 #ifndef NO_MOVE_MAILBOX
1536 /*
1537  * This is the new and improved code to carefully chown/rename the user's
1538  * mailbox. Maybe I am too paranoid but the mail spool dir sometimes
1539  * happens to be mode 1777 (this makes mail user agents work without
1540  * being setgid mail, but is NOT recommended; they all should be fixed
1541  * to use movemail).  --marekm
1542  */
1543 static void move_mailbox (void)
1544 {
1545         const char *maildir;
1546         char mailfile[1024], newmailfile[1024];
1547         int fd;
1548         struct stat st;
1549
1550         maildir = getdef_str ("MAIL_DIR");
1551 #ifdef MAIL_SPOOL_DIR
1552         if ((NULL == maildir) && (getdef_str ("MAIL_FILE") == NULL)) {
1553                 maildir = MAIL_SPOOL_DIR;
1554         }
1555 #endif
1556         if (NULL == maildir) {
1557                 return;
1558         }
1559
1560         /*
1561          * O_NONBLOCK is to make sure open won't hang on mandatory locks.
1562          * We do fstat/fchown to make sure there are no races (someone
1563          * replacing /var/spool/mail/luser with a hard link to /etc/passwd
1564          * between stat and chown).  --marekm
1565          */
1566         snprintf (mailfile, sizeof mailfile, "%s/%s", maildir, user_name);
1567         fd = open (mailfile, O_RDONLY | O_NONBLOCK, 0);
1568         if (fd < 0) {
1569                 /* no need for warnings if the mailbox doesn't exist */
1570                 if (errno != ENOENT) {
1571                         perror (mailfile);
1572                 }
1573                 return;
1574         }
1575         if (fstat (fd, &st) < 0) {
1576                 perror ("fstat");
1577                 close (fd);
1578                 return;
1579         }
1580         if (st.st_uid != user_id) {
1581                 /* better leave it alone */
1582                 fprintf (stderr, _("%s: warning: %s not owned by %s\n"),
1583                          Prog, mailfile, user_name);
1584                 close (fd);
1585                 return;
1586         }
1587         if (uflg) {
1588                 if (fchown (fd, user_newid, (gid_t) - 1) < 0) {
1589                         perror (_("failed to change mailbox owner"));
1590                 }
1591 #ifdef WITH_AUDIT
1592                 else {
1593                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
1594                                       "changing mail file owner",
1595                                       user_newname, (unsigned int) user_newid, 1);
1596                 }
1597 #endif
1598         }
1599
1600         close (fd);
1601
1602         if (lflg) {
1603                 snprintf (newmailfile, sizeof newmailfile, "%s/%s",
1604                           maildir, user_newname);
1605                 if (   (link (mailfile, newmailfile) != 0)
1606                     || (unlink (mailfile) != 0)) {
1607                         perror (_("failed to rename mailbox"));
1608                 }
1609 #ifdef WITH_AUDIT
1610                 else {
1611                         audit_logger (AUDIT_USER_CHAUTHTOK, Prog,
1612                                       "changing mail file name",
1613                                       user_newname, (unsigned int) user_newid, 1);
1614                 }
1615 #endif
1616         }
1617 }
1618 #endif
1619
1620 /*
1621  * main - usermod command
1622  */
1623 int main (int argc, char **argv)
1624 {
1625 #ifdef ACCT_TOOLS_SETUID
1626 #ifdef USE_PAM
1627         pam_handle_t *pamh = NULL;
1628         int retval;
1629 #endif                          /* USE_PAM */
1630 #endif                          /* ACCT_TOOLS_SETUID */
1631
1632 #ifdef WITH_AUDIT
1633         audit_help_open ();
1634 #endif
1635
1636         /*
1637          * Get my name so that I can use it to report errors.
1638          */
1639         Prog = Basename (argv[0]);
1640
1641         (void) setlocale (LC_ALL, "");
1642         (void) bindtextdomain (PACKAGE, LOCALEDIR);
1643         (void) textdomain (PACKAGE);
1644
1645         sys_ngroups = sysconf (_SC_NGROUPS_MAX);
1646         user_groups = (char **) malloc (sizeof (char *) * (1 + sys_ngroups));
1647         user_groups[0] = (char *) 0;
1648
1649         OPENLOG ("usermod");
1650
1651         is_shadow_pwd = spw_file_present ();
1652 #ifdef SHADOWGRP
1653         is_shadow_grp = sgr_file_present ();
1654 #endif
1655
1656         process_flags (argc, argv);
1657
1658 #ifdef ACCT_TOOLS_SETUID
1659 #ifdef USE_PAM
1660         {
1661                 struct passwd *pampw;
1662                 pampw = getpwuid (getuid ()); /* local, no need for xgetpwuid */
1663                 if (pampw == NULL) {
1664                         fprintf (stderr,
1665                                  _("%s: Cannot determine your user name.\n"),
1666                                  Prog);
1667                         exit (1);
1668                 }
1669
1670                 retval = pam_start ("usermod", pampw->pw_name, &conv, &pamh);
1671         }
1672
1673         if (PAM_SUCCESS == retval) {
1674                 retval = pam_authenticate (pamh, 0);
1675         }
1676
1677         if (PAM_SUCCESS == retval) {
1678                 retval = pam_acct_mgmt (pamh, 0);
1679         }
1680
1681         if (NULL != pamh) {
1682                 (void) pam_end (pamh, retval);
1683         }
1684         if (PAM_SUCCESS != retval) {
1685                 fprintf (stderr, _("%s: PAM authentication failed\n"), Prog);
1686                 exit (1);
1687         }
1688 #endif                          /* USE_PAM */
1689 #endif                          /* ACCT_TOOLS_SETUID */
1690
1691         /*
1692          * Do the hard stuff - open the files, change the user entries,
1693          * change the home directory, then close and update the files.
1694          */
1695         open_files ();
1696         if (   cflg || dflg || eflg || fflg || gflg || Lflg || lflg || pflg
1697             || sflg || uflg || Uflg) {
1698                 usr_update ();
1699         }
1700         if (Gflg || lflg) {
1701                 grp_update ();
1702         }
1703         close_files ();
1704
1705         nscd_flush_cache ("passwd");
1706         nscd_flush_cache ("group");
1707
1708         if (mflg) {
1709                 move_home ();
1710         }
1711
1712 #ifndef NO_MOVE_MAILBOX
1713         if (lflg || uflg) {
1714                 move_mailbox ();
1715         }
1716 #endif
1717
1718         if (uflg) {
1719                 update_lastlog ();
1720                 update_faillog ();
1721
1722                 /*
1723                  * Change the UID on all of the files owned by `user_id' to
1724                  * `user_newid' in the user's home directory.
1725                  */
1726                 chown_tree (dflg ? user_newhome : user_home,
1727                             user_id, user_newid,
1728                             user_gid, gflg ? user_newgid : user_gid);
1729         }
1730
1731         exit (E_SUCCESS);
1732         /* NOT REACHED */
1733 }
1734