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