]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.sbin/pw/pw.c
Fix regression in pw(8) when creating numeric users or groups.
[FreeBSD/releng/10.2.git] / usr.sbin / pw / pw.c
1 /*-
2  * Copyright (C) 1996
3  *      David L. Nugent.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #ifndef lint
28 static const char rcsid[] =
29   "$FreeBSD$";
30 #endif /* not lint */
31
32 #include <err.h>
33 #include <fcntl.h>
34 #include <locale.h>
35 #include <paths.h>
36 #include <stdbool.h>
37 #include <sys/wait.h>
38 #include "pw.h"
39
40 #if !defined(_PATH_YP)
41 #define _PATH_YP        "/var/yp/"
42 #endif
43 const char     *Modes[] = {
44   "add", "del", "mod", "show", "next",
45   NULL};
46 const char     *Which[] = {"user", "group", NULL};
47 static const char *Combo1[] = {
48   "useradd", "userdel", "usermod", "usershow", "usernext",
49   "lock", "unlock",
50   "groupadd", "groupdel", "groupmod", "groupshow", "groupnext",
51   NULL};
52 static const char *Combo2[] = {
53   "adduser", "deluser", "moduser", "showuser", "nextuser",
54   "lock", "unlock",
55   "addgroup", "delgroup", "modgroup", "showgroup", "nextgroup",
56   NULL};
57
58 struct pwf PWF =
59 {
60         PWF_REGULAR,
61         setpwent,
62         endpwent,
63         getpwent,
64         getpwuid,
65         getpwnam,
66         setgrent,
67         endgrent,
68         getgrent,
69         getgrgid,
70         getgrnam,
71
72 };
73 struct pwf VPWF =
74 {
75         PWF_ALT,
76         vsetpwent,
77         vendpwent,
78         vgetpwent,
79         vgetpwuid,
80         vgetpwnam,
81         vsetgrent,
82         vendgrent,
83         vgetgrent,
84         vgetgrgid,
85         vgetgrnam,
86 };
87
88 struct pwconf conf;
89
90 static struct cargs arglist;
91
92 static int      getindex(const char *words[], const char *word);
93 static void     cmdhelp(int mode, int which);
94
95
96 int
97 main(int argc, char *argv[])
98 {
99         int             ch;
100         int             mode = -1;
101         int             which = -1;
102         long            id = -1;
103         char            *config = NULL;
104         struct stat     st;
105         const char      *errstr;
106         char            arg, *name;
107         bool            relocated, nis;
108
109         static const char *opts[W_NUM][M_NUM] =
110         {
111                 { /* user */
112                         "R:V:C:qn:u:c:d:e:p:g:G:mM:k:s:oL:i:w:h:H:Db:NPy:Y",
113                         "R:V:C:qn:u:rY",
114                         "R:V:C:qn:u:c:d:e:p:g:G:mM:l:k:s:w:L:h:H:FNPY",
115                         "R:V:C:qn:u:FPa7",
116                         "R:V:C:q",
117                         "R:V:C:q",
118                         "R:V:C:q"
119                 },
120                 { /* grp  */
121                         "R:V:C:qn:g:h:H:M:opNPY",
122                         "R:V:C:qn:g:Y",
123                         "R:V:C:qn:d:g:l:h:H:FM:m:NPY",
124                         "R:V:C:qn:g:FPa",
125                         "R:V:C:q"
126                  }
127         };
128
129         static int      (*funcs[W_NUM]) (int _mode, char *_name, long _id,
130             struct cargs * _args) =
131         {                       /* Request handlers */
132                 pw_user,
133                 pw_group
134         };
135
136         name = NULL;
137         relocated = nis = false;
138         memset(&conf, 0, sizeof(conf));
139         strlcpy(conf.etcpath, _PATH_PWD, sizeof(conf.etcpath));
140         conf.checkduplicate = true;
141
142         LIST_INIT(&arglist);
143
144         (void)setlocale(LC_ALL, "");
145
146         /*
147          * Break off the first couple of words to determine what exactly
148          * we're being asked to do
149          */
150         while (argc > 1) {
151                 int             tmp;
152
153                 if (*argv[1] == '-') {
154                         /*
155                          * Special case, allow pw -V<dir> <operation> [args] for scripts etc.
156                          */
157                         arg = argv[1][1];
158                         if (arg == 'V' || arg == 'R') {
159                                 if (relocated)
160                                         errx(EXIT_FAILURE, "Both '-R' and '-V' "
161                                             "specified, only one accepted");
162                                 relocated = true;
163                                 optarg = &argv[1][2];
164                                 if (*optarg == '\0') {
165                                         if (stat(argv[2], &st) != 0)
166                                                 errx(EX_OSFILE, \
167                                                     "no such directory `%s'",
168                                                     argv[2]);
169                                         if (!S_ISDIR(st.st_mode))
170                                                 errx(EX_OSFILE, "`%s' not a "
171                                                     "directory", argv[2]);
172                                         optarg = argv[2];
173                                         ++argv;
174                                         --argc;
175                                 }
176                                 memcpy(&PWF, &VPWF, sizeof PWF);
177                                 if (arg == 'R') {
178                                         strlcpy(conf.rootdir, optarg,
179                                             sizeof(conf.rootdir));
180                                         PWF._altdir = PWF_ROOTDIR;
181                                 }
182                                 snprintf(conf.etcpath, sizeof(conf.etcpath),
183                                     "%s%s", optarg, arg == 'R' ? "/etc" : "");
184                         } else
185                                 break;
186                 }
187                 else if (mode == -1 && (tmp = getindex(Modes, argv[1])) != -1)
188                         mode = tmp;
189                 else if (which == -1 && (tmp = getindex(Which, argv[1])) != -1)
190                         which = tmp;
191                 else if ((mode == -1 && which == -1) &&
192                          ((tmp = getindex(Combo1, argv[1])) != -1 ||
193                           (tmp = getindex(Combo2, argv[1])) != -1)) {
194                         which = tmp / M_NUM;
195                         mode = tmp % M_NUM;
196                 } else if (strcmp(argv[1], "help") == 0 && argv[2] == NULL)
197                         cmdhelp(mode, which);
198                 else if (which != -1 && mode != -1) {
199                         if (strspn(argv[1], "0123456789") == strlen(argv[1])) {
200                                 id = strtonum(argv[1], 0, LONG_MAX, &errstr);
201                                 if (errstr != NULL)
202                                         errx(EX_USAGE, "Bad id '%s': %s",
203                                             argv[1], errstr);
204                         } else
205                                 name = argv[1];
206                 } else
207                         errx(EX_USAGE, "unknown keyword `%s'", argv[1]);
208                 ++argv;
209                 --argc;
210         }
211
212         /*
213          * Bail out unless the user is specific!
214          */
215         if (mode == -1 || which == -1)
216                 cmdhelp(mode, which);
217
218         /*
219          * We know which mode we're in and what we're about to do, so now
220          * let's dispatch the remaining command line args in a genric way.
221          */
222         optarg = NULL;
223
224         while ((ch = getopt(argc, argv, opts[which][mode])) != -1) {
225                 switch (ch) {
226                 case '?':
227                         errx(EX_USAGE, "unknown switch");
228                         break;
229                 case '7':
230                         conf.v7 = true;
231                         break;
232                 case 'C':
233                         conf.config = optarg;
234                         config = conf.config;
235                         break;
236                 case 'N':
237                         conf.dryrun = true;
238                         break;
239                 case 'l':
240                         if (strlen(optarg) >= MAXLOGNAME)
241                                 errx(EX_USAGE, "new name too long: %s", optarg);
242                         conf.newname = optarg;
243                         break;
244                 case 'P':
245                         conf.pretty = true;
246                         break;
247                 case 'Y':
248                         nis = true;
249                         break;
250                 case 'g':
251                         if (which == 0) { /* for user* */
252                                 addarg(&arglist, 'g', optarg);
253                                 break;
254                         }
255                         if (strspn(optarg, "0123456789") != strlen(optarg))
256                                 errx(EX_USAGE, "-g expects a number");
257                         id = strtonum(optarg, 0, LONG_MAX, &errstr);
258                         if (errstr != NULL)
259                                 errx(EX_USAGE, "Bad id '%s': %s", optarg,
260                                     errstr);
261                         break;
262                 case 'u':
263                         if (strspn(optarg, "0123456789,") != strlen(optarg))
264                                 errx(EX_USAGE, "-u expects a number");
265                         if (strchr(optarg, ',') != NULL) {
266                                 addarg(&arglist, 'u', optarg);
267                                 break;
268                         }
269                         id = strtonum(optarg, 0, LONG_MAX, &errstr);
270                         if (errstr != NULL)
271                                 errx(EX_USAGE, "Bad id '%s': %s", optarg,
272                                     errstr);
273                         break;
274                 case 'n':
275                         name = optarg;
276                         break;
277                 case 'o':
278                         conf.checkduplicate = false;
279                         break;
280                 default:
281                         addarg(&arglist, ch, optarg);
282                         break;
283                 }
284                 optarg = NULL;
285         }
286
287         if (name != NULL && strlen(name) >= MAXLOGNAME)
288                 errx(EX_USAGE, "name too long: %s", name);
289
290         /*
291          * Must be root to attempt an update
292          */
293         if (geteuid() != 0 && mode != M_PRINT && mode != M_NEXT && !conf.dryrun)
294                 errx(EX_NOPERM, "you must be root to run this program");
295
296         /*
297          * We should immediately look for the -q 'quiet' switch so that we
298          * don't bother with extraneous errors
299          */
300         if (getarg(&arglist, 'q') != NULL)
301                 freopen(_PATH_DEVNULL, "w", stderr);
302
303         /*
304          * Set our base working path if not overridden
305          */
306
307         if (config == NULL) {   /* Only override config location if -C not specified */
308                 asprintf(&config, "%s/pw.conf", conf.etcpath);
309                 if (config == NULL)
310                         errx(EX_OSERR, "out of memory");
311         }
312
313         /*
314          * Now, let's do the common initialisation
315          */
316         conf.userconf = read_userconfig(config);
317
318         ch = funcs[which] (mode, name, id, &arglist);
319
320         /*
321          * If everything went ok, and we've been asked to update
322          * the NIS maps, then do it now
323          */
324         if (ch == EXIT_SUCCESS && nis) {
325                 pid_t   pid;
326
327                 fflush(NULL);
328                 if (chdir(_PATH_YP) == -1)
329                         warn("chdir(" _PATH_YP ")");
330                 else if ((pid = fork()) == -1)
331                         warn("fork()");
332                 else if (pid == 0) {
333                         /* Is make anywhere else? */
334                         execlp("/usr/bin/make", "make", (char *)NULL);
335                         _exit(1);
336                 } else {
337                         int   i;
338                         waitpid(pid, &i, 0);
339                         if ((i = WEXITSTATUS(i)) != 0)
340                                 errx(ch, "make exited with status %d", i);
341                         else
342                                 pw_log(conf.userconf, mode, which, "NIS maps updated");
343                 }
344         }
345         return ch;
346 }
347
348
349 static int
350 getindex(const char *words[], const char *word)
351 {
352         int             i = 0;
353
354         while (words[i]) {
355                 if (strcmp(words[i], word) == 0)
356                         return i;
357                 i++;
358         }
359         return -1;
360 }
361
362
363 /*
364  * This is probably an overkill for a cmdline help system, but it reflects
365  * the complexity of the command line.
366  */
367
368 static void
369 cmdhelp(int mode, int which)
370 {
371         if (which == -1)
372                 fprintf(stderr, "usage:\n  pw [user|group|lock|unlock] [add|del|mod|show|next] [help|switches/values]\n");
373         else if (mode == -1)
374                 fprintf(stderr, "usage:\n  pw %s [add|del|mod|show|next] [help|switches/values]\n", Which[which]);
375         else {
376
377                 /*
378                  * We need to give mode specific help
379                  */
380                 static const char *help[W_NUM][M_NUM] =
381                 {
382                         {
383                                 "usage: pw useradd [name] [switches]\n"
384                                 "\t-V etcdir      alternate /etc location\n"
385                                 "\t-R rootir      alternate root directory\n"
386                                 "\t-C config      configuration file\n"
387                                 "\t-q             quiet operation\n"
388                                 "  Adding users:\n"
389                                 "\t-n name        login name\n"
390                                 "\t-u uid         user id\n"
391                                 "\t-c comment     user name/comment\n"
392                                 "\t-d directory   home directory\n"
393                                 "\t-e date        account expiry date\n"
394                                 "\t-p date        password expiry date\n"
395                                 "\t-g grp         initial group\n"
396                                 "\t-G grp1,grp2   additional groups\n"
397                                 "\t-m [ -k dir ]  create and set up home\n"
398                                 "\t-M mode        home directory permissions\n"
399                                 "\t-s shell       name of login shell\n"
400                                 "\t-o             duplicate uid ok\n"
401                                 "\t-L class       user class\n"
402                                 "\t-h fd          read password on fd\n"
403                                 "\t-H fd          read encrypted password on fd\n"
404                                 "\t-Y             update NIS maps\n"
405                                 "\t-N             no update\n"
406                                 "  Setting defaults:\n"
407                                 "\t-V etcdir      alternate /etc location\n"
408                                 "\t-R rootir      alternate root directory\n"
409                                 "\t-D             set user defaults\n"
410                                 "\t-b dir         default home root dir\n"
411                                 "\t-e period      default expiry period\n"
412                                 "\t-p period      default password change period\n"
413                                 "\t-g group       default group\n"
414                                 "\t-G grp1,grp2   additional groups\n"
415                                 "\t-L class       default user class\n"
416                                 "\t-k dir         default home skeleton\n"
417                                 "\t-M mode        home directory permissions\n"
418                                 "\t-u min,max     set min,max uids\n"
419                                 "\t-i min,max     set min,max gids\n"
420                                 "\t-w method      set default password method\n"
421                                 "\t-s shell       default shell\n"
422                                 "\t-y path        set NIS passwd file path\n",
423                                 "usage: pw userdel [uid|name] [switches]\n"
424                                 "\t-V etcdir      alternate /etc location\n"
425                                 "\t-R rootir      alternate root directory\n"
426                                 "\t-n name        login name\n"
427                                 "\t-u uid         user id\n"
428                                 "\t-Y             update NIS maps\n"
429                                 "\t-r             remove home & contents\n",
430                                 "usage: pw usermod [uid|name] [switches]\n"
431                                 "\t-V etcdir      alternate /etc location\n"
432                                 "\t-R rootir      alternate root directory\n"
433                                 "\t-C config      configuration file\n"
434                                 "\t-q             quiet operation\n"
435                                 "\t-F             force add if no user\n"
436                                 "\t-n name        login name\n"
437                                 "\t-u uid         user id\n"
438                                 "\t-c comment     user name/comment\n"
439                                 "\t-d directory   home directory\n"
440                                 "\t-e date        account expiry date\n"
441                                 "\t-p date        password expiry date\n"
442                                 "\t-g grp         initial group\n"
443                                 "\t-G grp1,grp2   additional groups\n"
444                                 "\t-l name        new login name\n"
445                                 "\t-L class       user class\n"
446                                 "\t-m [ -k dir ]  create and set up home\n"
447                                 "\t-M mode        home directory permissions\n"
448                                 "\t-s shell       name of login shell\n"
449                                 "\t-w method      set new password using method\n"
450                                 "\t-h fd          read password on fd\n"
451                                 "\t-H fd          read encrypted password on fd\n"
452                                 "\t-Y             update NIS maps\n"
453                                 "\t-N             no update\n",
454                                 "usage: pw usershow [uid|name] [switches]\n"
455                                 "\t-V etcdir      alternate /etc location\n"
456                                 "\t-R rootir      alternate root directory\n"
457                                 "\t-n name        login name\n"
458                                 "\t-u uid         user id\n"
459                                 "\t-F             force print\n"
460                                 "\t-P             prettier format\n"
461                                 "\t-a             print all users\n"
462                                 "\t-7             print in v7 format\n",
463                                 "usage: pw usernext [switches]\n"
464                                 "\t-V etcdir      alternate /etc location\n"
465                                 "\t-R rootir      alternate root directory\n"
466                                 "\t-C config      configuration file\n"
467                                 "\t-q             quiet operation\n",
468                                 "usage pw: lock [switches]\n"
469                                 "\t-V etcdir      alternate /etc locations\n"
470                                 "\t-C config      configuration file\n"
471                                 "\t-q             quiet operation\n",
472                                 "usage pw: unlock [switches]\n"
473                                 "\t-V etcdir      alternate /etc locations\n"
474                                 "\t-C config      configuration file\n"
475                                 "\t-q             quiet operation\n"
476                         },
477                         {
478                                 "usage: pw groupadd [group|gid] [switches]\n"
479                                 "\t-V etcdir      alternate /etc location\n"
480                                 "\t-R rootir      alternate root directory\n"
481                                 "\t-C config      configuration file\n"
482                                 "\t-q             quiet operation\n"
483                                 "\t-n group       group name\n"
484                                 "\t-g gid         group id\n"
485                                 "\t-M usr1,usr2   add users as group members\n"
486                                 "\t-o             duplicate gid ok\n"
487                                 "\t-Y             update NIS maps\n"
488                                 "\t-N             no update\n",
489                                 "usage: pw groupdel [group|gid] [switches]\n"
490                                 "\t-V etcdir      alternate /etc location\n"
491                                 "\t-R rootir      alternate root directory\n"
492                                 "\t-n name        group name\n"
493                                 "\t-g gid         group id\n"
494                                 "\t-Y             update NIS maps\n",
495                                 "usage: pw groupmod [group|gid] [switches]\n"
496                                 "\t-V etcdir      alternate /etc location\n"
497                                 "\t-R rootir      alternate root directory\n"
498                                 "\t-C config      configuration file\n"
499                                 "\t-q             quiet operation\n"
500                                 "\t-F             force add if not exists\n"
501                                 "\t-n name        group name\n"
502                                 "\t-g gid         group id\n"
503                                 "\t-M usr1,usr2   replaces users as group members\n"
504                                 "\t-m usr1,usr2   add users as group members\n"
505                                 "\t-d usr1,usr2   delete users as group members\n"
506                                 "\t-l name        new group name\n"
507                                 "\t-Y             update NIS maps\n"
508                                 "\t-N             no update\n",
509                                 "usage: pw groupshow [group|gid] [switches]\n"
510                                 "\t-V etcdir      alternate /etc location\n"
511                                 "\t-R rootir      alternate root directory\n"
512                                 "\t-n name        group name\n"
513                                 "\t-g gid         group id\n"
514                                 "\t-F             force print\n"
515                                 "\t-P             prettier format\n"
516                                 "\t-a             print all accounting groups\n",
517                                 "usage: pw groupnext [switches]\n"
518                                 "\t-V etcdir      alternate /etc location\n"
519                                 "\t-R rootir      alternate root directory\n"
520                                 "\t-C config      configuration file\n"
521                                 "\t-q             quiet operation\n"
522                         }
523                 };
524
525                 fprintf(stderr, "%s", help[which][mode]);
526         }
527         exit(EXIT_FAILURE);
528 }
529
530 struct carg    *
531 getarg(struct cargs * _args, int ch)
532 {
533         struct carg    *c = LIST_FIRST(_args);
534
535         while (c != NULL && c->ch != ch)
536                 c = LIST_NEXT(c, list);
537         return c;
538 }
539
540 struct carg    *
541 addarg(struct cargs * _args, int ch, char *argstr)
542 {
543         struct carg    *ca = malloc(sizeof(struct carg));
544
545         if (ca == NULL)
546                 errx(EX_OSERR, "out of memory");
547         ca->ch = ch;
548         ca->val = argstr;
549         LIST_INSERT_HEAD(_args, ca, list);
550         return ca;
551 }