1 /* $OpenBSD: user.c,v 1.131 2023/05/18 18:29:28 millert Exp $ */
2 /* $NetBSD: user.c,v 1.69 2003/04/14 17:40:07 agc Exp $ */
3
4 /*
5 * Copyright (c) 1999 Alistair G. Crooks. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote
16 * products derived from this software without specific prior written
17 * permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
20 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
25 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
28 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/wait.h>
35
36 #include <ctype.h>
37 #include <dirent.h>
38 #include <err.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <grp.h>
42 #include <limits.h>
43 #include <login_cap.h>
44 #include <paths.h>
45 #include <pwd.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <syslog.h>
50 #include <time.h>
51 #include <unistd.h>
52 #include <util.h>
53
54 #include "usermgmt.h"
55
56
57 /* this struct describes a uid range */
58 typedef struct range_t {
59 uid_t r_from; /* low uid */
60 uid_t r_to; /* high uid */
61 } range_t;
62
63 /* this struct encapsulates the user information */
64 typedef struct user_t {
65 int u_flags; /* see below */
66 uid_t u_uid; /* uid of user */
67 char *u_password; /* encrypted password */
68 char *u_comment; /* comment field */
69 char *u_home; /* home directory */
70 char *u_primgrp; /* primary group */
71 int u_groupc; /* # of secondary groups */
72 const char *u_groupv[NGROUPS_MAX]; /* secondary groups */
73 char *u_shell; /* user's shell */
74 char *u_basedir; /* base directory for home */
75 char *u_expire; /* when account will expire */
76 char *u_inactive; /* when password will expire */
77 char *u_skeldir; /* directory for startup files */
78 char *u_class; /* login class */
79 unsigned int u_rsize; /* size of range array */
80 unsigned int u_rc; /* # of ranges */
81 range_t *u_rv; /* the ranges */
82 unsigned int u_defrc; /* # of ranges in defaults */
83 int u_preserve; /* preserve uids on deletion */
84 } user_t;
85
86 /* flags for which fields of the user_t replace the passwd entry */
87 enum {
88 F_COMMENT = 0x0001,
89 F_DUPUID = 0x0002,
90 F_EXPIRE = 0x0004,
91 F_GROUP = 0x0008,
92 F_HOMEDIR = 0x0010,
93 F_MKDIR = 0x0020,
94 F_INACTIVE = 0x0040,
95 F_PASSWORD = 0x0080,
96 F_SECGROUP = 0x0100,
97 F_SHELL = 0x0200,
98 F_UID = 0x0400,
99 F_USERNAME = 0x0800,
100 F_CLASS = 0x1000,
101 F_SETSECGROUP = 0x4000,
102 F_ACCTLOCK = 0x8000,
103 F_ACCTUNLOCK = 0x10000
104 };
105
106 /* flags for runas() */
107 enum {
108 RUNAS_DUP_DEVNULL = 0x01,
109 RUNAS_IGNORE_EXITVAL = 0x02
110 };
111
112 #define CONFFILE "/etc/usermgmt.conf"
113 #define _PATH_NONEXISTENT "/nonexistent"
114
115 #ifndef DEF_GROUP
116 #define DEF_GROUP "=uid"
117 #endif
118
119 #ifndef DEF_BASEDIR
120 #define DEF_BASEDIR "/home"
121 #endif
122
123 #ifndef DEF_SKELDIR
124 #define DEF_SKELDIR "/etc/skel"
125 #endif
126
127 #ifndef DEF_SHELL
128 #define DEF_SHELL _PATH_KSHELL
129 #endif
130
131 #ifndef DEF_COMMENT
132 #define DEF_COMMENT ""
133 #endif
134
135 #ifndef DEF_LOWUID
136 #define DEF_LOWUID 1000
137 #endif
138
139 #ifndef DEF_HIGHUID
140 #define DEF_HIGHUID 60000
141 #endif
142
143 #ifndef DEF_INACTIVE
144 #define DEF_INACTIVE 0
145 #endif
146
147 #ifndef DEF_EXPIRE
148 #define DEF_EXPIRE NULL
149 #endif
150
151 #ifndef DEF_CLASS
152 #define DEF_CLASS ""
153 #endif
154
155 #ifndef WAITSECS
156 #define WAITSECS 10
157 #endif
158
159 #ifndef NOBODY_UID
160 #define NOBODY_UID 32767
161 #endif
162
163 /* some useful constants */
164 enum {
165 MaxShellNameLen = 256,
166 MaxFileNameLen = PATH_MAX,
167 MaxUserNameLen = _PW_NAME_LEN,
168 MaxCommandLen = 2048,
169 PasswordLength = _PASSWORD_LEN,
170 LowGid = DEF_LOWUID,
171 HighGid = DEF_HIGHUID
172 };
173
174 /* Full paths of programs used here */
175 #define CHMOD "/bin/chmod"
176 #define CHOWN "/sbin/chown"
177 #define MKDIR "/bin/mkdir"
178 #define MV "/bin/mv"
179 #define NOLOGIN "/sbin/nologin"
180 #define CP "/bin/cp"
181 #define RM "/bin/rm"
182
183 #define UNSET_INACTIVE "Null (unset)"
184 #define UNSET_EXPIRY "Null (unset)"
185
186 static int adduser(char *, user_t *);
187 static int append_group(char *, int, const char **);
188 static int copydotfiles(char *, char *);
189 static int creategid(char *, gid_t, const char *);
190 static int getnextgid(uid_t *, uid_t, uid_t);
191 static int getnextuid(int, uid_t *, uid_t, uid_t);
192 static int is_local(char *, const char *);
193 static int modify_gid(char *, char *);
194 static int moduser(char *, char *, user_t *);
195 static int removehomedir(const char *, uid_t, const char *);
196 static int rm_user_from_groups(char *);
197 static int save_range(user_t *, char *);
198 static int scantime(time_t *, char *);
199 static int setdefaults(user_t *);
200 static int valid_class(char *);
201 static int valid_group(char *);
202 static int valid_login(char *);
203 static size_t expand_len(const char *, const char *);
204 static struct group *find_group_info(const char *);
205 static struct passwd *find_user_info(const char *);
206 static void checkeuid(void);
207 static void strsave(char **, const char *);
208 static void read_defaults(user_t *);
209
210 static int verbose;
211
212 /* free *cpp, then store a copy of `s' in it */
213 static void
strsave(char ** cpp,const char * s)214 strsave(char **cpp, const char *s)
215 {
216 free(*cpp);
217 if ((*cpp = strdup(s)) == NULL)
218 err(1, NULL);
219 }
220
221 /* run the given command with optional arguments as the specified uid */
222 static int
runas(const char * path,const char * const argv[],uid_t uid,int flags)223 runas(const char *path, const char *const argv[], uid_t uid, int flags)
224 {
225 int argc, status, ret = 0;
226 char buf[MaxCommandLen];
227 pid_t child;
228
229 strlcpy(buf, path, sizeof(buf));
230 for (argc = 1; argv[argc] != NULL; argc++) {
231 strlcat(buf, " ", sizeof(buf));
232 strlcat(buf, argv[argc], sizeof(buf));
233 }
234 if (verbose)
235 printf("Command: %s\n", buf);
236
237 child = fork();
238 switch (child) {
239 case -1:
240 err(EXIT_FAILURE, "fork");
241 case 0:
242 if (flags & RUNAS_DUP_DEVNULL) {
243 /* Redirect output to /dev/null if possible. */
244 int dev_null = open(_PATH_DEVNULL, O_RDWR);
245 if (dev_null != -1) {
246 dup2(dev_null, STDOUT_FILENO);
247 dup2(dev_null, STDERR_FILENO);
248 if (dev_null > STDERR_FILENO)
249 close(dev_null);
250 } else {
251 warn("%s", _PATH_DEVNULL);
252 }
253 }
254 if (uid != -1) {
255 if (setresuid(uid, uid, uid) == -1)
256 warn("setresuid(%u, %u, %u)", uid, uid, uid);
257 }
258 execv(path, (char **)argv);
259 warn("%s", buf);
260 _exit(EXIT_FAILURE);
261 default:
262 while (waitpid(child, &status, 0) == -1) {
263 if (errno != EINTR)
264 err(EXIT_FAILURE, "waitpid");
265 }
266 if (WIFSIGNALED(status)) {
267 ret = WTERMSIG(status);
268 warnx("[Warning] `%s' killed by signal %d", buf, ret);
269 ret |= 128;
270 } else {
271 if (!(flags & RUNAS_IGNORE_EXITVAL))
272 ret = WEXITSTATUS(status);
273 if (ret != 0) {
274 warnx("[Warning] `%s' failed with status %d",
275 buf, ret);
276 }
277 }
278 return ret;
279 }
280 }
281
282 /* run the given command with optional arguments */
283 static int
run(const char * path,const char * const argv[])284 run(const char *path, const char *const argv[])
285 {
286 return runas(path, argv, -1, 0);
287 }
288
289 /* remove a users home directory, returning 1 for success (ie, no problems encountered) */
290 static int
removehomedir(const char * user,uid_t uid,const char * dir)291 removehomedir(const char *user, uid_t uid, const char *dir)
292 {
293 const char *rm_argv[] = { "rm", "-rf", dir, NULL };
294 struct stat st;
295
296 /* userid not root? */
297 if (uid == 0) {
298 warnx("Not deleting home directory `%s'; userid is 0", dir);
299 return 0;
300 }
301
302 /* directory exists (and is a directory!) */
303 if (stat(dir, &st) == -1) {
304 warnx("Home directory `%s' doesn't exist", dir);
305 return 0;
306 }
307 if (!S_ISDIR(st.st_mode)) {
308 warnx("Home directory `%s' is not a directory", dir);
309 return 0;
310 }
311
312 /* userid matches directory owner? */
313 if (st.st_uid != uid) {
314 warnx("User `%s' doesn't own directory `%s', not removed",
315 user, dir);
316 return 0;
317 }
318
319 /* run "rm -rf dir 2>&1/dev/null" as user, not root */
320 (void) runas(RM, rm_argv, uid, RUNAS_DUP_DEVNULL|RUNAS_IGNORE_EXITVAL);
321 if (rmdir(dir) == -1 && errno != ENOENT) {
322 warnx("Unable to remove all files in `%s'", dir);
323 return 0;
324 }
325 return 1;
326 }
327
328 /*
329 * check that the effective uid is 0 - called from funcs which will
330 * modify data and config files.
331 */
332 static void
checkeuid(void)333 checkeuid(void)
334 {
335 if (geteuid() != 0) {
336 errx(EXIT_FAILURE, "Program must be run as root");
337 }
338 }
339
340 /* copy any dot files into the user's home directory */
341 static int
copydotfiles(char * skeldir,char * dst)342 copydotfiles(char *skeldir, char *dst)
343 {
344 char src[MaxFileNameLen];
345 struct dirent *dp;
346 DIR *dirp;
347 int len, n;
348
349 if (*skeldir == '\0')
350 return 0;
351 if ((dirp = opendir(skeldir)) == NULL) {
352 warn("can't open source . files dir `%s'", skeldir);
353 return 0;
354 }
355 for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) {
356 if (strcmp(dp->d_name, ".") == 0 ||
357 strcmp(dp->d_name, "..") == 0) {
358 continue;
359 }
360 n = 1;
361 }
362 (void) closedir(dirp);
363 if (n == 0) {
364 warnx("No \"dot\" initialisation files found");
365 } else {
366 len = snprintf(src, sizeof(src), "%s/.", skeldir);
367 if (len < 0 || len >= sizeof(src)) {
368 warnx("skeleton directory `%s' too long", skeldir);
369 n = 0;
370 } else {
371 const char *cp_argv[] = { "cp", "-a", src, dst, NULL };
372 if (verbose)
373 cp_argv[1] = "-av";
374 run(CP, cp_argv);
375 }
376 }
377 return n;
378 }
379
380 /* returns 1 if the specified gid exists in the group file, else 0 */
381 static int
gid_exists(gid_t gid)382 gid_exists(gid_t gid)
383 {
384 return group_from_gid(gid, 1) != NULL;
385 }
386
387 /* return 1 if the specified group exists in the group file, else 0 */
388 static int
group_exists(const char * group)389 group_exists(const char *group)
390 {
391 gid_t gid;
392
393 return gid_from_group(group, &gid) != -1;
394 }
395
396 /* create a group entry with gid `gid' */
397 static int
creategid(char * group,gid_t gid,const char * name)398 creategid(char *group, gid_t gid, const char *name)
399 {
400 struct stat st;
401 FILE *from;
402 FILE *to;
403 char *buf;
404 char f[MaxFileNameLen];
405 int fd, ret;
406 int wroteit = 0;
407 size_t len;
408
409 if (group_exists(group)) {
410 warnx("group `%s' already exists", group);
411 return 0;
412 }
413 if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
414 warn("can't create gid for `%s': can't open `%s'", group,
415 _PATH_GROUP);
416 return 0;
417 }
418 if (flock(fileno(from), LOCK_EX | LOCK_NB) == -1) {
419 warn("can't lock `%s'", _PATH_GROUP);
420 }
421 (void) fstat(fileno(from), &st);
422 (void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
423 if ((fd = mkstemp(f)) == -1) {
424 warn("can't create gid: mkstemp failed");
425 fclose(from);
426 return 0;
427 }
428 if ((to = fdopen(fd, "w")) == NULL) {
429 warn("can't create gid: fdopen `%s' failed", f);
430 fclose(from);
431 close(fd);
432 unlink(f);
433 return 0;
434 }
435 while ((buf = fgetln(from, &len)) != NULL && len > 0) {
436 ret = 0;
437 if (buf[0] == '+' && wroteit == 0) {
438 ret = fprintf(to, "%s:*:%u:%s\n", group, gid, name);
439 wroteit = 1;
440 }
441 if (ret == -1 ||
442 fprintf(to, "%*.*s", (int)len, (int)len, buf) != len) {
443 warn("can't create gid: short write to `%s'", f);
444 fclose(from);
445 fclose(to);
446 unlink(f);
447 return 0;
448 }
449 }
450 ret = 0;
451 if (wroteit == 0)
452 ret = fprintf(to, "%s:*:%u:%s\n", group, gid, name);
453 fclose(from);
454 if (fclose(to) == EOF || ret == -1) {
455 warn("can't create gid: short write to `%s'", f);
456 unlink(f);
457 return 0;
458 }
459 if (rename(f, _PATH_GROUP) == -1) {
460 warn("can't create gid: can't rename `%s' to `%s'", f,
461 _PATH_GROUP);
462 unlink(f);
463 return 0;
464 }
465 (void) chmod(_PATH_GROUP, st.st_mode & 0777);
466 syslog(LOG_INFO, "new group added: name=%s, gid=%u", group, gid);
467 return 1;
468 }
469
470 /* modify the group entry with name `group' to be newent */
471 static int
modify_gid(char * group,char * newent)472 modify_gid(char *group, char *newent)
473 {
474 struct stat st;
475 FILE *from;
476 FILE *to;
477 char buf[LINE_MAX];
478 char f[MaxFileNameLen];
479 char *colon;
480 int groupc;
481 int entc;
482 int fd;
483 int cc;
484
485 if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
486 warn("can't modify gid for `%s': can't open `%s'", group,
487 _PATH_GROUP);
488 return 0;
489 }
490 if (flock(fileno(from), LOCK_EX | LOCK_NB) == -1) {
491 warn("can't lock `%s'", _PATH_GROUP);
492 }
493 (void) fstat(fileno(from), &st);
494 (void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
495 if ((fd = mkstemp(f)) == -1) {
496 warn("can't modify gid: mkstemp failed");
497 fclose(from);
498 return 0;
499 }
500 if ((to = fdopen(fd, "w")) == NULL) {
501 warn("can't modify gid: fdopen `%s' failed", f);
502 fclose(from);
503 close(fd);
504 unlink(f);
505 return 0;
506 }
507 groupc = strlen(group);
508 while (fgets(buf, sizeof(buf), from) != NULL) {
509 cc = strlen(buf);
510 if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
511 while (fgetc(from) != '\n' && !feof(from))
512 cc++;
513 warnx("%s: line `%s' too long (%d bytes), skipping",
514 _PATH_GROUP, buf, cc);
515 continue;
516 }
517 if ((colon = strchr(buf, ':')) == NULL) {
518 /*
519 * The only valid entry with no column is the all-YP
520 * line.
521 */
522 if (strcmp(buf, "+\n") != 0) {
523 warnx("badly formed entry `%.*s'", cc - 1, buf);
524 continue;
525 }
526 } else {
527 entc = (int)(colon - buf);
528 if (entc == groupc && strncmp(group, buf, entc) == 0) {
529 if (newent == NULL) {
530 continue;
531 } else {
532 cc = strlcpy(buf, newent, sizeof(buf));
533 if (cc >= sizeof(buf)) {
534 warnx("group `%s' entry too long",
535 newent);
536 fclose(from);
537 fclose(to);
538 unlink(f);
539 return (0);
540 }
541 }
542 }
543 }
544 if (fwrite(buf, cc, 1, to) != 1) {
545 warn("can't modify gid: short write to `%s'", f);
546 fclose(from);
547 fclose(to);
548 unlink(f);
549 return 0;
550 }
551 }
552 fclose(from);
553 if (fclose(to) == EOF) {
554 warn("can't modify gid: short write to `%s'", f);
555 unlink(f);
556 return 0;
557 }
558 if (rename(f, _PATH_GROUP) == -1) {
559 warn("can't modify gid: can't rename `%s' to `%s'", f, _PATH_GROUP);
560 unlink(f);
561 return 0;
562 }
563 (void) chmod(_PATH_GROUP, st.st_mode & 0777);
564 if (newent == NULL) {
565 syslog(LOG_INFO, "group deleted: name=%s", group);
566 } else {
567 syslog(LOG_INFO, "group information modified: name=%s", group);
568 }
569 return 1;
570 }
571
572 /* modify the group entries for all `groups', by adding `user' */
573 static int
append_group(char * user,int ngroups,const char ** groups)574 append_group(char *user, int ngroups, const char **groups)
575 {
576 struct group *grp;
577 struct passwd *pwp;
578 struct stat st;
579 FILE *from;
580 FILE *to;
581 char buf[LINE_MAX];
582 char f[MaxFileNameLen];
583 char *colon;
584 const char *ugid = NULL;
585 int fd;
586 int cc;
587 int i;
588 int j;
589
590 if ((pwp = getpwnam(user))) {
591 if ((ugid = group_from_gid(pwp->pw_gid, 1)) == NULL) {
592 warnx("can't get primary group for user `%s'", user);
593 return 0;
594 }
595 }
596
597 for (i = 0 ; i < ngroups ; i++) {
598 if ((grp = getgrnam(groups[i])) == NULL) {
599 warnx("can't append group `%s' for user `%s'",
600 groups[i], user);
601 } else {
602 for (j = 0 ; grp->gr_mem[j] ; j++) {
603 if (strcmp(user, grp->gr_mem[j]) == 0) {
604 /* already in it */
605 groups[i] = "";
606 }
607 }
608 }
609 }
610 if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
611 warn("can't append group for `%s': can't open `%s'", user,
612 _PATH_GROUP);
613 return 0;
614 }
615 if (flock(fileno(from), LOCK_EX | LOCK_NB) == -1) {
616 warn("can't lock `%s'", _PATH_GROUP);
617 }
618 (void) fstat(fileno(from), &st);
619 (void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
620 if ((fd = mkstemp(f)) == -1) {
621 warn("can't append group: mkstemp failed");
622 fclose(from);
623 return 0;
624 }
625 if ((to = fdopen(fd, "w")) == NULL) {
626 warn("can't append group: fdopen `%s' failed", f);
627 fclose(from);
628 close(fd);
629 unlink(f);
630 return 0;
631 }
632 while (fgets(buf, sizeof(buf), from) != NULL) {
633 cc = strlen(buf);
634 if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
635 while (fgetc(from) != '\n' && !feof(from))
636 cc++;
637 warnx("%s: line `%s' too long (%d bytes), skipping",
638 _PATH_GROUP, buf, cc);
639 continue;
640 }
641 if ((colon = strchr(buf, ':')) == NULL) {
642 warnx("badly formed entry `%s'", buf);
643 continue;
644 }
645 for (i = 0 ; i < ngroups ; i++) {
646 j = (int)(colon - buf);
647 if (ugid) {
648 if (strcmp(ugid, groups[i]) == 0) {
649 /* user's primary group, no need to append */
650 groups[i] = "";
651 }
652 }
653 if (strncmp(groups[i], buf, j) == 0 &&
654 groups[i][j] == '\0') {
655 while (isspace((unsigned char)buf[cc - 1]))
656 cc--;
657 buf[(j = cc)] = '\0';
658 if (buf[strlen(buf) - 1] != ':')
659 strlcat(buf, ",", sizeof(buf));
660 cc = strlcat(buf, user, sizeof(buf)) + 1;
661 if (cc >= sizeof(buf)) {
662 warnx("Warning: group `%s' would "
663 "become too long, not modifying",
664 groups[i]);
665 cc = j + 1;
666 }
667 buf[cc - 1] = '\n';
668 buf[cc] = '\0';
669 }
670 }
671 if (fwrite(buf, cc, 1, to) != 1) {
672 warn("can't append group: short write to `%s'", f);
673 fclose(from);
674 fclose(to);
675 unlink(f);
676 return 0;
677 }
678 }
679 fclose(from);
680 if (fclose(to) == EOF) {
681 warn("can't append group: short write to `%s'", f);
682 unlink(f);
683 return 0;
684 }
685 if (rename(f, _PATH_GROUP) == -1) {
686 warn("can't append group: can't rename `%s' to `%s'", f, _PATH_GROUP);
687 unlink(f);
688 return 0;
689 }
690 (void) chmod(_PATH_GROUP, st.st_mode & 0777);
691 return 1;
692 }
693
694 /* return 1 if `login' is a valid login name */
695 static int
valid_login(char * login_name)696 valid_login(char *login_name)
697 {
698 unsigned char *cp;
699
700 /* The first character cannot be a hyphen */
701 if (*login_name == '-')
702 return 0;
703
704 for (cp = login_name ; *cp ; cp++) {
705 /* We allow '$' as the last character for samba */
706 if (!isalnum((unsigned char)*cp) && *cp != '.' &&
707 *cp != '_' && *cp != '-' &&
708 !(*cp == '$' && *(cp + 1) == '\0')) {
709 return 0;
710 }
711 }
712 if ((char *)cp - login_name > MaxUserNameLen)
713 return 0;
714 return 1;
715 }
716
717 /* return 1 if `group' is a valid group name */
718 static int
valid_group(char * group)719 valid_group(char *group)
720 {
721 unsigned char *cp;
722
723 for (cp = group ; *cp ; cp++) {
724 if (!isalnum((unsigned char)*cp) && *cp != '.' &&
725 *cp != '_' && *cp != '-') {
726 return 0;
727 }
728 }
729 if ((char *)cp - group > MaxUserNameLen)
730 return 0;
731 return 1;
732 }
733
734 /* return 1 if `class' exists */
735 static int
valid_class(char * class)736 valid_class(char *class)
737 {
738 login_cap_t *lc;
739
740 if ((lc = login_getclass(class)) != NULL)
741 login_close(lc);
742 return lc != NULL;
743 }
744
745 /* find the next gid in the range lo .. hi */
746 static int
getnextgid(uid_t * gidp,uid_t lo,uid_t hi)747 getnextgid(uid_t *gidp, uid_t lo, uid_t hi)
748 {
749 for (*gidp = lo ; *gidp < hi ; *gidp += 1) {
750 if (!gid_exists((gid_t)*gidp)) {
751 return 1;
752 }
753 }
754 return 0;
755 }
756
757 /* save a range of uids */
758 static int
save_range(user_t * up,char * cp)759 save_range(user_t *up, char *cp)
760 {
761 uid_t from;
762 uid_t to;
763 int i;
764
765 if (up->u_rc == up->u_rsize) {
766 up->u_rsize *= 2;
767 if ((up->u_rv = reallocarray(up->u_rv, up->u_rsize,
768 sizeof(range_t))) == NULL) {
769 warn(NULL);
770 return 0;
771 }
772 }
773 if (up->u_rv && sscanf(cp, "%u..%u", &from, &to) == 2) {
774 for (i = up->u_defrc ; i < up->u_rc ; i++) {
775 if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) {
776 break;
777 }
778 }
779 if (i == up->u_rc) {
780 up->u_rv[up->u_rc].r_from = from;
781 up->u_rv[up->u_rc].r_to = to;
782 up->u_rc += 1;
783 }
784 } else {
785 warnx("Bad uid range `%s'", cp);
786 return 0;
787 }
788 return 1;
789 }
790
791 /* set the defaults in the defaults file */
792 static int
setdefaults(user_t * up)793 setdefaults(user_t *up)
794 {
795 char template[MaxFileNameLen];
796 FILE *fp;
797 int ret;
798 int fd;
799 int i;
800
801 (void) snprintf(template, sizeof(template), "%s.XXXXXXXX", CONFFILE);
802 if ((fd = mkstemp(template)) == -1) {
803 warnx("can't mkstemp `%s' for writing", CONFFILE);
804 return 0;
805 }
806 if ((fp = fdopen(fd, "w")) == NULL) {
807 warn("can't fdopen `%s' for writing", CONFFILE);
808 return 0;
809 }
810 ret = 1;
811 if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 ||
812 fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 ||
813 fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 ||
814 fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 ||
815 fprintf(fp, "class\t\t%s\n", up->u_class) <= 0 ||
816 fprintf(fp, "inactive\t%s\n", (up->u_inactive == NULL) ? UNSET_INACTIVE : up->u_inactive) <= 0 ||
817 fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? UNSET_EXPIRY : up->u_expire) <= 0 ||
818 fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? "false" : "true") <= 0) {
819 warn("can't write to `%s'", CONFFILE);
820 ret = 0;
821 }
822 for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) {
823 if (fprintf(fp, "range\t\t%u..%u\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) {
824 warn("can't write to `%s'", CONFFILE);
825 ret = 0;
826 }
827 }
828 if (fclose(fp) == EOF) {
829 warn("can't write to `%s'", CONFFILE);
830 ret = 0;
831 }
832 if (ret) {
833 ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0));
834 }
835 return ret;
836 }
837
838 /* read the defaults file */
839 static void
read_defaults(user_t * up)840 read_defaults(user_t *up)
841 {
842 struct stat st;
843 size_t lineno;
844 size_t len;
845 FILE *fp;
846 unsigned char *cp;
847 unsigned char *s;
848
849 strsave(&up->u_primgrp, DEF_GROUP);
850 strsave(&up->u_basedir, DEF_BASEDIR);
851 strsave(&up->u_skeldir, DEF_SKELDIR);
852 strsave(&up->u_shell, DEF_SHELL);
853 strsave(&up->u_comment, DEF_COMMENT);
854 strsave(&up->u_class, DEF_CLASS);
855 up->u_rsize = 16;
856 up->u_defrc = 0;
857 if ((up->u_rv = calloc(up->u_rsize, sizeof(range_t))) == NULL)
858 err(1, NULL);
859 up->u_inactive = DEF_INACTIVE;
860 up->u_expire = DEF_EXPIRE;
861 if ((fp = fopen(CONFFILE, "r")) == NULL) {
862 if (stat(CONFFILE, &st) == -1 && !setdefaults(up)) {
863 warn("can't create `%s' defaults file", CONFFILE);
864 }
865 fp = fopen(CONFFILE, "r");
866 }
867 if (fp != NULL) {
868 while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) {
869 if (strncmp(s, "group", 5) == 0) {
870 for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
871 }
872 strsave(&up->u_primgrp, cp);
873 } else if (strncmp(s, "base_dir", 8) == 0) {
874 for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
875 }
876 strsave(&up->u_basedir, cp);
877 } else if (strncmp(s, "skel_dir", 8) == 0) {
878 for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
879 }
880 strsave(&up->u_skeldir, cp);
881 } else if (strncmp(s, "shell", 5) == 0) {
882 for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
883 }
884 strsave(&up->u_shell, cp);
885 } else if (strncmp(s, "password", 8) == 0) {
886 for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
887 }
888 strsave(&up->u_password, cp);
889 } else if (strncmp(s, "class", 5) == 0) {
890 for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
891 }
892 strsave(&up->u_class, cp);
893 } else if (strncmp(s, "inactive", 8) == 0) {
894 for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
895 }
896 if (strcmp(cp, UNSET_INACTIVE) == 0) {
897 free(up->u_inactive);
898 up->u_inactive = NULL;
899 } else {
900 strsave(&up->u_inactive, cp);
901 }
902 } else if (strncmp(s, "range", 5) == 0) {
903 for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
904 }
905 (void) save_range(up, cp);
906 } else if (strncmp(s, "preserve", 8) == 0) {
907 for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
908 }
909 up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 :
910 (strncmp(cp, "yes", 3) == 0) ? 1 :
911 strtonum(cp, INT_MIN, INT_MAX, NULL);
912 } else if (strncmp(s, "expire", 6) == 0) {
913 for (cp = s + 6 ; isspace((unsigned char)*cp); cp++) {
914 }
915 if (strcmp(cp, UNSET_EXPIRY) == 0) {
916 free(up->u_expire);
917 up->u_expire = NULL;
918 } else {
919 strsave(&up->u_expire, cp);
920 }
921 }
922 free(s);
923 }
924 fclose(fp);
925 }
926 if (up->u_rc == 0) {
927 up->u_rv[up->u_rc].r_from = DEF_LOWUID;
928 up->u_rv[up->u_rc].r_to = DEF_HIGHUID;
929 up->u_rc += 1;
930 }
931 up->u_defrc = up->u_rc;
932 }
933
934 /* return 1 if the specified uid exists in the passwd file, else 0 */
935 static int
uid_exists(uid_t uid)936 uid_exists(uid_t uid)
937 {
938 return user_from_uid(uid, 1) != NULL;
939 }
940
941 /* return 1 if the specified user exists in the passwd file, else 0 */
942 static int
user_exists(const char * user)943 user_exists(const char *user)
944 {
945 uid_t uid;
946
947 return uid_from_user(user, &uid) != -1;
948 }
949
950 /* return the next valid unused uid */
951 static int
getnextuid(int sync_uid_gid,uid_t * uid,uid_t low_uid,uid_t high_uid)952 getnextuid(int sync_uid_gid, uid_t *uid, uid_t low_uid, uid_t high_uid)
953 {
954 for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) {
955 if (!uid_exists((uid_t)*uid) && *uid != NOBODY_UID) {
956 if (sync_uid_gid) {
957 if (!gid_exists((gid_t)*uid)) {
958 return 1;
959 }
960 } else {
961 return 1;
962 }
963 }
964 }
965 return 0;
966 }
967
968 /* look for a valid time, return 0 if it was specified but bad */
969 static int
scantime(time_t * tp,char * s)970 scantime(time_t *tp, char *s)
971 {
972 struct tm tm;
973
974 *tp = 0;
975 if (s != NULL) {
976 memset(&tm, 0, sizeof(tm));
977 tm.tm_isdst = -1;
978 if (strptime(s, "%c", &tm) != NULL) {
979 *tp = mktime(&tm);
980 } else if (strptime(s, "%B %d %Y", &tm) != NULL) {
981 *tp = mktime(&tm);
982 } else if (isdigit((unsigned char) s[0]) != 0) {
983 *tp = (time_t)atoll(s);
984 } else {
985 return 0;
986 }
987 }
988 return 1;
989 }
990
991 /* compute the extra length '&' expansion consumes */
992 static size_t
expand_len(const char * p,const char * username)993 expand_len(const char *p, const char *username)
994 {
995 size_t alen;
996 size_t ulen;
997
998 ulen = strlen(username);
999 for (alen = 0; *p != '\0'; p++)
1000 if (*p == '&')
1001 alen += ulen - 1;
1002 return alen;
1003 }
1004
1005 /* see if we can find out the user struct */
1006 static struct passwd *
find_user_info(const char * name)1007 find_user_info(const char *name)
1008 {
1009 struct passwd *pwp;
1010 const char *errstr;
1011 uid_t uid;
1012
1013 if ((pwp = getpwnam(name)) == NULL) {
1014 uid = strtonum(name, -1, UID_MAX, &errstr);
1015 if (errstr == NULL)
1016 pwp = getpwuid(uid);
1017 }
1018 return pwp;
1019 }
1020
1021 /* see if we can find out the group struct */
1022 static struct group *
find_group_info(const char * name)1023 find_group_info(const char *name)
1024 {
1025 struct group *grp;
1026 const char *errstr;
1027 gid_t gid;
1028
1029 if ((grp = getgrnam(name)) == NULL) {
1030 gid = strtonum(name, -1, GID_MAX, &errstr);
1031 if (errstr == NULL)
1032 grp = getgrgid(gid);
1033 }
1034 return grp;
1035 }
1036
1037 /* add a user */
1038 static int
adduser(char * login_name,user_t * up)1039 adduser(char *login_name, user_t *up)
1040 {
1041 struct group *grp;
1042 struct stat st;
1043 time_t expire;
1044 time_t inactive;
1045 char password[PasswordLength + 1];
1046 char home[MaxFileNameLen];
1047 char buf[LINE_MAX];
1048 int sync_uid_gid;
1049 int masterfd;
1050 int ptmpfd;
1051 gid_t gid;
1052 int cc;
1053 int i, yp = 0;
1054 FILE *fp;
1055
1056 if (!valid_login(login_name)) {
1057 errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
1058 }
1059 if (!valid_class(up->u_class)) {
1060 errx(EXIT_FAILURE, "No such login class `%s'", up->u_class);
1061 }
1062 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) == -1) {
1063 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
1064 }
1065 if (flock(masterfd, LOCK_EX | LOCK_NB) == -1) {
1066 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
1067 }
1068 pw_init();
1069 if ((ptmpfd = pw_lock(WAITSECS)) == -1) {
1070 int saved_errno = errno;
1071 close(masterfd);
1072 errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
1073 }
1074 if ((fp = fdopen(masterfd, "r")) == NULL) {
1075 int saved_errno = errno;
1076 close(masterfd);
1077 close(ptmpfd);
1078 pw_abort();
1079 errc(EXIT_FAILURE, saved_errno,
1080 "can't fdopen `%s' for reading", _PATH_MASTERPASSWD);
1081 }
1082 while (fgets(buf, sizeof(buf), fp) != NULL) {
1083 cc = strlen(buf);
1084 /*
1085 * Stop copying the file at the yp entry; we want to
1086 * put the new user before it, and preserve entries
1087 * after the yp entry.
1088 */
1089 if (cc > 1 && buf[0] == '+' && buf[1] == ':') {
1090 yp = 1;
1091 break;
1092 }
1093 if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1094 int saved_errno = errno;
1095 fclose(fp);
1096 close(ptmpfd);
1097 pw_abort();
1098 errc(EXIT_FAILURE, saved_errno,
1099 "short write to /etc/ptmp (not %d chars)", cc);
1100 }
1101 }
1102 if (ferror(fp)) {
1103 int saved_errno = errno;
1104 fclose(fp);
1105 close(ptmpfd);
1106 pw_abort();
1107 errc(EXIT_FAILURE, saved_errno, "read error on %s",
1108 _PATH_MASTERPASSWD);
1109 }
1110 /* if no uid was specified, get next one in [low_uid..high_uid] range */
1111 sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
1112 if (up->u_uid == -1) {
1113 int got_id = 0;
1114
1115 /*
1116 * Look for a free UID in the command line ranges (if any).
1117 * These start after the ranges specified in the config file.
1118 */
1119 for (i = up->u_defrc; got_id == 0 && i < up->u_rc ; i++) {
1120 got_id = getnextuid(sync_uid_gid, &up->u_uid,
1121 up->u_rv[i].r_from, up->u_rv[i].r_to);
1122 }
1123 /*
1124 * If there were no free UIDs in the command line ranges,
1125 * try the ranges from the config file (there will always
1126 * be at least one default).
1127 */
1128 if (got_id == 0) {
1129 for (i = 0; got_id == 0 && i < up->u_defrc; i++) {
1130 got_id = getnextuid(sync_uid_gid, &up->u_uid,
1131 up->u_rv[i].r_from, up->u_rv[i].r_to);
1132 }
1133 }
1134 if (got_id == 0) {
1135 close(ptmpfd);
1136 pw_abort();
1137 errx(EXIT_FAILURE, "can't get next uid for %u", up->u_uid);
1138 }
1139 }
1140 /* check uid isn't already allocated */
1141 if (!(up->u_flags & F_DUPUID) && uid_exists((uid_t)up->u_uid)) {
1142 close(ptmpfd);
1143 pw_abort();
1144 errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1145 }
1146 /* if -g=uid was specified, check gid is unused */
1147 if (sync_uid_gid) {
1148 if (gid_exists((gid_t)up->u_uid)) {
1149 close(ptmpfd);
1150 pw_abort();
1151 errx(EXIT_FAILURE, "gid %u is already in use", up->u_uid);
1152 }
1153 gid = up->u_uid;
1154 } else {
1155 if ((grp = find_group_info(up->u_primgrp)) == NULL) {
1156 close(ptmpfd);
1157 pw_abort();
1158 errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
1159 }
1160 gid = grp->gr_gid;
1161 }
1162 /* check name isn't already in use */
1163 if (!(up->u_flags & F_DUPUID) && user_exists(login_name)) {
1164 close(ptmpfd);
1165 pw_abort();
1166 errx(EXIT_FAILURE, "already a `%s' user", login_name);
1167 }
1168 if (up->u_flags & F_HOMEDIR) {
1169 if (strlcpy(home, up->u_home, sizeof(home)) >= sizeof(home)) {
1170 close(ptmpfd);
1171 pw_abort();
1172 errx(EXIT_FAILURE, "home directory `%s' too long",
1173 up->u_home);
1174 }
1175 } else {
1176 /* if home directory hasn't been given, make it up */
1177 if (snprintf(home, sizeof(home), "%s/%s", up->u_basedir,
1178 login_name) >= sizeof(home)) {
1179 close(ptmpfd);
1180 pw_abort();
1181 errx(EXIT_FAILURE, "home directory `%s/%s' too long",
1182 up->u_basedir, login_name);
1183 }
1184 }
1185 if (!scantime(&inactive, up->u_inactive)) {
1186 warnx("Warning: inactive time `%s' invalid, password expiry off",
1187 up->u_inactive);
1188 }
1189 if (!scantime(&expire, up->u_expire)) {
1190 warnx("Warning: expire time `%s' invalid, account expiry off",
1191 up->u_expire);
1192 }
1193 if (lstat(home, &st) == -1 && !(up->u_flags & F_MKDIR) &&
1194 strcmp(home, _PATH_NONEXISTENT) != 0) {
1195 warnx("Warning: home directory `%s' doesn't exist, and -m was"
1196 " not specified", home);
1197 }
1198 (void) strlcpy(password, up->u_password ? up->u_password : "*",
1199 sizeof(password));
1200 cc = snprintf(buf, sizeof(buf), "%s:%s:%u:%u:%s:%lld:%lld:%s:%s:%s\n",
1201 login_name,
1202 password,
1203 up->u_uid,
1204 gid,
1205 up->u_class,
1206 (long long) inactive,
1207 (long long) expire,
1208 up->u_comment,
1209 home,
1210 up->u_shell);
1211 if (cc >= sizeof(buf) || cc < 0 ||
1212 cc + expand_len(up->u_comment, login_name) >= 1023) {
1213 close(ptmpfd);
1214 pw_abort();
1215 errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1216 }
1217 if (write(ptmpfd, buf, (size_t) cc) != cc) {
1218 int saved_errno = errno;
1219 close(ptmpfd);
1220 pw_abort();
1221 errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1222 }
1223 if (yp) {
1224 /* put back the + line */
1225 cc = snprintf(buf, sizeof(buf), "+:*::::::::\n");
1226 if (cc < 0 || cc >= sizeof(buf)) {
1227 close(ptmpfd);
1228 pw_abort();
1229 errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1230 }
1231 if (write(ptmpfd, buf, (size_t) cc) != cc) {
1232 int saved_errno = errno;
1233 close(ptmpfd);
1234 pw_abort();
1235 errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1236 }
1237 /* copy the entries following it, if any */
1238 while (fgets(buf, sizeof(buf), fp) != NULL) {
1239 cc = strlen(buf);
1240 if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1241 int saved_errno = errno;
1242 fclose(fp);
1243 close(ptmpfd);
1244 pw_abort();
1245 errc(EXIT_FAILURE, saved_errno,
1246 "short write to /etc/ptmp (not %d chars)",
1247 cc);
1248 }
1249 }
1250 if (ferror(fp)) {
1251 int saved_errno = errno;
1252 fclose(fp);
1253 close(ptmpfd);
1254 pw_abort();
1255 errc(EXIT_FAILURE, saved_errno, "read error on %s",
1256 _PATH_MASTERPASSWD);
1257 }
1258 }
1259 if (up->u_flags & F_MKDIR) {
1260 if (lstat(home, &st) == 0) {
1261 close(ptmpfd);
1262 pw_abort();
1263 errx(EXIT_FAILURE, "home directory `%s' already exists",
1264 home);
1265 } else {
1266 char idstr[64];
1267 const char *mkdir_argv[] =
1268 { "mkdir", "-p", home, NULL };
1269 const char *chown_argv[] =
1270 { "chown", "-RP", idstr, home, NULL };
1271 const char *chmod_argv[] =
1272 { "chmod", "-R", "u+w", home, NULL };
1273
1274 if (run(MKDIR, mkdir_argv) != 0) {
1275 int saved_errno = errno;
1276 close(ptmpfd);
1277 pw_abort();
1278 errc(EXIT_FAILURE, saved_errno,
1279 "can't mkdir `%s'", home);
1280 }
1281 (void) copydotfiles(up->u_skeldir, home);
1282 (void) snprintf(idstr, sizeof(idstr), "%u:%u",
1283 up->u_uid, gid);
1284 (void) run(CHOWN, chown_argv);
1285 (void) run(CHMOD, chmod_argv);
1286 }
1287 }
1288 if (strcmp(up->u_primgrp, "=uid") == 0 && !group_exists(login_name) &&
1289 !creategid(login_name, gid, "")) {
1290 close(ptmpfd);
1291 pw_abort();
1292 errx(EXIT_FAILURE, "can't create gid %u for login name %s",
1293 gid, login_name);
1294 }
1295 if (up->u_groupc > 0 && !append_group(login_name, up->u_groupc, up->u_groupv)) {
1296 close(ptmpfd);
1297 pw_abort();
1298 errx(EXIT_FAILURE, "can't append `%s' to new groups", login_name);
1299 }
1300 fclose(fp);
1301 close(ptmpfd);
1302 if (pw_mkdb(yp ? NULL : login_name, 0) == -1) {
1303 pw_abort();
1304 err(EXIT_FAILURE, "pw_mkdb failed");
1305 }
1306 syslog(LOG_INFO, "new user added: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1307 login_name, up->u_uid, gid, home, up->u_shell);
1308 return 1;
1309 }
1310
1311 /* remove a user from the groups file */
1312 static int
rm_user_from_groups(char * login_name)1313 rm_user_from_groups(char *login_name)
1314 {
1315 struct stat st;
1316 size_t login_len;
1317 FILE *from;
1318 FILE *to;
1319 char buf[LINE_MAX];
1320 char f[MaxFileNameLen];
1321 char *cp, *ep;
1322 int fd;
1323 int cc;
1324
1325 login_len = strlen(login_name);
1326 if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
1327 warn("can't remove gid for `%s': can't open `%s'",
1328 login_name, _PATH_GROUP);
1329 return 0;
1330 }
1331 if (flock(fileno(from), LOCK_EX | LOCK_NB) == -1) {
1332 warn("can't lock `%s'", _PATH_GROUP);
1333 }
1334 (void) fstat(fileno(from), &st);
1335 (void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
1336 if ((fd = mkstemp(f)) == -1) {
1337 warn("can't remove gid for `%s': mkstemp failed", login_name);
1338 fclose(from);
1339 return 0;
1340 }
1341 if ((to = fdopen(fd, "w")) == NULL) {
1342 warn("can't remove gid for `%s': fdopen `%s' failed",
1343 login_name, f);
1344 fclose(from);
1345 close(fd);
1346 unlink(f);
1347 return 0;
1348 }
1349 while (fgets(buf, sizeof(buf), from) != NULL) {
1350 cc = strlen(buf);
1351 if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
1352 while (fgetc(from) != '\n' && !feof(from))
1353 cc++;
1354 warnx("%s: line `%s' too long (%d bytes), skipping",
1355 _PATH_GROUP, buf, cc);
1356 continue;
1357 }
1358
1359 /* Break out the group list. */
1360 for (cp = buf, cc = 0; *cp != '\0' && cc < 3; cp++) {
1361 if (*cp == ':')
1362 cc++;
1363 }
1364 if (cc != 3) {
1365 buf[strcspn(buf, "\n")] = '\0';
1366 warnx("Malformed entry `%s'. Skipping", buf);
1367 continue;
1368 }
1369 while ((cp = strstr(cp, login_name)) != NULL) {
1370 if ((cp[-1] == ':' || cp[-1] == ',') &&
1371 (cp[login_len] == ',' || cp[login_len] == '\n')) {
1372 ep = cp + login_len;
1373 if (cp[login_len] == ',')
1374 ep++;
1375 else if (cp[-1] == ',')
1376 cp--;
1377 memmove(cp, ep, strlen(ep) + 1);
1378 } else {
1379 if ((cp = strchr(cp, ',')) == NULL)
1380 break;
1381 cp++;
1382 }
1383 }
1384 if (fwrite(buf, strlen(buf), 1, to) != 1) {
1385 warn("can't remove gid for `%s': short write to `%s'",
1386 login_name, f);
1387 fclose(from);
1388 fclose(to);
1389 unlink(f);
1390 return 0;
1391 }
1392 }
1393 (void) fchmod(fileno(to), st.st_mode & 0777);
1394 fclose(from);
1395 if (fclose(to) == EOF) {
1396 warn("can't remove gid for `%s': short write to `%s'",
1397 login_name, f);
1398 unlink(f);
1399 return 0;
1400 }
1401 if (rename(f, _PATH_GROUP) == -1) {
1402 warn("can't remove gid for `%s': can't rename `%s' to `%s'",
1403 login_name, f, _PATH_GROUP);
1404 unlink(f);
1405 return 0;
1406 }
1407 return 1;
1408 }
1409
1410 /* check that the user or group is local, not from YP/NIS */
1411 static int
is_local(char * name,const char * file)1412 is_local(char *name, const char *file)
1413 {
1414 FILE *fp;
1415 char buf[LINE_MAX];
1416 size_t len;
1417 int ret;
1418 int cc;
1419
1420 if ((fp = fopen(file, "r")) == NULL) {
1421 err(EXIT_FAILURE, "can't open `%s'", file);
1422 }
1423 len = strlen(name);
1424 for (ret = 0 ; fgets(buf, sizeof(buf), fp) != NULL ; ) {
1425 cc = strlen(buf);
1426 if (cc > 0 && buf[cc - 1] != '\n' && !feof(fp)) {
1427 while (fgetc(fp) != '\n' && !feof(fp))
1428 cc++;
1429 warnx("%s: line `%s' too long (%d bytes), skipping",
1430 file, buf, cc);
1431 continue;
1432 }
1433 if (strncmp(buf, name, len) == 0 && buf[len] == ':') {
1434 ret = 1;
1435 break;
1436 }
1437 }
1438 fclose(fp);
1439 return ret;
1440 }
1441
1442 /* modify a user */
1443 static int
moduser(char * login_name,char * newlogin,user_t * up)1444 moduser(char *login_name, char *newlogin, user_t *up)
1445 {
1446 struct passwd *pwp = NULL;
1447 struct group *grp;
1448 const char *homedir;
1449 char buf[LINE_MAX];
1450 char acctlock_str[] = "-";
1451 char pwlock_str[] = "*";
1452 char pw_len[PasswordLength + 1];
1453 char shell_len[MaxShellNameLen];
1454 char *shell_last_char;
1455 size_t colonc, loginc;
1456 size_t cc;
1457 size_t shell_buf;
1458 FILE *master;
1459 char newdir[MaxFileNameLen];
1460 char *colon;
1461 char *pw_tmp = NULL;
1462 char *shell_tmp = NULL;
1463 int len;
1464 int locked = 0;
1465 int unlocked = 0;
1466 int masterfd;
1467 int ptmpfd;
1468 int rval;
1469 int i;
1470
1471 if (!valid_login(newlogin)) {
1472 errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
1473 }
1474 if ((pwp = getpwnam_shadow(login_name)) == NULL) {
1475 errx(EXIT_FAILURE, "No such user `%s'", login_name);
1476 }
1477 if (up != NULL) {
1478 if ((*pwp->pw_passwd != '\0') &&
1479 (up->u_flags & F_PASSWORD) == 0) {
1480 up->u_flags |= F_PASSWORD;
1481 strsave(&up->u_password, pwp->pw_passwd);
1482 explicit_bzero(pwp->pw_passwd, strlen(pwp->pw_passwd));
1483 }
1484 }
1485 endpwent();
1486
1487 if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
1488 NULL) == -1)
1489 err(1, "pledge");
1490
1491 if (!is_local(login_name, _PATH_MASTERPASSWD)) {
1492 errx(EXIT_FAILURE, "User `%s' must be a local user", login_name);
1493 }
1494 if (up != NULL) {
1495 if ((up->u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)) && (pwp->pw_uid < 1000))
1496 errx(EXIT_FAILURE, "(un)locking is not supported for the `%s' account", pwp->pw_name);
1497 }
1498 /* keep dir name in case we need it for '-m' */
1499 homedir = pwp->pw_dir;
1500
1501 /* get the last char of the shell in case we need it for '-U' or '-Z' */
1502 shell_last_char = pwp->pw_shell+strlen(pwp->pw_shell) - 1;
1503
1504 if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) == -1) {
1505 err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
1506 }
1507 if (flock(masterfd, LOCK_EX | LOCK_NB) == -1) {
1508 err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
1509 }
1510 pw_init();
1511 if ((ptmpfd = pw_lock(WAITSECS)) == -1) {
1512 int saved_errno = errno;
1513 close(masterfd);
1514 errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
1515 }
1516 if ((master = fdopen(masterfd, "r")) == NULL) {
1517 int saved_errno = errno;
1518 close(masterfd);
1519 close(ptmpfd);
1520 pw_abort();
1521 errc(EXIT_FAILURE, saved_errno, "can't fdopen fd for %s",
1522 _PATH_MASTERPASSWD);
1523 }
1524 if (up != NULL) {
1525 if (up->u_flags & F_USERNAME) {
1526 /* if changing name, check new name isn't already in use */
1527 if (strcmp(login_name, newlogin) != 0 &&
1528 user_exists(newlogin)) {
1529 close(ptmpfd);
1530 pw_abort();
1531 errx(EXIT_FAILURE, "already a `%s' user", newlogin);
1532 }
1533 pwp->pw_name = newlogin;
1534
1535 /*
1536 * Provide a new directory name in case the
1537 * home directory is to be moved.
1538 */
1539 if (up->u_flags & F_MKDIR) {
1540 (void) snprintf(newdir, sizeof(newdir),
1541 "%s/%s", up->u_basedir, newlogin);
1542 pwp->pw_dir = newdir;
1543 }
1544 }
1545 if (up->u_flags & F_PASSWORD) {
1546 if (up->u_password != NULL)
1547 pwp->pw_passwd = up->u_password;
1548 }
1549 if (up->u_flags & F_ACCTLOCK) {
1550 /* lock the account */
1551 if (*shell_last_char != *acctlock_str) {
1552 shell_tmp = malloc(strlen(pwp->pw_shell) + sizeof(acctlock_str));
1553 if (shell_tmp == NULL) {
1554 close(ptmpfd);
1555 pw_abort();
1556 errx(EXIT_FAILURE, "account lock: cannot allocate memory");
1557 }
1558 strlcpy(shell_tmp, pwp->pw_shell, sizeof(shell_len));
1559 strlcat(shell_tmp, acctlock_str, sizeof(shell_len));
1560 pwp->pw_shell = shell_tmp;
1561 } else {
1562 locked++;
1563 }
1564 /* lock the password */
1565 if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) != 0) {
1566 pw_tmp = malloc(strlen(pwp->pw_passwd) + sizeof(pwlock_str));
1567 if (pw_tmp == NULL) {
1568 close(ptmpfd);
1569 pw_abort();
1570 errx(EXIT_FAILURE, "password lock: cannot allocate memory");
1571 }
1572 strlcpy(pw_tmp, pwlock_str, sizeof(pw_len));
1573 strlcat(pw_tmp, pwp->pw_passwd, sizeof(pw_len));
1574 pwp->pw_passwd = pw_tmp;
1575 } else {
1576 locked++;
1577 }
1578
1579 if (locked > 1)
1580 warnx("account `%s' is already locked", pwp->pw_name);
1581 }
1582 if (up->u_flags & F_ACCTUNLOCK) {
1583 /* unlock the password */
1584 if (strcmp(pwp->pw_passwd, pwlock_str) != 0 &&
1585 strcmp(pwp->pw_passwd, "*************") != 0) {
1586 if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) == 0) {
1587 pwp->pw_passwd += sizeof(pwlock_str)-1;
1588 } else {
1589 unlocked++;
1590 }
1591 } else {
1592 warnx("account `%s' has no password: cannot fully unlock", pwp->pw_name);
1593 }
1594 /* unlock the account */
1595 if (*shell_last_char == *acctlock_str) {
1596 shell_buf = strlen(pwp->pw_shell) + 2 - sizeof(acctlock_str);
1597 shell_tmp = malloc(shell_buf);
1598 if (shell_tmp == NULL) {
1599 close(ptmpfd);
1600 pw_abort();
1601 errx(EXIT_FAILURE, "unlock: cannot allocate memory");
1602 }
1603 strlcpy(shell_tmp, pwp->pw_shell, shell_buf);
1604 pwp->pw_shell = shell_tmp;
1605 } else {
1606 unlocked++;
1607 }
1608
1609 if (unlocked > 1)
1610 warnx("account `%s' is not locked", pwp->pw_name);
1611 }
1612 if (up->u_flags & F_UID) {
1613 /* check uid isn't already allocated */
1614 if (!(up->u_flags & F_DUPUID) &&
1615 uid_exists((uid_t)up->u_uid)) {
1616 close(ptmpfd);
1617 pw_abort();
1618 errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1619 }
1620 pwp->pw_uid = up->u_uid;
1621 }
1622 if (up->u_flags & F_GROUP) {
1623 /* if -g=uid was specified, check gid is unused */
1624 if (strcmp(up->u_primgrp, "=uid") == 0) {
1625 if (gid_exists((gid_t)pwp->pw_uid)) {
1626 close(ptmpfd);
1627 pw_abort();
1628 errx(EXIT_FAILURE, "gid %u is already "
1629 "in use", pwp->pw_uid);
1630 }
1631 pwp->pw_gid = pwp->pw_uid;
1632 if (!creategid(newlogin, pwp->pw_gid, "")) {
1633 close(ptmpfd);
1634 pw_abort();
1635 errx(EXIT_FAILURE, "could not create "
1636 "group %s with gid %u", newlogin,
1637 pwp->pw_gid);
1638 }
1639 } else {
1640 if ((grp = find_group_info(up->u_primgrp)) == NULL) {
1641 close(ptmpfd);
1642 pw_abort();
1643 errx(EXIT_FAILURE, "group %s not found",
1644 up->u_primgrp);
1645 }
1646 pwp->pw_gid = grp->gr_gid;
1647 }
1648 }
1649 if (up->u_flags & F_INACTIVE) {
1650 if (!scantime(&pwp->pw_change, up->u_inactive)) {
1651 warnx("Warning: inactive time `%s' invalid, password expiry off",
1652 up->u_inactive);
1653 }
1654 }
1655 if (up->u_flags & F_EXPIRE) {
1656 if (!scantime(&pwp->pw_expire, up->u_expire)) {
1657 warnx("Warning: expire time `%s' invalid, account expiry off",
1658 up->u_expire);
1659 }
1660 }
1661 if (up->u_flags & F_COMMENT)
1662 pwp->pw_gecos = up->u_comment;
1663 if (up->u_flags & F_HOMEDIR)
1664 pwp->pw_dir = up->u_home;
1665 if (up->u_flags & F_SHELL)
1666 pwp->pw_shell = up->u_shell;
1667 if (up->u_flags & F_CLASS) {
1668 if (!valid_class(up->u_class)) {
1669 close(ptmpfd);
1670 pw_abort();
1671 errx(EXIT_FAILURE,
1672 "No such login class `%s'", up->u_class);
1673 }
1674 pwp->pw_class = up->u_class;
1675 }
1676 }
1677 loginc = strlen(login_name);
1678 while (fgets(buf, sizeof(buf), master) != NULL) {
1679 if ((colon = strchr(buf, ':')) == NULL) {
1680 warnx("Malformed entry `%s'. Skipping", buf);
1681 continue;
1682 }
1683 colonc = (size_t)(colon - buf);
1684 if (strncmp(login_name, buf, loginc) == 0 && loginc == colonc) {
1685 if (up != NULL) {
1686 if ((len = snprintf(buf, sizeof(buf),
1687 "%s:%s:%u:%u:%s:%lld:%lld:%s:%s:%s\n",
1688 newlogin,
1689 pwp->pw_passwd,
1690 pwp->pw_uid,
1691 pwp->pw_gid,
1692 pwp->pw_class,
1693 (long long)pwp->pw_change,
1694 (long long)pwp->pw_expire,
1695 pwp->pw_gecos,
1696 pwp->pw_dir,
1697 pwp->pw_shell)) >= sizeof(buf) || len < 0 ||
1698 len + expand_len(pwp->pw_gecos, newlogin)
1699 >= 1023) {
1700 close(ptmpfd);
1701 pw_abort();
1702 errx(EXIT_FAILURE, "can't add `%s', "
1703 "line too long (%zu bytes)", buf,
1704 len + expand_len(pwp->pw_gecos,
1705 newlogin));
1706 }
1707 if (write(ptmpfd, buf, len) != len) {
1708 int saved_errno = errno;
1709 close(ptmpfd);
1710 pw_abort();
1711 errc(EXIT_FAILURE, saved_errno,
1712 "can't add `%s'", buf);
1713 }
1714 }
1715 } else {
1716 len = strlen(buf);
1717 if ((cc = write(ptmpfd, buf, len)) != len) {
1718 int saved_errno = errno;
1719 close(masterfd);
1720 close(ptmpfd);
1721 pw_abort();
1722 errc(EXIT_FAILURE, saved_errno,
1723 "short write to /etc/ptmp (%lld not %lld chars)",
1724 (long long)cc, (long long)len);
1725 }
1726 }
1727 }
1728 if (up != NULL) {
1729 const char *mv_argv[] = { "mv", homedir, pwp->pw_dir, NULL };
1730 if ((up->u_flags & F_MKDIR) &&
1731 run(MV, mv_argv) != 0) {
1732 int saved_errno = errno;
1733 close(ptmpfd);
1734 pw_abort();
1735 errc(EXIT_FAILURE, saved_errno,
1736 "can't move `%s' to `%s'", homedir, pwp->pw_dir);
1737 }
1738 if (up->u_flags & F_SETSECGROUP) {
1739 for (i = 0 ; i < up->u_groupc ; i++) {
1740 if (!group_exists(up->u_groupv[i])) {
1741 close(ptmpfd);
1742 pw_abort();
1743 errx(EXIT_FAILURE,
1744 "aborting, group `%s' does not exist",
1745 up->u_groupv[i]);
1746 }
1747 }
1748 if (!rm_user_from_groups(newlogin)) {
1749 close(ptmpfd);
1750 pw_abort();
1751 errx(EXIT_FAILURE,
1752 "can't reset groups for `%s'", newlogin);
1753 }
1754 }
1755 if (up->u_groupc > 0) {
1756 if (!append_group(newlogin, up->u_groupc, up->u_groupv)) {
1757 close(ptmpfd);
1758 pw_abort();
1759 errx(EXIT_FAILURE, "can't append `%s' to new groups",
1760 newlogin);
1761 }
1762 }
1763 }
1764 fclose(master);
1765 close(ptmpfd);
1766 free(pw_tmp);
1767 free(shell_tmp);
1768 if (up != NULL && strcmp(login_name, newlogin) == 0)
1769 rval = pw_mkdb(login_name, 0);
1770 else
1771 rval = pw_mkdb(NULL, 0);
1772 if (rval == -1) {
1773 pw_abort();
1774 err(EXIT_FAILURE, "pw_mkdb failed");
1775 }
1776 if (up == NULL) {
1777 syslog(LOG_INFO, "user removed: name=%s", login_name);
1778 } else if (strcmp(login_name, newlogin) == 0) {
1779 syslog(LOG_INFO, "user information modified: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1780 login_name, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, pwp->pw_shell);
1781 } else {
1782 syslog(LOG_INFO, "user information modified: name=%s, new name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1783 login_name, newlogin, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, pwp->pw_shell);
1784 }
1785 return 1;
1786 }
1787
1788 /* print out usage message, and then exit */
1789 void
usermgmt_usage(const char * prog)1790 usermgmt_usage(const char *prog)
1791 {
1792 if (strcmp(prog, "useradd") == 0) {
1793 fprintf(stderr, "usage: %s -D [-b base-directory] "
1794 "[-e expiry-time] [-f inactive-time]\n"
1795 " [-g gid | name | =uid] [-k skel-directory] "
1796 "[-L login-class]\n"
1797 " [-r low..high] [-s shell]\n", prog);
1798 fprintf(stderr, " %s [-mov] [-b base-directory] "
1799 "[-c comment] [-d home-directory]\n"
1800 " [-e expiry-time] [-f inactive-time]\n"
1801 " [-G secondary-group[,group,...]] "
1802 "[-g gid | name | =uid]\n"
1803 " [-k skel-directory] [-L login-class] "
1804 "[-p password] [-r low..high]\n"
1805 " [-s shell] [-u uid] user\n", prog);
1806 } else if (strcmp(prog, "usermod") == 0) {
1807 fprintf(stderr, "usage: %s [-moUvZ] "
1808 "[-c comment] [-d home-directory] [-e expiry-time]\n"
1809 " [-f inactive-time] "
1810 "[-G secondary-group[,group,...]]\n"
1811 " [-g gid | name | =uid] [-L login-class] "
1812 "[-l new-login]\n"
1813 " [-p password] "
1814 "[-S secondary-group[,group,...]]\n"
1815 " [-s shell] [-u uid] user\n",
1816 prog);
1817 } else if (strcmp(prog, "userdel") == 0) {
1818 fprintf(stderr, "usage: %s -D [-p preserve-value]\n",
1819 prog);
1820 fprintf(stderr, " %s [-rv] [-p preserve-value] "
1821 "user\n", prog);
1822 } else if (strcmp(prog, "userinfo") == 0) {
1823 fprintf(stderr, "usage: %s [-e] user\n", prog);
1824 } else if (strcmp(prog, "groupadd") == 0) {
1825 fprintf(stderr, "usage: %s [-ov] [-g gid] group\n",
1826 prog);
1827 } else if (strcmp(prog, "groupdel") == 0) {
1828 fprintf(stderr, "usage: %s [-v] group\n", prog);
1829 } else if (strcmp(prog, "groupmod") == 0) {
1830 fprintf(stderr, "usage: %s [-ov] [-g gid] [-n newname] "
1831 "group\n", prog);
1832 } else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) {
1833 fprintf(stderr, "usage: %s [add | del | mod"
1834 " | info"
1835 "] ...\n",
1836 prog);
1837 } else if (strcmp(prog, "groupinfo") == 0) {
1838 fprintf(stderr, "usage: %s [-e] group\n", prog);
1839 } else {
1840 fprintf(stderr, "This program must be called as {user,group}{add,del,mod,info},\n%s is not an understood name.\n", prog);
1841 }
1842 exit(EXIT_FAILURE);
1843 }
1844
1845 int
useradd(int argc,char ** argv)1846 useradd(int argc, char **argv)
1847 {
1848 user_t u;
1849 const char *errstr;
1850 int defaultfield;
1851 int bigD;
1852 int c;
1853 int i;
1854
1855 memset(&u, 0, sizeof(u));
1856 read_defaults(&u);
1857 u.u_uid = -1;
1858 defaultfield = bigD = 0;
1859 while ((c = getopt(argc, argv, "DG:L:b:c:d:e:f:g:k:mop:r:s:u:v")) != -1) {
1860 switch(c) {
1861 case 'D':
1862 bigD = 1;
1863 break;
1864 case 'G':
1865 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1866 u.u_groupc < NGROUPS_MAX - 2) {
1867 if (u.u_groupv[u.u_groupc][0] != 0) {
1868 u.u_groupc++;
1869 }
1870 }
1871 if (optarg != NULL) {
1872 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1873 }
1874 break;
1875 case 'b':
1876 defaultfield = 1;
1877 strsave(&u.u_basedir, optarg);
1878 break;
1879 case 'c':
1880 strsave(&u.u_comment, optarg);
1881 break;
1882 case 'd':
1883 strsave(&u.u_home, optarg);
1884 u.u_flags |= F_HOMEDIR;
1885 break;
1886 case 'e':
1887 defaultfield = 1;
1888 strsave(&u.u_expire, optarg);
1889 break;
1890 case 'f':
1891 defaultfield = 1;
1892 strsave(&u.u_inactive, optarg);
1893 break;
1894 case 'g':
1895 defaultfield = 1;
1896 strsave(&u.u_primgrp, optarg);
1897 break;
1898 case 'k':
1899 defaultfield = 1;
1900 strsave(&u.u_skeldir, optarg);
1901 break;
1902 case 'L':
1903 defaultfield = 1;
1904 strsave(&u.u_class, optarg);
1905 break;
1906 case 'm':
1907 u.u_flags |= F_MKDIR;
1908 break;
1909 case 'o':
1910 u.u_flags |= F_DUPUID;
1911 break;
1912 case 'p':
1913 strsave(&u.u_password, optarg);
1914 explicit_bzero(optarg, strlen(optarg));
1915 break;
1916 case 'r':
1917 defaultfield = 1;
1918 if (!save_range(&u, optarg))
1919 exit(EXIT_FAILURE);
1920 break;
1921 case 's':
1922 defaultfield = 1;
1923 strsave(&u.u_shell, optarg);
1924 break;
1925 case 'u':
1926 u.u_uid = strtonum(optarg, -1, UID_MAX, &errstr);
1927 if (errstr != NULL) {
1928 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1929 }
1930 break;
1931 case 'v':
1932 verbose = 1;
1933 break;
1934 default:
1935 usermgmt_usage("useradd");
1936 }
1937 }
1938
1939 if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
1940 NULL) == -1)
1941 err(1, "pledge");
1942
1943 if (bigD) {
1944 if (defaultfield) {
1945 checkeuid();
1946 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
1947 }
1948 printf("group\t\t%s\n", u.u_primgrp);
1949 printf("base_dir\t%s\n", u.u_basedir);
1950 printf("skel_dir\t%s\n", u.u_skeldir);
1951 printf("shell\t\t%s\n", u.u_shell);
1952 printf("class\t\t%s\n", u.u_class);
1953 printf("inactive\t%s\n", (u.u_inactive == NULL) ? UNSET_INACTIVE : u.u_inactive);
1954 printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire);
1955 for (i = 0 ; i < u.u_rc ; i++) {
1956 printf("range\t\t%u..%u\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
1957 }
1958 return EXIT_SUCCESS;
1959 }
1960 argc -= optind;
1961 argv += optind;
1962 if (argc != 1) {
1963 usermgmt_usage("useradd");
1964 }
1965 checkeuid();
1966 openlog("useradd", LOG_PID, LOG_USER);
1967 return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1968 }
1969
1970 int
usermod(int argc,char ** argv)1971 usermod(int argc, char **argv)
1972 {
1973 user_t u;
1974 char newuser[MaxUserNameLen + 1];
1975 int c, have_new_user;
1976 const char *errstr;
1977
1978 memset(&u, 0, sizeof(u));
1979 memset(newuser, 0, sizeof(newuser));
1980 read_defaults(&u);
1981 free(u.u_primgrp);
1982 u.u_primgrp = NULL;
1983 have_new_user = 0;
1984 while ((c = getopt(argc, argv, "G:L:S:UZc:d:e:f:g:l:mop:s:u:v")) != -1) {
1985 switch(c) {
1986 case 'G':
1987 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1988 u.u_groupc < NGROUPS_MAX - 2) {
1989 if (u.u_groupv[u.u_groupc][0] != 0) {
1990 u.u_groupc++;
1991 }
1992 }
1993 if (optarg != NULL) {
1994 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1995 }
1996 u.u_flags |= F_SECGROUP;
1997 break;
1998 case 'S':
1999 while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
2000 u.u_groupc < NGROUPS_MAX - 2) {
2001 if (u.u_groupv[u.u_groupc][0] != 0) {
2002 u.u_groupc++;
2003 }
2004 }
2005 if (optarg != NULL) {
2006 warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
2007 }
2008 u.u_flags |= F_SETSECGROUP;
2009 break;
2010 case 'U':
2011 u.u_flags |= F_ACCTUNLOCK;
2012 break;
2013 case 'Z':
2014 u.u_flags |= F_ACCTLOCK;
2015 break;
2016 case 'c':
2017 strsave(&u.u_comment, optarg);
2018 u.u_flags |= F_COMMENT;
2019 break;
2020 case 'd':
2021 strsave(&u.u_home, optarg);
2022 u.u_flags |= F_HOMEDIR;
2023 break;
2024 case 'e':
2025 strsave(&u.u_expire, optarg);
2026 u.u_flags |= F_EXPIRE;
2027 break;
2028 case 'f':
2029 strsave(&u.u_inactive, optarg);
2030 u.u_flags |= F_INACTIVE;
2031 break;
2032 case 'g':
2033 strsave(&u.u_primgrp, optarg);
2034 u.u_flags |= F_GROUP;
2035 break;
2036 case 'l':
2037 if (strlcpy(newuser, optarg, sizeof(newuser)) >=
2038 sizeof(newuser))
2039 errx(EXIT_FAILURE, "username `%s' too long",
2040 optarg);
2041 have_new_user = 1;
2042 u.u_flags |= F_USERNAME;
2043 break;
2044 case 'L':
2045 strsave(&u.u_class, optarg);
2046 u.u_flags |= F_CLASS;
2047 break;
2048 case 'm':
2049 u.u_flags |= F_MKDIR;
2050 break;
2051 case 'o':
2052 u.u_flags |= F_DUPUID;
2053 break;
2054 case 'p':
2055 strsave(&u.u_password, optarg);
2056 explicit_bzero(optarg, strlen(optarg));
2057 u.u_flags |= F_PASSWORD;
2058 break;
2059 case 's':
2060 strsave(&u.u_shell, optarg);
2061 u.u_flags |= F_SHELL;
2062 break;
2063 case 'u':
2064 u.u_uid = strtonum(optarg, -1, UID_MAX, &errstr);
2065 u.u_flags |= F_UID;
2066 if (errstr != NULL) {
2067 errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
2068 }
2069 break;
2070 case 'v':
2071 verbose = 1;
2072 break;
2073 default:
2074 usermgmt_usage("usermod");
2075 }
2076 }
2077
2078 if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) &&
2079 !(u.u_flags & F_USERNAME)) {
2080 warnx("option 'm' useless without 'd' or 'l' -- ignored");
2081 u.u_flags &= ~F_MKDIR;
2082 }
2083 if ((u.u_flags & F_SECGROUP) && (u.u_flags & F_SETSECGROUP))
2084 errx(EXIT_FAILURE, "options 'G' and 'S' are mutually exclusive");
2085 if ((u.u_flags & F_ACCTLOCK) && (u.u_flags & F_ACCTUNLOCK))
2086 errx(EXIT_FAILURE, "options 'U' and 'Z' are mutually exclusive");
2087 if ((u.u_flags & F_PASSWORD) && (u.u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)))
2088 errx(EXIT_FAILURE, "options 'U' or 'Z' with 'p' are mutually exclusive");
2089 argc -= optind;
2090 argv += optind;
2091 if (argc != 1) {
2092 usermgmt_usage("usermod");
2093 }
2094 checkeuid();
2095 openlog("usermod", LOG_PID, LOG_USER);
2096 return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ?
2097 EXIT_SUCCESS : EXIT_FAILURE;
2098 }
2099
2100 int
userdel(int argc,char ** argv)2101 userdel(int argc, char **argv)
2102 {
2103 struct passwd *pwp;
2104 user_t u;
2105 int defaultfield;
2106 int rmhome;
2107 int bigD;
2108 int c;
2109
2110 memset(&u, 0, sizeof(u));
2111 read_defaults(&u);
2112 defaultfield = bigD = rmhome = 0;
2113 while ((c = getopt(argc, argv, "Dp:rv")) != -1) {
2114 switch(c) {
2115 case 'D':
2116 bigD = 1;
2117 break;
2118 case 'p':
2119 defaultfield = 1;
2120 u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
2121 (strcmp(optarg, "yes") == 0) ? 1 :
2122 strtonum(optarg, INT_MIN, INT_MAX, NULL);
2123 break;
2124 case 'r':
2125 rmhome = 1;
2126 break;
2127 case 'v':
2128 verbose = 1;
2129 break;
2130 default:
2131 usermgmt_usage("userdel");
2132 }
2133 }
2134 if (bigD) {
2135 if (defaultfield) {
2136 checkeuid();
2137 return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
2138 }
2139 printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
2140 return EXIT_SUCCESS;
2141 }
2142 argc -= optind;
2143 argv += optind;
2144 if (argc != 1) {
2145 usermgmt_usage("userdel");
2146 }
2147
2148 if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
2149 NULL) == -1)
2150 err(1, "pledge");
2151
2152 checkeuid();
2153 if ((pwp = getpwnam(*argv)) == NULL) {
2154 warnx("No such user `%s'", *argv);
2155 return EXIT_FAILURE;
2156 }
2157 if (rmhome)
2158 (void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir);
2159 if (u.u_preserve) {
2160 u.u_flags |= F_SHELL;
2161 strsave(&u.u_shell, NOLOGIN);
2162 strsave(&u.u_password, "*");
2163 u.u_flags |= F_PASSWORD;
2164 openlog("userdel", LOG_PID, LOG_USER);
2165 return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
2166 }
2167 if (!rm_user_from_groups(*argv)) {
2168 return 0;
2169 }
2170 openlog("userdel", LOG_PID, LOG_USER);
2171 return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
2172 }
2173
2174 /* add a group */
2175 int
groupadd(int argc,char ** argv)2176 groupadd(int argc, char **argv)
2177 {
2178 int dupgid;
2179 int gid;
2180 int c;
2181 const char *errstr;
2182
2183 gid = -1;
2184 dupgid = 0;
2185 while ((c = getopt(argc, argv, "g:ov")) != -1) {
2186 switch(c) {
2187 case 'g':
2188 gid = strtonum(optarg, -1, GID_MAX, &errstr);
2189 if (errstr != NULL) {
2190 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2191 }
2192 break;
2193 case 'o':
2194 dupgid = 1;
2195 break;
2196 case 'v':
2197 verbose = 1;
2198 break;
2199 default:
2200 usermgmt_usage("groupadd");
2201 }
2202 }
2203 argc -= optind;
2204 argv += optind;
2205 if (argc != 1) {
2206 usermgmt_usage("groupadd");
2207 }
2208
2209 if (pledge("stdio rpath wpath cpath fattr flock getpw", NULL) == -1)
2210 err(1, "pledge");
2211
2212 checkeuid();
2213 if (!valid_group(*argv)) {
2214 errx(EXIT_FAILURE, "invalid group name `%s'", *argv);
2215 }
2216 if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
2217 errx(EXIT_FAILURE, "can't add group: can't get next gid");
2218 }
2219 if (!dupgid && gid_exists((gid_t)gid)) {
2220 errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
2221 }
2222 openlog("groupadd", LOG_PID, LOG_USER);
2223 if (!creategid(*argv, gid, "")) {
2224 errx(EXIT_FAILURE, "can't add group: problems with %s file",
2225 _PATH_GROUP);
2226 }
2227 return EXIT_SUCCESS;
2228 }
2229
2230 /* remove a group */
2231 int
groupdel(int argc,char ** argv)2232 groupdel(int argc, char **argv)
2233 {
2234 int c;
2235
2236 while ((c = getopt(argc, argv, "v")) != -1) {
2237 switch(c) {
2238 case 'v':
2239 verbose = 1;
2240 break;
2241 default:
2242 usermgmt_usage("groupdel");
2243 }
2244 }
2245 argc -= optind;
2246 argv += optind;
2247 if (argc != 1) {
2248 usermgmt_usage("groupdel");
2249 }
2250 checkeuid();
2251 openlog("groupdel", LOG_PID, LOG_USER);
2252 if (!group_exists(*argv)) {
2253 warnx("No such group: `%s'", *argv);
2254 return EXIT_FAILURE;
2255 }
2256
2257 if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1)
2258 err(1, "pledge");
2259
2260 if (!modify_gid(*argv, NULL)) {
2261 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2262 }
2263 return EXIT_SUCCESS;
2264 }
2265
2266 /* modify a group */
2267 int
groupmod(int argc,char ** argv)2268 groupmod(int argc, char **argv)
2269 {
2270 struct group *grp;
2271 const char *errstr;
2272 char buf[LINE_MAX];
2273 char *newname;
2274 char **cpp;
2275 int dupgid;
2276 int gid;
2277 int cc;
2278 int c;
2279
2280 gid = -1;
2281 dupgid = 0;
2282 newname = NULL;
2283 while ((c = getopt(argc, argv, "g:n:ov")) != -1) {
2284 switch(c) {
2285 case 'g':
2286 gid = strtonum(optarg, -1, GID_MAX, &errstr);
2287 if (errstr != NULL) {
2288 errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2289 }
2290 break;
2291 case 'o':
2292 dupgid = 1;
2293 break;
2294 case 'n':
2295 strsave(&newname, optarg);
2296 break;
2297 case 'v':
2298 verbose = 1;
2299 break;
2300 default:
2301 usermgmt_usage("groupmod");
2302 }
2303 }
2304 argc -= optind;
2305 argv += optind;
2306 if (argc != 1) {
2307 usermgmt_usage("groupmod");
2308 }
2309 checkeuid();
2310 if (gid < 0 && newname == NULL) {
2311 errx(EXIT_FAILURE, "Nothing to change");
2312 }
2313 if (dupgid && gid < 0) {
2314 errx(EXIT_FAILURE, "Duplicate which gid?");
2315 }
2316 if ((grp = getgrnam(*argv)) == NULL) {
2317 errx(EXIT_FAILURE, "can't find group `%s' to modify", *argv);
2318 }
2319
2320 if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1)
2321 err(1, "pledge");
2322
2323 if (!is_local(*argv, _PATH_GROUP)) {
2324 errx(EXIT_FAILURE, "Group `%s' must be a local group", *argv);
2325 }
2326 if (newname != NULL && !valid_group(newname)) {
2327 errx(EXIT_FAILURE, "invalid group name `%s'", newname);
2328 }
2329 if ((cc = snprintf(buf, sizeof(buf), "%s:%s:%u:",
2330 (newname) ? newname : grp->gr_name, grp->gr_passwd,
2331 (gid < 0) ? grp->gr_gid : gid)) >= sizeof(buf) || cc < 0)
2332 errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2333
2334 for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2335 cc = strlcat(buf, *cpp, sizeof(buf)) + 1;
2336 if (cc >= sizeof(buf))
2337 errx(EXIT_FAILURE, "group `%s' entry too long",
2338 grp->gr_name);
2339 if (cpp[1] != NULL) {
2340 buf[cc - 1] = ',';
2341 buf[cc] = '\0';
2342 }
2343 }
2344 cc = strlcat(buf, "\n", sizeof(buf));
2345 if (cc >= sizeof(buf))
2346 errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2347
2348 openlog("groupmod", LOG_PID, LOG_USER);
2349 if (!modify_gid(*argv, buf))
2350 err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2351 return EXIT_SUCCESS;
2352 }
2353
2354 /* display user information */
2355 int
userinfo(int argc,char ** argv)2356 userinfo(int argc, char **argv)
2357 {
2358 struct passwd *pwp;
2359 struct group *grp;
2360 char **cpp;
2361 int exists;
2362 int i;
2363
2364 exists = 0;
2365 while ((i = getopt(argc, argv, "ev")) != -1) {
2366 switch(i) {
2367 case 'e':
2368 exists = 1;
2369 break;
2370 case 'v':
2371 verbose = 1;
2372 break;
2373 default:
2374 usermgmt_usage("userinfo");
2375 }
2376 }
2377 argc -= optind;
2378 argv += optind;
2379 if (argc != 1) {
2380 usermgmt_usage("userinfo");
2381 }
2382
2383 if (pledge("stdio getpw", NULL) == -1)
2384 err(1, "pledge");
2385
2386 pwp = find_user_info(*argv);
2387 if (exists) {
2388 exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE);
2389 }
2390 if (pwp == NULL) {
2391 errx(EXIT_FAILURE, "can't find user `%s'", *argv);
2392 }
2393 printf("login\t%s\n", pwp->pw_name);
2394 printf("passwd\t%s\n", pwp->pw_passwd);
2395 printf("uid\t%u\n", pwp->pw_uid);
2396 if ((grp = getgrgid(pwp->pw_gid)) == NULL)
2397 printf("groups\t%u", pwp->pw_gid);
2398 else
2399 printf("groups\t%s", grp->gr_name);
2400 while ((grp = getgrent()) != NULL) {
2401 for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2402 if (strcmp(*cpp, pwp->pw_name) == 0 &&
2403 grp->gr_gid != pwp->pw_gid)
2404 printf(" %s", grp->gr_name);
2405 }
2406 }
2407 fputc('\n', stdout);
2408 printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n");
2409 printf("class\t%s\n", pwp->pw_class);
2410 printf("gecos\t%s\n", pwp->pw_gecos);
2411 printf("dir\t%s\n", pwp->pw_dir);
2412 printf("shell\t%s\n", pwp->pw_shell);
2413 printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n");
2414 return EXIT_SUCCESS;
2415 }
2416
2417 /* display user information */
2418 int
groupinfo(int argc,char ** argv)2419 groupinfo(int argc, char **argv)
2420 {
2421 struct group *grp;
2422 char **cpp;
2423 int exists;
2424 int i;
2425
2426 exists = 0;
2427 while ((i = getopt(argc, argv, "ev")) != -1) {
2428 switch(i) {
2429 case 'e':
2430 exists = 1;
2431 break;
2432 case 'v':
2433 verbose = 1;
2434 break;
2435 default:
2436 usermgmt_usage("groupinfo");
2437 }
2438 }
2439 argc -= optind;
2440 argv += optind;
2441 if (argc != 1) {
2442 usermgmt_usage("groupinfo");
2443 }
2444
2445 if (pledge("stdio getpw", NULL) == -1)
2446 err(1, "pledge");
2447
2448 grp = find_group_info(*argv);
2449 if (exists) {
2450 exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE);
2451 }
2452 if (grp == NULL) {
2453 errx(EXIT_FAILURE, "can't find group `%s'", *argv);
2454 }
2455 printf("name\t%s\n", grp->gr_name);
2456 printf("passwd\t%s\n", grp->gr_passwd);
2457 printf("gid\t%u\n", grp->gr_gid);
2458 printf("members\t");
2459 for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2460 printf("%s ", *cpp);
2461 }
2462 fputc('\n', stdout);
2463 return EXIT_SUCCESS;
2464 }
2465