GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/user/user.c Lines: 222 1282 17.3 %
Date: 2017-11-07 Branches: 140 964 14.5 %

Line Branch Exec Source
1
/* $OpenBSD: user.c,v 1.120 2017/05/24 09:18:15 mestre 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
35
#include <ctype.h>
36
#include <dirent.h>
37
#include <err.h>
38
#include <errno.h>
39
#include <fcntl.h>
40
#include <grp.h>
41
#include <limits.h>
42
#include <login_cap.h>
43
#include <paths.h>
44
#include <pwd.h>
45
#include <stdarg.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
#define CONFFILE		"/etc/usermgmt.conf"
107
#define _PATH_NONEXISTENT	"/nonexistent"
108
109
#ifndef DEF_GROUP
110
#define DEF_GROUP	"=uid"
111
#endif
112
113
#ifndef DEF_BASEDIR
114
#define DEF_BASEDIR	"/home"
115
#endif
116
117
#ifndef DEF_SKELDIR
118
#define DEF_SKELDIR	"/etc/skel"
119
#endif
120
121
#ifndef DEF_SHELL
122
#define DEF_SHELL	_PATH_KSHELL
123
#endif
124
125
#ifndef DEF_COMMENT
126
#define DEF_COMMENT	""
127
#endif
128
129
#ifndef DEF_LOWUID
130
#define DEF_LOWUID	1000
131
#endif
132
133
#ifndef DEF_HIGHUID
134
#define DEF_HIGHUID	60000
135
#endif
136
137
#ifndef DEF_INACTIVE
138
#define DEF_INACTIVE	0
139
#endif
140
141
#ifndef DEF_EXPIRE
142
#define DEF_EXPIRE	NULL
143
#endif
144
145
#ifndef DEF_CLASS
146
#define DEF_CLASS	""
147
#endif
148
149
#ifndef WAITSECS
150
#define WAITSECS	10
151
#endif
152
153
#ifndef NOBODY_UID
154
#define NOBODY_UID	32767
155
#endif
156
157
/* some useful constants */
158
enum {
159
	MaxShellNameLen = 256,
160
	MaxFileNameLen = PATH_MAX,
161
	MaxUserNameLen = _PW_NAME_LEN,
162
	MaxCommandLen = 2048,
163
	PasswordLength = _PASSWORD_LEN,
164
	LowGid = DEF_LOWUID,
165
	HighGid = DEF_HIGHUID
166
};
167
168
/* Full paths of programs used here */
169
#define CHMOD		"/bin/chmod"
170
#define CHOWN		"/sbin/chown"
171
#define MKDIR		"/bin/mkdir"
172
#define MV		"/bin/mv"
173
#define NOLOGIN		"/sbin/nologin"
174
#define PAX		"/bin/pax"
175
#define RM		"/bin/rm"
176
177
#define UNSET_INACTIVE	"Null (unset)"
178
#define UNSET_EXPIRY	"Null (unset)"
179
180
static int adduser(char *, user_t *);
181
static int append_group(char *, int, const char **);
182
static int asystem(const char *, ...)
183
	__attribute__((__format__(__printf__, 1, 2)));
184
static int copydotfiles(char *, char *);
185
static int creategid(char *, gid_t, const char *);
186
static int getnextgid(uid_t *, uid_t, uid_t);
187
static int getnextuid(int, uid_t *, uid_t, uid_t);
188
static int is_local(char *, const char *);
189
static int modify_gid(char *, char *);
190
static int moduser(char *, char *, user_t *);
191
static int removehomedir(const char *, uid_t, const char *);
192
static int rm_user_from_groups(char *);
193
static int save_range(user_t *, char *);
194
static int scantime(time_t *, char *);
195
static int setdefaults(user_t *);
196
static int valid_class(char *);
197
static int valid_group(char *);
198
static int valid_login(char *);
199
static size_t expand_len(const char *, const char *);
200
static struct group *find_group_info(const char *);
201
static struct passwd *find_user_info(const char *);
202
static void checkeuid(void);
203
static void memsave(char **, const char *, size_t);
204
static void read_defaults(user_t *);
205
206
static int	verbose;
207
208
/* if *cpp is non-null, free it, then assign `n' chars of `s' to it */
209
static void
210
memsave(char **cpp, const char *s, size_t n)
211
{
212
32
	free(*cpp);
213
16
	if ((*cpp = calloc (n + 1, sizeof(char))) == NULL)
214
		err(1, NULL);
215
16
	memcpy(*cpp, s, n);
216
16
	(*cpp)[n] = '\0';
217
16
}
218
219
/* a replacement for system(3) */
220
static int
221
asystem(const char *fmt, ...)
222
{
223
	va_list	vp;
224
	char	buf[MaxCommandLen];
225
	int	ret;
226
227
	va_start(vp, fmt);
228
	(void) vsnprintf(buf, sizeof(buf), fmt, vp);
229
	va_end(vp);
230
	if (verbose) {
231
		printf("Command: %s\n", buf);
232
	}
233
	if ((ret = system(buf)) != 0) {
234
		warnx("[Warning] can't system `%s'", buf);
235
	}
236
	return ret;
237
}
238
239
/* remove a users home directory, returning 1 for success (ie, no problems encountered) */
240
static int
241
removehomedir(const char *user, uid_t uid, const char *dir)
242
{
243
	struct stat st;
244
245
	/* userid not root? */
246
	if (uid == 0) {
247
		warnx("Not deleting home directory `%s'; userid is 0", dir);
248
		return 0;
249
	}
250
251
	/* directory exists (and is a directory!) */
252
	if (stat(dir, &st) < 0) {
253
		warnx("Home directory `%s' doesn't exist", dir);
254
		return 0;
255
	}
256
	if (!S_ISDIR(st.st_mode)) {
257
		warnx("Home directory `%s' is not a directory", dir);
258
		return 0;
259
	}
260
261
	/* userid matches directory owner? */
262
	if (st.st_uid != uid) {
263
		warnx("User `%s' doesn't own directory `%s', not removed",
264
		    user, dir);
265
		return 0;
266
	}
267
268
	(void) seteuid(uid);
269
	/* we add the "|| true" to keep asystem() quiet if there is a non-zero exit status. */
270
	(void) asystem("%s -rf %s > /dev/null 2>&1 || true", RM, dir);
271
	(void) seteuid(0);
272
	if (rmdir(dir) < 0) {
273
		warnx("Unable to remove all files in `%s'", dir);
274
		return 0;
275
	}
276
	return 1;
277
}
278
279
/*
280
 * check that the effective uid is 0 - called from funcs which will
281
 * modify data and config files.
282
 */
283
static void
284
checkeuid(void)
285
{
286
4
	if (geteuid() != 0) {
287
		errx(EXIT_FAILURE, "Program must be run as root");
288
	}
289
2
}
290
291
/* copy any dot files into the user's home directory */
292
static int
293
copydotfiles(char *skeldir, char *dir)
294
{
295
	struct dirent	*dp;
296
	DIR		*dirp;
297
	int		n;
298
299
	if (*skeldir == '\0')
300
		return 0;
301
	if ((dirp = opendir(skeldir)) == NULL) {
302
		warn("can't open source . files dir `%s'", skeldir);
303
		return 0;
304
	}
305
	for (n = 0; (dp = readdir(dirp)) != NULL && n == 0 ; ) {
306
		if (strcmp(dp->d_name, ".") == 0 ||
307
		    strcmp(dp->d_name, "..") == 0) {
308
			continue;
309
		}
310
		n = 1;
311
	}
312
	(void) closedir(dirp);
313
	if (n == 0) {
314
		warnx("No \"dot\" initialisation files found");
315
	} else {
316
		(void) asystem("cd %s && %s -rw -pe %s . %s",
317
				skeldir, PAX, (verbose) ? "-v" : "", dir);
318
	}
319
	return n;
320
}
321
322
/* create a group entry with gid `gid' */
323
static int
324
creategid(char *group, gid_t gid, const char *name)
325
{
326
2
	struct stat	st;
327
	FILE		*from;
328
	FILE		*to;
329
	char		*buf;
330
1
	char		f[MaxFileNameLen];
331
	int		fd, ret;
332
	int		wroteit = 0;
333
1
	size_t		len;
334
335
1
	if (getgrnam(group) != NULL) {
336
		warnx("group `%s' already exists", group);
337
		return 0;
338
	}
339
1
	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
340
		warn("can't create gid for `%s': can't open `%s'", group,
341
		    _PATH_GROUP);
342
		return 0;
343
	}
344

3
	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
345
		warn("can't lock `%s'", _PATH_GROUP);
346
	}
347
3
	(void) fstat(fileno(from), &st);
348
1
	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
349
1
	if ((fd = mkstemp(f)) < 0) {
350
		warn("can't create gid: mkstemp failed");
351
		fclose(from);
352
		return 0;
353
	}
354
1
	if ((to = fdopen(fd, "w")) == NULL) {
355
		warn("can't create gid: fdopen `%s' failed", f);
356
		fclose(from);
357
		close(fd);
358
		unlink(f);
359
		return 0;
360
	}
361
86
	while ((buf = fgetln(from, &len)) != NULL && len > 0) {
362
		ret = 0;
363
85
		if (buf[0] == '+' && wroteit == 0) {
364
			ret = fprintf(to, "%s:*:%u:%s\n", group, gid, name);
365
			wroteit = 1;
366
		}
367

170
		if (ret == -1 ||
368
85
		    fprintf(to, "%*.*s", (int)len, (int)len, buf) != len) {
369
			warn("can't create gid: short write to `%s'", f);
370
			fclose(from);
371
			fclose(to);
372
			unlink(f);
373
			return 0;
374
		}
375
	}
376
	ret = 0;
377
1
	if (wroteit == 0)
378
1
		ret = fprintf(to, "%s:*:%u:%s\n", group, gid, name);
379
1
	fclose(from);
380
1
	if (fclose(to) == EOF || ret == -1) {
381
		warn("can't create gid: short write to `%s'", f);
382
		unlink(f);
383
		return 0;
384
	}
385
1
	if (rename(f, _PATH_GROUP) < 0) {
386
		warn("can't create gid: can't rename `%s' to `%s'", f,
387
		    _PATH_GROUP);
388
		unlink(f);
389
		return 0;
390
	}
391
1
	(void) chmod(_PATH_GROUP, st.st_mode & 0777);
392
1
	syslog(LOG_INFO, "new group added: name=%s, gid=%u", group, gid);
393
1
	return 1;
394
1
}
395
396
/* modify the group entry with name `group' to be newent */
397
static int
398
modify_gid(char *group, char *newent)
399
{
400
	struct stat	st;
401
	FILE		*from;
402
	FILE		*to;
403
	char		buf[LINE_MAX];
404
	char		f[MaxFileNameLen];
405
	char		*colon;
406
	int		groupc;
407
	int		entc;
408
	int		fd;
409
	int		cc;
410
411
	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
412
		warn("can't modify gid for `%s': can't open `%s'", group,
413
		    _PATH_GROUP);
414
		return 0;
415
	}
416
	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
417
		warn("can't lock `%s'", _PATH_GROUP);
418
	}
419
	(void) fstat(fileno(from), &st);
420
	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
421
	if ((fd = mkstemp(f)) < 0) {
422
		warn("can't modify gid: mkstemp failed");
423
		fclose(from);
424
		return 0;
425
	}
426
	if ((to = fdopen(fd, "w")) == NULL) {
427
		warn("can't modify gid: fdopen `%s' failed", f);
428
		fclose(from);
429
		close(fd);
430
		unlink(f);
431
		return 0;
432
	}
433
	groupc = strlen(group);
434
	while (fgets(buf, sizeof(buf), from) != NULL) {
435
		cc = strlen(buf);
436
		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
437
			while (fgetc(from) != '\n' && !feof(from))
438
				cc++;
439
			warnx("%s: line `%s' too long (%d bytes), skipping",
440
			    _PATH_GROUP, buf, cc);
441
			continue;
442
		}
443
		if ((colon = strchr(buf, ':')) == NULL) {
444
			/*
445
			 * The only valid entry with no column is the all-YP
446
			 * line.
447
			 */
448
			if (strcmp(buf, "+\n") != 0) {
449
				warnx("badly formed entry `%.*s'", cc - 1, buf);
450
				continue;
451
			}
452
		} else {
453
			entc = (int)(colon - buf);
454
			if (entc == groupc && strncmp(group, buf, entc) == 0) {
455
				if (newent == NULL) {
456
					continue;
457
				} else {
458
					cc = strlcpy(buf, newent, sizeof(buf));
459
					if (cc >= sizeof(buf)) {
460
						warnx("group `%s' entry too long",
461
						    newent);
462
						fclose(from);
463
						fclose(to);
464
						unlink(f);
465
						return (0);
466
					}
467
				}
468
			}
469
		}
470
		if (fwrite(buf, cc, 1, to) != 1) {
471
			warn("can't modify gid: short write to `%s'", f);
472
			fclose(from);
473
			fclose(to);
474
			unlink(f);
475
			return 0;
476
		}
477
	}
478
	fclose(from);
479
	if (fclose(to) == EOF) {
480
		warn("can't modify gid: short write to `%s'", f);
481
		unlink(f);
482
		return 0;
483
	}
484
	if (rename(f, _PATH_GROUP) < 0) {
485
		warn("can't modify gid: can't rename `%s' to `%s'", f, _PATH_GROUP);
486
		unlink(f);
487
		return 0;
488
	}
489
	(void) chmod(_PATH_GROUP, st.st_mode & 0777);
490
	if (newent == NULL) {
491
		syslog(LOG_INFO, "group deleted: name=%s", group);
492
	} else {
493
		syslog(LOG_INFO, "group information modified: name=%s", group);
494
	}
495
	return 1;
496
}
497
498
/* modify the group entries for all `groups', by adding `user' */
499
static int
500
append_group(char *user, int ngroups, const char **groups)
501
{
502
	struct group	*grp;
503
	struct passwd	*pwp;
504
	struct stat	st;
505
	FILE		*from;
506
	FILE		*to;
507
	char		buf[LINE_MAX];
508
	char		f[MaxFileNameLen];
509
	char		*colon;
510
	char		*ugid = NULL;
511
	int		fd;
512
	int		cc;
513
	int		i;
514
	int		j;
515
516
	if ((pwp = getpwnam(user))) {
517
		if ((ugid = group_from_gid(pwp->pw_gid, 1)) == NULL) {
518
			warnx("can't get primary group for user `%s'", user);
519
			return 0;
520
		}
521
	}
522
523
	for (i = 0 ; i < ngroups ; i++) {
524
		if ((grp = getgrnam(groups[i])) == NULL) {
525
			warnx("can't append group `%s' for user `%s'",
526
			    groups[i], user);
527
		} else {
528
			for (j = 0 ; grp->gr_mem[j] ; j++) {
529
				if (strcmp(user, grp->gr_mem[j]) == 0) {
530
					/* already in it */
531
					groups[i] = "";
532
				}
533
			}
534
		}
535
	}
536
	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
537
		warn("can't append group for `%s': can't open `%s'", user,
538
		    _PATH_GROUP);
539
		return 0;
540
	}
541
	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
542
		warn("can't lock `%s'", _PATH_GROUP);
543
	}
544
	(void) fstat(fileno(from), &st);
545
	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
546
	if ((fd = mkstemp(f)) < 0) {
547
		warn("can't append group: mkstemp failed");
548
		fclose(from);
549
		return 0;
550
	}
551
	if ((to = fdopen(fd, "w")) == NULL) {
552
		warn("can't append group: fdopen `%s' failed", f);
553
		fclose(from);
554
		close(fd);
555
		unlink(f);
556
		return 0;
557
	}
558
	while (fgets(buf, sizeof(buf), from) != NULL) {
559
		cc = strlen(buf);
560
		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
561
			while (fgetc(from) != '\n' && !feof(from))
562
				cc++;
563
			warnx("%s: line `%s' too long (%d bytes), skipping",
564
			    _PATH_GROUP, buf, cc);
565
			continue;
566
		}
567
		if ((colon = strchr(buf, ':')) == NULL) {
568
			warnx("badly formed entry `%s'", buf);
569
			continue;
570
		}
571
		for (i = 0 ; i < ngroups ; i++) {
572
			j = (int)(colon - buf);
573
			if (ugid) {
574
				if (strcmp(ugid, groups[i]) == 0) {
575
					/* user's primary group, no need to append */
576
					groups[i] = "";
577
				}
578
			}
579
			if (strncmp(groups[i], buf, j) == 0 &&
580
			    groups[i][j] == '\0') {
581
				while (isspace((unsigned char)buf[cc - 1]))
582
					cc--;
583
				buf[(j = cc)] = '\0';
584
				if (buf[strlen(buf) - 1] != ':')
585
					strlcat(buf, ",", sizeof(buf));
586
				cc = strlcat(buf, user, sizeof(buf)) + 1;
587
				if (cc >= sizeof(buf)) {
588
					warnx("Warning: group `%s' would "
589
					    "become too long, not modifying",
590
					    groups[i]);
591
					cc = j + 1;
592
				}
593
				buf[cc - 1] = '\n';
594
				buf[cc] = '\0';
595
			}
596
		}
597
		if (fwrite(buf, cc, 1, to) != 1) {
598
			warn("can't append group: short write to `%s'", f);
599
			fclose(from);
600
			fclose(to);
601
			unlink(f);
602
			return 0;
603
		}
604
	}
605
	fclose(from);
606
	if (fclose(to) == EOF) {
607
		warn("can't append group: short write to `%s'", f);
608
		unlink(f);
609
		return 0;
610
	}
611
	if (rename(f, _PATH_GROUP) < 0) {
612
		warn("can't append group: can't rename `%s' to `%s'", f, _PATH_GROUP);
613
		unlink(f);
614
		return 0;
615
	}
616
	(void) chmod(_PATH_GROUP, st.st_mode & 0777);
617
	return 1;
618
}
619
620
/* return 1 if `login' is a valid login name */
621
static int
622
valid_login(char *login_name)
623
{
624
	unsigned char	*cp;
625
626
	/* The first character cannot be a hyphen */
627
2
	if (*login_name == '-')
628
		return 0;
629
630
18
	for (cp = login_name ; *cp ; cp++) {
631
		/* We allow '$' as the last character for samba */
632

9
		if (!isalnum((unsigned char)*cp) && *cp != '.' &&
633

1
		    *cp != '_' && *cp != '-' &&
634
		    !(*cp == '$' && *(cp + 1) == '\0')) {
635
			return 0;
636
		}
637
	}
638
1
	if ((char *)cp - login_name > MaxUserNameLen)
639
		return 0;
640
1
	return 1;
641
1
}
642
643
/* return 1 if `group' is a valid group name */
644
static int
645
valid_group(char *group)
646
{
647
	unsigned char	*cp;
648
649
19
	for (cp = group ; *cp ; cp++) {
650

9
		if (!isalnum((unsigned char)*cp) && *cp != '.' &&
651
1
		    *cp != '_' && *cp != '-') {
652
			return 0;
653
		}
654
	}
655
1
	if ((char *)cp - group > MaxUserNameLen)
656
		return 0;
657
1
	return 1;
658
1
}
659
660
/* return 1 if `class' exists */
661
static int
662
valid_class(char *class)
663
{
664
	login_cap_t *lc;
665
666
2
	if ((lc = login_getclass(class)) != NULL)
667
1
		login_close(lc);
668
1
	return lc != NULL;
669
}
670
671
/* find the next gid in the range lo .. hi */
672
static int
673
getnextgid(uid_t *gidp, uid_t lo, uid_t hi)
674
{
675
	for (*gidp = lo ; *gidp < hi ; *gidp += 1) {
676
		if (getgrgid((gid_t)*gidp) == NULL) {
677
			return 1;
678
		}
679
	}
680
	return 0;
681
}
682
683
/* save a range of uids */
684
static int
685
save_range(user_t *up, char *cp)
686
{
687
	uid_t	from;
688
	uid_t	to;
689
	int	i;
690
691
	if (up->u_rc == up->u_rsize) {
692
		up->u_rsize *= 2;
693
		if ((up->u_rv = reallocarray(up->u_rv, up->u_rsize,
694
		    sizeof(range_t))) == NULL) {
695
			warn(NULL);
696
			return 0;
697
		}
698
	}
699
	if (up->u_rv && sscanf(cp, "%u..%u", &from, &to) == 2) {
700
		for (i = up->u_defrc ; i < up->u_rc ; i++) {
701
			if (up->u_rv[i].r_from == from && up->u_rv[i].r_to == to) {
702
				break;
703
			}
704
		}
705
		if (i == up->u_rc) {
706
			up->u_rv[up->u_rc].r_from = from;
707
			up->u_rv[up->u_rc].r_to = to;
708
			up->u_rc += 1;
709
		}
710
	} else {
711
		warnx("Bad range `%s'", cp);
712
		return 0;
713
	}
714
	return 1;
715
}
716
717
/* set the defaults in the defaults file */
718
static int
719
setdefaults(user_t *up)
720
{
721
	char	template[MaxFileNameLen];
722
	FILE	*fp;
723
	int	ret;
724
	int	fd;
725
	int	i;
726
727
	(void) snprintf(template, sizeof(template), "%s.XXXXXXXX", CONFFILE);
728
	if ((fd = mkstemp(template)) < 0) {
729
		warnx("can't mkstemp `%s' for writing", CONFFILE);
730
		return 0;
731
	}
732
	if ((fp = fdopen(fd, "w")) == NULL) {
733
		warn("can't fdopen `%s' for writing", CONFFILE);
734
		return 0;
735
	}
736
	ret = 1;
737
	if (fprintf(fp, "group\t\t%s\n", up->u_primgrp) <= 0 ||
738
	    fprintf(fp, "base_dir\t%s\n", up->u_basedir) <= 0 ||
739
	    fprintf(fp, "skel_dir\t%s\n", up->u_skeldir) <= 0 ||
740
	    fprintf(fp, "shell\t\t%s\n", up->u_shell) <= 0 ||
741
	    fprintf(fp, "class\t\t%s\n", up->u_class) <= 0 ||
742
	    fprintf(fp, "inactive\t%s\n", (up->u_inactive == NULL) ? UNSET_INACTIVE : up->u_inactive) <= 0 ||
743
	    fprintf(fp, "expire\t\t%s\n", (up->u_expire == NULL) ? UNSET_EXPIRY : up->u_expire) <= 0 ||
744
	    fprintf(fp, "preserve\t%s\n", (up->u_preserve == 0) ? "false" : "true") <= 0) {
745
		warn("can't write to `%s'", CONFFILE);
746
		ret = 0;
747
	}
748
	for (i = (up->u_defrc != up->u_rc) ? up->u_defrc : 0 ; i < up->u_rc ; i++) {
749
		if (fprintf(fp, "range\t\t%u..%u\n", up->u_rv[i].r_from, up->u_rv[i].r_to) <= 0) {
750
			warn("can't write to `%s'", CONFFILE);
751
			ret = 0;
752
		}
753
	}
754
	if (fclose(fp) == EOF) {
755
		warn("can't write to `%s'", CONFFILE);
756
		ret = 0;
757
	}
758
	if (ret) {
759
		ret = ((rename(template, CONFFILE) == 0) && (chmod(CONFFILE, 0644) == 0));
760
	}
761
	return ret;
762
}
763
764
/* read the defaults file */
765
static void
766
read_defaults(user_t *up)
767
{
768
2
	struct stat	st;
769
1
	size_t		lineno;
770
1
	size_t		len;
771
	FILE		*fp;
772
	unsigned char	*cp;
773
	unsigned char	*s;
774
775
1
	memsave(&up->u_primgrp, DEF_GROUP, strlen(DEF_GROUP));
776
1
	memsave(&up->u_basedir, DEF_BASEDIR, strlen(DEF_BASEDIR));
777
1
	memsave(&up->u_skeldir, DEF_SKELDIR, strlen(DEF_SKELDIR));
778
1
	memsave(&up->u_shell, DEF_SHELL, strlen(DEF_SHELL));
779
1
	memsave(&up->u_comment, DEF_COMMENT, strlen(DEF_COMMENT));
780
1
	memsave(&up->u_class, DEF_CLASS, strlen(DEF_CLASS));
781
1
	up->u_rsize = 16;
782
1
	up->u_defrc = 0;
783
1
	if ((up->u_rv = calloc(up->u_rsize, sizeof(range_t))) == NULL)
784
		err(1, NULL);
785
1
	up->u_inactive = DEF_INACTIVE;
786
1
	up->u_expire = DEF_EXPIRE;
787
1
	if ((fp = fopen(CONFFILE, "r")) == NULL) {
788
		if (stat(CONFFILE, &st) < 0 && !setdefaults(up)) {
789
			warn("can't create `%s' defaults file", CONFFILE);
790
		}
791
		fp = fopen(CONFFILE, "r");
792
	}
793
1
	if (fp != NULL) {
794
17
		while ((s = fparseln(fp, &len, &lineno, NULL, 0)) != NULL) {
795
8
			if (strncmp(s, "group", 5) == 0) {
796
6
				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
797
				}
798
1
				memsave(&up->u_primgrp, cp, strlen(cp));
799
8
			} else if (strncmp(s, "base_dir", 8) == 0) {
800
4
				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
801
				}
802
1
				memsave(&up->u_basedir, cp, strlen(cp));
803
7
			} else if (strncmp(s, "skel_dir", 8) == 0) {
804
4
				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
805
				}
806
1
				memsave(&up->u_skeldir, cp, strlen(cp));
807
6
			} else if (strncmp(s, "shell", 5) == 0) {
808
6
				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
809
				}
810
1
				memsave(&up->u_shell, cp, strlen(cp));
811
5
			} else if (strncmp(s, "password", 8) == 0) {
812
				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
813
				}
814
				memsave(&up->u_password, cp, strlen(cp));
815
4
			} else if (strncmp(s, "class", 5) == 0) {
816
6
				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
817
				}
818
1
				memsave(&up->u_class, cp, strlen(cp));
819
4
			} else if (strncmp(s, "inactive", 8) == 0) {
820
4
				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
821
				}
822
1
				if (strcmp(cp, UNSET_INACTIVE) == 0) {
823
1
					free(up->u_inactive);
824
1
					up->u_inactive = NULL;
825
1
				} else {
826
					memsave(&up->u_inactive, cp, strlen(cp));
827
				}
828
2
			} else if (strncmp(s, "range", 5) == 0) {
829
				for (cp = s + 5 ; isspace((unsigned char)*cp); cp++) {
830
				}
831
				(void) save_range(up, cp);
832
2
			} else if (strncmp(s, "preserve", 8) == 0) {
833
4
				for (cp = s + 8 ; isspace((unsigned char)*cp); cp++) {
834
				}
835
2
				up->u_preserve = (strncmp(cp, "true", 4) == 0) ? 1 :
836
2
						  (strncmp(cp, "yes", 3) == 0) ? 1 :
837
1
						   strtonum(cp, INT_MIN, INT_MAX, NULL);
838
2
			} else if (strncmp(s, "expire", 6) == 0) {
839
6
				for (cp = s + 6 ; isspace((unsigned char)*cp); cp++) {
840
				}
841
1
				if (strcmp(cp, UNSET_EXPIRY) == 0) {
842
1
					free(up->u_expire);
843
1
					up->u_expire = NULL;
844
1
				} else {
845
					memsave(&up->u_expire, cp, strlen(cp));
846
				}
847
			}
848
8
			free(s);
849
		}
850
1
		fclose(fp);
851
1
	}
852
1
	if (up->u_rc == 0) {
853
1
		up->u_rv[up->u_rc].r_from = DEF_LOWUID;
854
1
		up->u_rv[up->u_rc].r_to = DEF_HIGHUID;
855
1
		up->u_rc += 1;
856
1
	}
857
1
	up->u_defrc = up->u_rc;
858
1
}
859
860
/* return the next valid unused uid */
861
static int
862
getnextuid(int sync_uid_gid, uid_t *uid, uid_t low_uid, uid_t high_uid)
863
{
864
	for (*uid = low_uid ; *uid <= high_uid ; (*uid)++) {
865
		if (getpwuid((uid_t)(*uid)) == NULL && *uid != NOBODY_UID) {
866
			if (sync_uid_gid) {
867
				if (getgrgid((gid_t)(*uid)) == NULL) {
868
					return 1;
869
				}
870
			} else {
871
				return 1;
872
			}
873
		}
874
	}
875
	return 0;
876
}
877
878
/* look for a valid time, return 0 if it was specified but bad */
879
static int
880
scantime(time_t *tp, char *s)
881
{
882
4
	struct tm	tm;
883
884
2
	*tp = 0;
885
2
	if (s != NULL) {
886
		memset(&tm, 0, sizeof(tm));
887
		tm.tm_isdst = -1;
888
		if (strptime(s, "%c", &tm) != NULL) {
889
			*tp = mktime(&tm);
890
		} else if (strptime(s, "%B %d %Y", &tm) != NULL) {
891
			*tp = mktime(&tm);
892
		} else if (isdigit((unsigned char) s[0]) != 0) {
893
			*tp = (time_t)atoll(s);
894
		} else {
895
			return 0;
896
		}
897
	}
898
2
	return 1;
899
2
}
900
901
/* compute the extra length '&' expansion consumes */
902
static size_t
903
expand_len(const char *p, const char *username)
904
{
905
	size_t alen;
906
	size_t ulen;
907
908
2
	ulen = strlen(username);
909
28
	for (alen = 0; *p != '\0'; p++)
910
13
		if (*p == '&')
911
			alen += ulen - 1;
912
1
	return alen;
913
}
914
915
/* see if we can find out the user struct */
916
static struct passwd *
917
find_user_info(const char *name)
918
{
919
	struct passwd	*pwp;
920
	const char	*errstr;
921
	uid_t		uid;
922
923
	if ((pwp = getpwnam(name)) == NULL) {
924
		uid = strtonum(name, -1, UID_MAX, &errstr);
925
		if (errstr == NULL)
926
			pwp = getpwuid(uid);
927
	}
928
	return pwp;
929
}
930
931
/* see if we can find out the group struct */
932
static struct group *
933
find_group_info(const char *name)
934
{
935
	struct group	*grp;
936
2
	const char	*errstr;
937
	gid_t		gid;
938
939
1
	if ((grp = getgrnam(name)) == NULL) {
940
		gid = strtonum(name, -1, GID_MAX, &errstr);
941
		if (errstr == NULL)
942
			grp = getgrgid(gid);
943
	}
944
1
	return grp;
945
1
}
946
947
/* add a user */
948
static int
949
adduser(char *login_name, user_t *up)
950
{
951
	struct group	*grp;
952
2
	struct stat	st;
953
1
	time_t		expire;
954
1
	time_t		inactive;
955
1
	char		password[PasswordLength + 1];
956
1
	char		home[MaxFileNameLen];
957
1
	char		buf[LINE_MAX];
958
	int		sync_uid_gid;
959
	int		masterfd;
960
	int		ptmpfd;
961
	gid_t		gid;
962
	int		cc;
963
	int		i, yp = 0;
964
	FILE		*fp;
965
966
1
	if (!valid_login(login_name)) {
967
		errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
968
	}
969
1
	if (!valid_class(up->u_class)) {
970
		errx(EXIT_FAILURE, "No such login class `%s'", up->u_class);
971
	}
972
1
	if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
973
		err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
974
	}
975
1
	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
976
		err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
977
	}
978
1
	pw_init();
979
1
	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
980
		int saved_errno = errno;
981
		close(masterfd);
982
		errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
983
	}
984
1
	if ((fp = fdopen(masterfd, "r")) == NULL) {
985
		int saved_errno = errno;
986
		close(masterfd);
987
		close(ptmpfd);
988
		pw_abort();
989
		errc(EXIT_FAILURE, saved_errno,
990
		    "can't fdopen `%s' for reading", _PATH_MASTERPASSWD);
991
	}
992
67
	while (fgets(buf, sizeof(buf), fp) != NULL) {
993
66
		cc = strlen(buf);
994
		/*
995
		 * Stop copying the file at the yp entry; we want to
996
		 * put the new user before it, and preserve entries
997
		 * after the yp entry.
998
		 */
999

132
		if (cc > 1 && buf[0] == '+' && buf[1] == ':') {
1000
			yp = 1;
1001
			break;
1002
		}
1003
66
		if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1004
			int saved_errno = errno;
1005
			fclose(fp);
1006
			close(ptmpfd);
1007
			pw_abort();
1008
			errc(EXIT_FAILURE, saved_errno,
1009
			    "short write to /etc/ptmp (not %d chars)", cc);
1010
		}
1011
	}
1012

2
	if (ferror(fp)) {
1013
		int saved_errno = errno;
1014
		fclose(fp);
1015
		close(ptmpfd);
1016
		pw_abort();
1017
		errc(EXIT_FAILURE, saved_errno, "read error on %s",
1018
		    _PATH_MASTERPASSWD);
1019
	}
1020
	/* if no uid was specified, get next one in [low_uid..high_uid] range */
1021
1
	sync_uid_gid = (strcmp(up->u_primgrp, "=uid") == 0);
1022
1
	if (up->u_uid == UID_MAX) {
1023
		int got_id = 0;
1024
1025
		/*
1026
		 * Look for a free UID in the command line ranges (if any).
1027
		 * These start after the ranges specified in the config file.
1028
		 */
1029
		for (i = up->u_defrc; got_id == 0 && i < up->u_rc ; i++) {
1030
			got_id = getnextuid(sync_uid_gid, &up->u_uid,
1031
			    up->u_rv[i].r_from, up->u_rv[i].r_to);
1032
		}
1033
		/*
1034
		 * If there were no free UIDs in the command line ranges,
1035
		 * try the ranges from the config file (there will always
1036
		 * be at least one default).
1037
		 */
1038
		if (got_id == 0) {
1039
			for (i = 0; got_id == 0 && i < up->u_defrc; i++) {
1040
				got_id = getnextuid(sync_uid_gid, &up->u_uid,
1041
				    up->u_rv[i].r_from, up->u_rv[i].r_to);
1042
			}
1043
		}
1044
		if (got_id == 0) {
1045
			close(ptmpfd);
1046
			pw_abort();
1047
			errx(EXIT_FAILURE, "can't get next uid for %u", up->u_uid);
1048
		}
1049
	}
1050
	/* check uid isn't already allocated */
1051

2
	if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
1052
		close(ptmpfd);
1053
		pw_abort();
1054
		errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1055
	}
1056
	/* if -g=uid was specified, check gid is unused */
1057
1
	if (sync_uid_gid) {
1058
		if (getgrgid((gid_t)(up->u_uid)) != NULL) {
1059
			close(ptmpfd);
1060
			pw_abort();
1061
			errx(EXIT_FAILURE, "gid %u is already in use", up->u_uid);
1062
		}
1063
		gid = up->u_uid;
1064
	} else {
1065
1
		if ((grp = find_group_info(up->u_primgrp)) == NULL) {
1066
			close(ptmpfd);
1067
			pw_abort();
1068
			errx(EXIT_FAILURE, "group %s not found", up->u_primgrp);
1069
		}
1070
1
		gid = grp->gr_gid;
1071
	}
1072
	/* check name isn't already in use */
1073

2
	if (!(up->u_flags & F_DUPUID) && getpwnam(login_name) != NULL) {
1074
		close(ptmpfd);
1075
		pw_abort();
1076
		errx(EXIT_FAILURE, "already a `%s' user", login_name);
1077
	}
1078
1
	if (up->u_flags & F_HOMEDIR) {
1079
1
		if (strlcpy(home, up->u_home, sizeof(home)) >= sizeof(home)) {
1080
			close(ptmpfd);
1081
			pw_abort();
1082
			errx(EXIT_FAILURE, "home directory `%s' too long",
1083
			    up->u_home);
1084
		}
1085
	} else {
1086
		/* if home directory hasn't been given, make it up */
1087
		if (snprintf(home, sizeof(home), "%s/%s", up->u_basedir,
1088
		    login_name) >= sizeof(home)) {
1089
			close(ptmpfd);
1090
			pw_abort();
1091
			errx(EXIT_FAILURE, "home directory `%s/%s' too long",
1092
			    up->u_basedir, login_name);
1093
		}
1094
	}
1095
1
	if (!scantime(&inactive, up->u_inactive)) {
1096
		warnx("Warning: inactive time `%s' invalid, password expiry off",
1097
				up->u_inactive);
1098
	}
1099
1
	if (!scantime(&expire, up->u_expire)) {
1100
		warnx("Warning: expire time `%s' invalid, account expiry off",
1101
				up->u_expire);
1102
	}
1103

3
	if (lstat(home, &st) < 0 && !(up->u_flags & F_MKDIR) &&
1104
1
	    strcmp(home, _PATH_NONEXISTENT) != 0) {
1105
		warnx("Warning: home directory `%s' doesn't exist, and -m was"
1106
		    " not specified", home);
1107
	}
1108
2
	(void) strlcpy(password, up->u_password ? up->u_password : "*",
1109
	    sizeof(password));
1110
1
	cc = snprintf(buf, sizeof(buf), "%s:%s:%u:%u:%s:%lld:%lld:%s:%s:%s\n",
1111
	    login_name,
1112
	    password,
1113
1
	    up->u_uid,
1114
	    gid,
1115
1
	    up->u_class,
1116
1
	    (long long) inactive,
1117
1
	    (long long) expire,
1118
1
	    up->u_comment,
1119
	    home,
1120
1
	    up->u_shell);
1121

2
	if (cc >= sizeof(buf) || cc < 0 ||
1122
1
	    cc + expand_len(up->u_comment, login_name) >= 1023) {
1123
		close(ptmpfd);
1124
		pw_abort();
1125
		errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1126
	}
1127
1
	if (write(ptmpfd, buf, (size_t) cc) != cc) {
1128
		int saved_errno = errno;
1129
		close(ptmpfd);
1130
		pw_abort();
1131
		errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1132
	}
1133
1
	if (yp) {
1134
		/* put back the + line */
1135
		cc = snprintf(buf, sizeof(buf), "+:*::::::::\n");
1136
		if (cc == -1 || cc >= sizeof(buf)) {
1137
			close(ptmpfd);
1138
			pw_abort();
1139
			errx(EXIT_FAILURE, "can't add `%s', line too long", buf);
1140
		}
1141
		if (write(ptmpfd, buf, (size_t) cc) != cc) {
1142
			int saved_errno = errno;
1143
			close(ptmpfd);
1144
			pw_abort();
1145
			errc(EXIT_FAILURE, saved_errno, "can't add `%s'", buf);
1146
		}
1147
		/* copy the entries following it, if any */
1148
		while (fgets(buf, sizeof(buf), fp) != NULL) {
1149
			cc = strlen(buf);
1150
			if (write(ptmpfd, buf, (size_t)(cc)) != cc) {
1151
				int saved_errno = errno;
1152
				fclose(fp);
1153
				close(ptmpfd);
1154
				pw_abort();
1155
				errc(EXIT_FAILURE, saved_errno,
1156
				    "short write to /etc/ptmp (not %d chars)",
1157
				    cc);
1158
			}
1159
		}
1160
		if (ferror(fp)) {
1161
			int saved_errno = errno;
1162
			fclose(fp);
1163
			close(ptmpfd);
1164
			pw_abort();
1165
			errc(EXIT_FAILURE, saved_errno, "read error on %s",
1166
			    _PATH_MASTERPASSWD);
1167
		}
1168
	}
1169
1
	if (up->u_flags & F_MKDIR) {
1170
		if (lstat(home, &st) == 0) {
1171
			close(ptmpfd);
1172
			pw_abort();
1173
			errx(EXIT_FAILURE, "home directory `%s' already exists",
1174
			    home);
1175
		} else {
1176
			if (asystem("%s -p %s", MKDIR, home) != 0) {
1177
				int saved_errno = errno;
1178
				close(ptmpfd);
1179
				pw_abort();
1180
				errc(EXIT_FAILURE, saved_errno,
1181
				    "can't mkdir `%s'", home);
1182
			}
1183
			(void) copydotfiles(up->u_skeldir, home);
1184
			(void) asystem("%s -R -P %u:%u %s", CHOWN, up->u_uid,
1185
			    gid, home);
1186
			(void) asystem("%s -R u+w %s", CHMOD, home);
1187
		}
1188
	}
1189

1
	if (strcmp(up->u_primgrp, "=uid") == 0 &&
1190
	    getgrnam(login_name) == NULL &&
1191
	    !creategid(login_name, gid, "")) {
1192
		close(ptmpfd);
1193
		pw_abort();
1194
		errx(EXIT_FAILURE, "can't create gid %u for login name %s",
1195
		    gid, login_name);
1196
	}
1197

1
	if (up->u_groupc > 0 && !append_group(login_name, up->u_groupc, up->u_groupv)) {
1198
		close(ptmpfd);
1199
		pw_abort();
1200
		errx(EXIT_FAILURE, "can't append `%s' to new groups", login_name);
1201
	}
1202
1
	fclose(fp);
1203
1
	close(ptmpfd);
1204
1
	if (pw_mkdb(yp ? NULL : login_name, 0) < 0) {
1205
		pw_abort();
1206
		err(EXIT_FAILURE, "pw_mkdb failed");
1207
	}
1208
1
	syslog(LOG_INFO, "new user added: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1209
1
		login_name, up->u_uid, gid, home, up->u_shell);
1210
1
	return 1;
1211
1
}
1212
1213
/* remove a user from the groups file */
1214
static int
1215
rm_user_from_groups(char *login_name)
1216
{
1217
	struct stat	st;
1218
	size_t		login_len;
1219
	FILE		*from;
1220
	FILE		*to;
1221
	char		buf[LINE_MAX];
1222
	char		f[MaxFileNameLen];
1223
	char		*cp, *ep;
1224
	int		fd;
1225
	int		cc;
1226
1227
	login_len = strlen(login_name);
1228
	if ((from = fopen(_PATH_GROUP, "r")) == NULL) {
1229
		warn("can't remove gid for `%s': can't open `%s'",
1230
		    login_name, _PATH_GROUP);
1231
		return 0;
1232
	}
1233
	if (flock(fileno(from), LOCK_EX | LOCK_NB) < 0) {
1234
		warn("can't lock `%s'", _PATH_GROUP);
1235
	}
1236
	(void) fstat(fileno(from), &st);
1237
	(void) snprintf(f, sizeof(f), "%s.XXXXXXXX", _PATH_GROUP);
1238
	if ((fd = mkstemp(f)) < 0) {
1239
		warn("can't remove gid for `%s': mkstemp failed", login_name);
1240
		fclose(from);
1241
		return 0;
1242
	}
1243
	if ((to = fdopen(fd, "w")) == NULL) {
1244
		warn("can't remove gid for `%s': fdopen `%s' failed",
1245
		    login_name, f);
1246
		fclose(from);
1247
		close(fd);
1248
		unlink(f);
1249
		return 0;
1250
	}
1251
	while (fgets(buf, sizeof(buf), from) != NULL) {
1252
		cc = strlen(buf);
1253
		if (cc > 0 && buf[cc - 1] != '\n' && !feof(from)) {
1254
			while (fgetc(from) != '\n' && !feof(from))
1255
				cc++;
1256
			warnx("%s: line `%s' too long (%d bytes), skipping",
1257
			    _PATH_GROUP, buf, cc);
1258
			continue;
1259
		}
1260
1261
		/* Break out the group list. */
1262
		for (cp = buf, cc = 0; *cp != '\0' && cc < 3; cp++) {
1263
			if (*cp == ':')
1264
				cc++;
1265
		}
1266
		if (cc != 3) {
1267
			buf[strcspn(buf, "\n")] = '\0';
1268
			warnx("Malformed entry `%s'. Skipping", buf);
1269
			continue;
1270
		}
1271
		while ((cp = strstr(cp, login_name)) != NULL) {
1272
			if ((cp[-1] == ':' || cp[-1] == ',') &&
1273
			    (cp[login_len] == ',' || cp[login_len] == '\n')) {
1274
				ep = cp + login_len;
1275
				if (cp[login_len] == ',')
1276
					ep++;
1277
				else if (cp[-1] == ',')
1278
					cp--;
1279
				memmove(cp, ep, strlen(ep) + 1);
1280
			} else {
1281
				if ((cp = strchr(cp, ',')) == NULL)
1282
					break;
1283
				cp++;
1284
			}
1285
		}
1286
		if (fwrite(buf, strlen(buf), 1, to) != 1) {
1287
			warn("can't remove gid for `%s': short write to `%s'",
1288
			    login_name, f);
1289
			fclose(from);
1290
			fclose(to);
1291
			unlink(f);
1292
			return 0;
1293
		}
1294
	}
1295
	(void) fchmod(fileno(to), st.st_mode & 0777);
1296
	fclose(from);
1297
	if (fclose(to) == EOF) {
1298
		warn("can't remove gid for `%s': short write to `%s'",
1299
		    login_name, f);
1300
		unlink(f);
1301
		return 0;
1302
	}
1303
	if (rename(f, _PATH_GROUP) < 0) {
1304
		warn("can't remove gid for `%s': can't rename `%s' to `%s'",
1305
		    login_name, f, _PATH_GROUP);
1306
		unlink(f);
1307
		return 0;
1308
	}
1309
	return 1;
1310
}
1311
1312
/* check that the user or group is local, not from YP/NIS */
1313
static int
1314
is_local(char *name, const char *file)
1315
{
1316
	FILE		*fp;
1317
	char		buf[LINE_MAX];
1318
	size_t		len;
1319
	int		ret;
1320
	int		cc;
1321
1322
	if ((fp = fopen(file, "r")) == NULL) {
1323
		err(EXIT_FAILURE, "can't open `%s'", file);
1324
	}
1325
	len = strlen(name);
1326
	for (ret = 0 ; fgets(buf, sizeof(buf), fp) != NULL ; ) {
1327
		cc = strlen(buf);
1328
		if (cc > 0 && buf[cc - 1] != '\n' && !feof(fp)) {
1329
			while (fgetc(fp) != '\n' && !feof(fp))
1330
				cc++;
1331
			warnx("%s: line `%s' too long (%d bytes), skipping",
1332
			    file, buf, cc);
1333
			continue;
1334
		}
1335
		if (strncmp(buf, name, len) == 0 && buf[len] == ':') {
1336
			ret = 1;
1337
			break;
1338
		}
1339
	}
1340
	fclose(fp);
1341
	return ret;
1342
}
1343
1344
/* modify a user */
1345
static int
1346
moduser(char *login_name, char *newlogin, user_t *up)
1347
{
1348
	struct passwd	*pwp = NULL;
1349
	struct group	*grp;
1350
	const char	*homedir;
1351
	char		buf[LINE_MAX];
1352
	char		acctlock_str[] = "-";
1353
	char		pwlock_str[] = "*";
1354
	char		pw_len[PasswordLength + 1];
1355
	char		shell_len[MaxShellNameLen];
1356
	char		*shell_last_char;
1357
	size_t		colonc, loginc;
1358
	size_t		cc;
1359
	size_t		shell_buf;
1360
	FILE		*master;
1361
	char		newdir[MaxFileNameLen];
1362
	char		*colon;
1363
	char		*pw_tmp = NULL;
1364
	char		*shell_tmp = NULL;
1365
	int		len;
1366
	int		locked = 0;
1367
	int		unlocked = 0;
1368
	int		masterfd;
1369
	int		ptmpfd;
1370
	int		rval;
1371
	int		i;
1372
1373
	if (!valid_login(newlogin)) {
1374
		errx(EXIT_FAILURE, "`%s' is not a valid login name", login_name);
1375
	}
1376
	if ((pwp = getpwnam_shadow(login_name)) == NULL) {
1377
		errx(EXIT_FAILURE, "No such user `%s'", login_name);
1378
	}
1379
	if (up != NULL) {
1380
		if ((*pwp->pw_passwd != '\0') &&
1381
		    (up->u_flags & F_PASSWORD) == 0) {
1382
			up->u_flags |= F_PASSWORD;
1383
			memsave(&up->u_password, pwp->pw_passwd,
1384
			    strlen(pwp->pw_passwd));
1385
			explicit_bzero(pwp->pw_passwd, strlen(pwp->pw_passwd));
1386
		}
1387
	}
1388
	endpwent();
1389
1390
	if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
1391
	    NULL) == -1)
1392
		err(1, "pledge");
1393
1394
	if (!is_local(login_name, _PATH_MASTERPASSWD)) {
1395
		errx(EXIT_FAILURE, "User `%s' must be a local user", login_name);
1396
	}
1397
	if (up != NULL) {
1398
		if ((up->u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)) && (pwp->pw_uid < 1000))
1399
			errx(EXIT_FAILURE, "(un)locking is not supported for the `%s' account", pwp->pw_name);
1400
	}
1401
	/* keep dir name in case we need it for '-m' */
1402
	homedir = pwp->pw_dir;
1403
1404
	/* get the last char of the shell in case we need it for '-U' or '-Z' */
1405
	shell_last_char = pwp->pw_shell+strlen(pwp->pw_shell) - 1;
1406
1407
	if ((masterfd = open(_PATH_MASTERPASSWD, O_RDONLY)) < 0) {
1408
		err(EXIT_FAILURE, "can't open `%s'", _PATH_MASTERPASSWD);
1409
	}
1410
	if (flock(masterfd, LOCK_EX | LOCK_NB) < 0) {
1411
		err(EXIT_FAILURE, "can't lock `%s'", _PATH_MASTERPASSWD);
1412
	}
1413
	pw_init();
1414
	if ((ptmpfd = pw_lock(WAITSECS)) < 0) {
1415
		int saved_errno = errno;
1416
		close(masterfd);
1417
		errc(EXIT_FAILURE, saved_errno, "can't obtain pw_lock");
1418
	}
1419
	if ((master = fdopen(masterfd, "r")) == NULL) {
1420
		int saved_errno = errno;
1421
		close(masterfd);
1422
		close(ptmpfd);
1423
		pw_abort();
1424
		errc(EXIT_FAILURE, saved_errno, "can't fdopen fd for %s",
1425
		    _PATH_MASTERPASSWD);
1426
	}
1427
	if (up != NULL) {
1428
		if (up->u_flags & F_USERNAME) {
1429
			/* if changing name, check new name isn't already in use */
1430
			if (strcmp(login_name, newlogin) != 0 && getpwnam(newlogin) != NULL) {
1431
				close(ptmpfd);
1432
				pw_abort();
1433
				errx(EXIT_FAILURE, "already a `%s' user", newlogin);
1434
			}
1435
			pwp->pw_name = newlogin;
1436
1437
			/*
1438
			 * Provide a new directory name in case the
1439
			 * home directory is to be moved.
1440
			 */
1441
			if (up->u_flags & F_MKDIR) {
1442
				(void) snprintf(newdir, sizeof(newdir),
1443
				    "%s/%s", up->u_basedir, newlogin);
1444
				pwp->pw_dir = newdir;
1445
			}
1446
		}
1447
		if (up->u_flags & F_PASSWORD) {
1448
			if (up->u_password != NULL)
1449
				pwp->pw_passwd = up->u_password;
1450
		}
1451
		if (up->u_flags & F_ACCTLOCK) {
1452
			/* lock the account */
1453
			if (*shell_last_char != *acctlock_str) {
1454
				shell_tmp = malloc(strlen(pwp->pw_shell) + sizeof(acctlock_str));
1455
				if (shell_tmp == NULL) {
1456
					close(ptmpfd);
1457
					pw_abort();
1458
					errx(EXIT_FAILURE, "account lock: cannot allocate memory");
1459
				}
1460
				strlcpy(shell_tmp, pwp->pw_shell, sizeof(shell_len));
1461
				strlcat(shell_tmp, acctlock_str, sizeof(shell_len));
1462
				pwp->pw_shell = shell_tmp;
1463
			} else {
1464
				locked++;
1465
			}
1466
			/* lock the password */
1467
			if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) != 0) {
1468
				pw_tmp = malloc(strlen(pwp->pw_passwd) + sizeof(pwlock_str));
1469
				if (pw_tmp == NULL) {
1470
					close(ptmpfd);
1471
					pw_abort();
1472
					errx(EXIT_FAILURE, "password lock: cannot allocate memory");
1473
				}
1474
				strlcpy(pw_tmp, pwlock_str, sizeof(pw_len));
1475
				strlcat(pw_tmp, pwp->pw_passwd, sizeof(pw_len));
1476
				pwp->pw_passwd = pw_tmp;
1477
			} else {
1478
				locked++;
1479
			}
1480
1481
			if (locked > 1)
1482
				warnx("account `%s' is already locked", pwp->pw_name);
1483
		}
1484
		if (up->u_flags & F_ACCTUNLOCK) {
1485
			/* unlock the password */
1486
			if (strcmp(pwp->pw_passwd, pwlock_str) != 0 &&
1487
			    strcmp(pwp->pw_passwd, "*************") != 0) {
1488
				if (strncmp(pwp->pw_passwd, pwlock_str, sizeof(pwlock_str)-1) == 0) {
1489
					pwp->pw_passwd += sizeof(pwlock_str)-1;
1490
				} else {
1491
					unlocked++;
1492
				}
1493
			} else {
1494
				warnx("account `%s' has no password: cannot fully unlock", pwp->pw_name);
1495
			}
1496
			/* unlock the account */
1497
			if (*shell_last_char == *acctlock_str) {
1498
				shell_buf = strlen(pwp->pw_shell) + 2 - sizeof(acctlock_str);
1499
				shell_tmp = malloc(shell_buf);
1500
				if (shell_tmp == NULL) {
1501
					close(ptmpfd);
1502
					pw_abort();
1503
					errx(EXIT_FAILURE, "unlock: cannot allocate memory");
1504
				}
1505
				strlcpy(shell_tmp, pwp->pw_shell, shell_buf);
1506
				pwp->pw_shell = shell_tmp;
1507
			} else {
1508
				unlocked++;
1509
			}
1510
1511
			if (unlocked > 1)
1512
				warnx("account `%s' is not locked", pwp->pw_name);
1513
		}
1514
		if (up->u_flags & F_UID) {
1515
			/* check uid isn't already allocated */
1516
			if (!(up->u_flags & F_DUPUID) && getpwuid((uid_t)(up->u_uid)) != NULL) {
1517
				close(ptmpfd);
1518
				pw_abort();
1519
				errx(EXIT_FAILURE, "uid %u is already in use", up->u_uid);
1520
			}
1521
			pwp->pw_uid = up->u_uid;
1522
		}
1523
		if (up->u_flags & F_GROUP) {
1524
			/* if -g=uid was specified, check gid is unused */
1525
			if (strcmp(up->u_primgrp, "=uid") == 0) {
1526
				if (getgrgid((gid_t)(pwp->pw_uid)) != NULL) {
1527
					close(ptmpfd);
1528
					pw_abort();
1529
					errx(EXIT_FAILURE, "gid %u is already "
1530
					    "in use", pwp->pw_uid);
1531
				}
1532
				pwp->pw_gid = pwp->pw_uid;
1533
				if (!creategid(newlogin, pwp->pw_gid, "")) {
1534
					close(ptmpfd);
1535
					pw_abort();
1536
					errx(EXIT_FAILURE, "could not create "
1537
					    "group %s with gid %u", newlogin,
1538
					    pwp->pw_gid);
1539
				}
1540
			} else {
1541
				if ((grp = find_group_info(up->u_primgrp)) == NULL) {
1542
					close(ptmpfd);
1543
					pw_abort();
1544
					errx(EXIT_FAILURE, "group %s not found",
1545
					    up->u_primgrp);
1546
				}
1547
				pwp->pw_gid = grp->gr_gid;
1548
			}
1549
		}
1550
		if (up->u_flags & F_INACTIVE) {
1551
			if (!scantime(&pwp->pw_change, up->u_inactive)) {
1552
				warnx("Warning: inactive time `%s' invalid, password expiry off",
1553
					up->u_inactive);
1554
			}
1555
		}
1556
		if (up->u_flags & F_EXPIRE) {
1557
			if (!scantime(&pwp->pw_expire, up->u_expire)) {
1558
				warnx("Warning: expire time `%s' invalid, account expiry off",
1559
					up->u_expire);
1560
			}
1561
		}
1562
		if (up->u_flags & F_COMMENT)
1563
			pwp->pw_gecos = up->u_comment;
1564
		if (up->u_flags & F_HOMEDIR)
1565
			pwp->pw_dir = up->u_home;
1566
		if (up->u_flags & F_SHELL)
1567
			pwp->pw_shell = up->u_shell;
1568
		if (up->u_flags & F_CLASS) {
1569
			if (!valid_class(up->u_class)) {
1570
				close(ptmpfd);
1571
				pw_abort();
1572
				errx(EXIT_FAILURE,
1573
				    "No such login class `%s'", up->u_class);
1574
			}
1575
			pwp->pw_class = up->u_class;
1576
		}
1577
	}
1578
	loginc = strlen(login_name);
1579
	while (fgets(buf, sizeof(buf), master) != NULL) {
1580
		if ((colon = strchr(buf, ':')) == NULL) {
1581
			warnx("Malformed entry `%s'. Skipping", buf);
1582
			continue;
1583
		}
1584
		colonc = (size_t)(colon - buf);
1585
		if (strncmp(login_name, buf, loginc) == 0 && loginc == colonc) {
1586
			if (up != NULL) {
1587
				if ((len = snprintf(buf, sizeof(buf),
1588
				    "%s:%s:%u:%u:%s:%lld:%lld:%s:%s:%s\n",
1589
				    newlogin,
1590
				    pwp->pw_passwd,
1591
				    pwp->pw_uid,
1592
				    pwp->pw_gid,
1593
				    pwp->pw_class,
1594
				    (long long)pwp->pw_change,
1595
				    (long long)pwp->pw_expire,
1596
				    pwp->pw_gecos,
1597
				    pwp->pw_dir,
1598
				    pwp->pw_shell)) >= sizeof(buf) || len < 0 ||
1599
				    len + expand_len(pwp->pw_gecos, newlogin)
1600
				    >= 1023) {
1601
					close(ptmpfd);
1602
					pw_abort();
1603
					errx(EXIT_FAILURE, "can't add `%s', "
1604
					    "line too long (%zu bytes)", buf,
1605
					    len + expand_len(pwp->pw_gecos,
1606
					    newlogin));
1607
				}
1608
				if (write(ptmpfd, buf, len) != len) {
1609
					int saved_errno = errno;
1610
					close(ptmpfd);
1611
					pw_abort();
1612
					errc(EXIT_FAILURE, saved_errno,
1613
					    "can't add `%s'", buf);
1614
				}
1615
			}
1616
		} else {
1617
			len = strlen(buf);
1618
			if ((cc = write(ptmpfd, buf, len)) != len) {
1619
				int saved_errno = errno;
1620
				close(masterfd);
1621
				close(ptmpfd);
1622
				pw_abort();
1623
				errc(EXIT_FAILURE, saved_errno,
1624
				    "short write to /etc/ptmp (%lld not %lld chars)",
1625
				    (long long)cc, (long long)len);
1626
			}
1627
		}
1628
	}
1629
	if (up != NULL) {
1630
		if ((up->u_flags & F_MKDIR) &&
1631
		    asystem("%s %s %s", MV, homedir, pwp->pw_dir) != 0) {
1632
			int saved_errno = errno;
1633
			close(ptmpfd);
1634
			pw_abort();
1635
			errc(EXIT_FAILURE, saved_errno,
1636
			    "can't move `%s' to `%s'", homedir, pwp->pw_dir);
1637
		}
1638
		if (up->u_flags & F_SETSECGROUP) {
1639
			for (i = 0 ; i < up->u_groupc ; i++) {
1640
				if (getgrnam(up->u_groupv[i]) == NULL) {
1641
					close(ptmpfd);
1642
					pw_abort();
1643
					errx(EXIT_FAILURE,
1644
					    "aborting, group `%s' does not exist",
1645
					    up->u_groupv[i]);
1646
				}
1647
			}
1648
			if (!rm_user_from_groups(newlogin)) {
1649
				close(ptmpfd);
1650
				pw_abort();
1651
				errx(EXIT_FAILURE,
1652
				    "can't reset groups for `%s'", newlogin);
1653
			}
1654
		}
1655
		if (up->u_groupc > 0) {
1656
		    if (!append_group(newlogin, up->u_groupc, up->u_groupv)) {
1657
			close(ptmpfd);
1658
			pw_abort();
1659
			errx(EXIT_FAILURE, "can't append `%s' to new groups",
1660
			    newlogin);
1661
		    }
1662
		}
1663
	}
1664
	fclose(master);
1665
	close(ptmpfd);
1666
	free(pw_tmp);
1667
	free(shell_tmp);
1668
	if (up != NULL && strcmp(login_name, newlogin) == 0)
1669
		rval = pw_mkdb(login_name, 0);
1670
	else
1671
		rval = pw_mkdb(NULL, 0);
1672
	if (rval == -1) {
1673
		pw_abort();
1674
		err(EXIT_FAILURE, "pw_mkdb failed");
1675
	}
1676
	if (up == NULL) {
1677
		syslog(LOG_INFO, "user removed: name=%s", login_name);
1678
	} else if (strcmp(login_name, newlogin) == 0) {
1679
		syslog(LOG_INFO, "user information modified: name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1680
			login_name, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, pwp->pw_shell);
1681
	} else {
1682
		syslog(LOG_INFO, "user information modified: name=%s, new name=%s, uid=%u, gid=%u, home=%s, shell=%s",
1683
			login_name, newlogin, pwp->pw_uid, pwp->pw_gid, pwp->pw_dir, pwp->pw_shell);
1684
	}
1685
	return 1;
1686
}
1687
1688
/* print out usage message, and then exit */
1689
void
1690
usermgmt_usage(const char *prog)
1691
{
1692
	if (strcmp(prog, "useradd") == 0) {
1693
		fprintf(stderr, "usage: %s -D [-b base-directory] "
1694
		    "[-e expiry-time] [-f inactive-time]\n"
1695
		    "               [-g gid | name | =uid] [-k skel-directory] "
1696
		    "[-L login-class]\n"
1697
		    "               [-r low..high] [-s shell]\n", prog);
1698
		fprintf(stderr, "       %s [-mov] [-b base-directory] "
1699
		    "[-c comment] [-d home-directory]\n"
1700
		    "               [-e expiry-time] [-f inactive-time]\n"
1701
		    "               [-G secondary-group[,group,...]] "
1702
		    "[-g gid | name | =uid]\n"
1703
		    "               [-k skel-directory] [-L login-class] "
1704
		    "[-p password] [-r low..high]\n"
1705
		    "               [-s shell] [-u uid] user\n", prog);
1706
	} else if (strcmp(prog, "usermod") == 0) {
1707
		fprintf(stderr, "usage: %s [-moUvZ] "
1708
		    "[-c comment] [-d home-directory] [-e expiry-time]\n"
1709
		    "               [-f inactive-time] "
1710
		    "[-G secondary-group[,group,...]]\n"
1711
		    "               [-g gid | name | =uid] [-L login-class] "
1712
		    "[-l new-login]\n"
1713
		    "               [-p password] "
1714
		    "[-S secondary-group[,group,...]]\n"
1715
		    "               [-s shell] [-u uid] user\n",
1716
		    prog);
1717
	} else if (strcmp(prog, "userdel") == 0) {
1718
		fprintf(stderr, "usage: %s -D [-p preserve-value]\n",
1719
		    prog);
1720
		fprintf(stderr, "       %s [-rv] [-p preserve-value] "
1721
		    "user\n", prog);
1722
	} else if (strcmp(prog, "userinfo") == 0) {
1723
		fprintf(stderr, "usage: %s [-e] user\n", prog);
1724
	} else if (strcmp(prog, "groupadd") == 0) {
1725
		fprintf(stderr, "usage: %s [-ov] [-g gid] group\n",
1726
		    prog);
1727
	} else if (strcmp(prog, "groupdel") == 0) {
1728
		fprintf(stderr, "usage: %s [-v] group\n", prog);
1729
	} else if (strcmp(prog, "groupmod") == 0) {
1730
		fprintf(stderr, "usage: %s [-ov] [-g gid] [-n newname] "
1731
		    "group\n", prog);
1732
	} else if (strcmp(prog, "user") == 0 || strcmp(prog, "group") == 0) {
1733
		fprintf(stderr, "usage: %s [add | del | mod"
1734
		" | info"
1735
		"] ...\n",
1736
		    prog);
1737
	} else if (strcmp(prog, "groupinfo") == 0) {
1738
		fprintf(stderr, "usage: %s [-e] group\n", prog);
1739
	} else {
1740
		fprintf(stderr, "This program must be called as {user,group}{add,del,mod,info},\n%s is not an understood name.\n", prog);
1741
	}
1742
	exit(EXIT_FAILURE);
1743
}
1744
1745
int
1746
useradd(int argc, char **argv)
1747
{
1748
2
	user_t	u;
1749
1
	const char *errstr;
1750
	int	defaultfield;
1751
	int	bigD;
1752
	int	c;
1753
	int	i;
1754
1755
1
	memset(&u, 0, sizeof(u));
1756
1
	read_defaults(&u);
1757
1
	u.u_uid = UID_MAX;
1758
	defaultfield = bigD = 0;
1759
8
	while ((c = getopt(argc, argv, "DG:L:b:c:d:e:f:g:k:mop:r:s:u:v")) != -1) {
1760




6
		switch(c) {
1761
		case 'D':
1762
			bigD = 1;
1763
			break;
1764
		case 'G':
1765
			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1766
			    u.u_groupc < NGROUPS_MAX - 2) {
1767
				if (u.u_groupv[u.u_groupc][0] != 0) {
1768
					u.u_groupc++;
1769
				}
1770
			}
1771
			if (optarg != NULL) {
1772
				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1773
			}
1774
			break;
1775
		case 'b':
1776
			defaultfield = 1;
1777
			memsave(&u.u_basedir, optarg, strlen(optarg));
1778
			break;
1779
		case 'c':
1780
1
			memsave(&u.u_comment, optarg, strlen(optarg));
1781
1
			break;
1782
		case 'd':
1783
1
			memsave(&u.u_home, optarg, strlen(optarg));
1784
1
			u.u_flags |= F_HOMEDIR;
1785
1
			break;
1786
		case 'e':
1787
			defaultfield = 1;
1788
			memsave(&u.u_expire, optarg, strlen(optarg));
1789
			break;
1790
		case 'f':
1791
			defaultfield = 1;
1792
			memsave(&u.u_inactive, optarg, strlen(optarg));
1793
			break;
1794
		case 'g':
1795
			defaultfield = 1;
1796
1
			memsave(&u.u_primgrp, optarg, strlen(optarg));
1797
1
			break;
1798
		case 'k':
1799
			defaultfield = 1;
1800
			memsave(&u.u_skeldir, optarg, strlen(optarg));
1801
			break;
1802
		case 'L':
1803
			defaultfield = 1;
1804
1
			memsave(&u.u_class, optarg, strlen(optarg));
1805
1
			break;
1806
		case 'm':
1807
			u.u_flags |= F_MKDIR;
1808
			break;
1809
		case 'o':
1810
			u.u_flags |= F_DUPUID;
1811
			break;
1812
		case 'p':
1813
			memsave(&u.u_password, optarg, strlen(optarg));
1814
			explicit_bzero(optarg, strlen(optarg));
1815
			break;
1816
		case 'r':
1817
			defaultfield = 1;
1818
			(void) save_range(&u, optarg);
1819
			break;
1820
		case 's':
1821
			defaultfield = 1;
1822
1
			memsave(&u.u_shell, optarg, strlen(optarg));
1823
1
			break;
1824
		case 'u':
1825
1
			u.u_uid = strtonum(optarg, -1, UID_MAX, &errstr);
1826
1
			if (errstr != NULL) {
1827
				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1828
			}
1829
			break;
1830
		case 'v':
1831
			verbose = 1;
1832
			break;
1833
		default:
1834
			usermgmt_usage("useradd");
1835
		}
1836
	}
1837
1838
2
	if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
1839
1
	    NULL) == -1)
1840
		err(1, "pledge");
1841
1842
1
	if (bigD) {
1843
		if (defaultfield) {
1844
			checkeuid();
1845
			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
1846
		}
1847
		printf("group\t\t%s\n", u.u_primgrp);
1848
		printf("base_dir\t%s\n", u.u_basedir);
1849
		printf("skel_dir\t%s\n", u.u_skeldir);
1850
		printf("shell\t\t%s\n", u.u_shell);
1851
		printf("class\t\t%s\n", u.u_class);
1852
		printf("inactive\t%s\n", (u.u_inactive == NULL) ? UNSET_INACTIVE : u.u_inactive);
1853
		printf("expire\t\t%s\n", (u.u_expire == NULL) ? UNSET_EXPIRY : u.u_expire);
1854
		for (i = 0 ; i < u.u_rc ; i++) {
1855
			printf("range\t\t%u..%u\n", u.u_rv[i].r_from, u.u_rv[i].r_to);
1856
		}
1857
		return EXIT_SUCCESS;
1858
	}
1859
1
	argc -= optind;
1860
1
	argv += optind;
1861
1
	if (argc != 1) {
1862
		usermgmt_usage("useradd");
1863
	}
1864
1
	checkeuid();
1865
1
	openlog("useradd", LOG_PID, LOG_USER);
1866
1
	return adduser(*argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
1867
1
}
1868
1869
int
1870
usermod(int argc, char **argv)
1871
{
1872
	user_t	u;
1873
	char	newuser[MaxUserNameLen + 1];
1874
	int	c, have_new_user;
1875
	const char *errstr;
1876
1877
	memset(&u, 0, sizeof(u));
1878
	memset(newuser, 0, sizeof(newuser));
1879
	read_defaults(&u);
1880
	free(u.u_primgrp);
1881
	u.u_primgrp = NULL;
1882
	have_new_user = 0;
1883
	while ((c = getopt(argc, argv, "G:L:S:UZc:d:e:f:g:l:mop:s:u:v")) != -1) {
1884
		switch(c) {
1885
		case 'G':
1886
			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1887
			    u.u_groupc < NGROUPS_MAX - 2) {
1888
				if (u.u_groupv[u.u_groupc][0] != 0) {
1889
					u.u_groupc++;
1890
				}
1891
			}
1892
			if (optarg != NULL) {
1893
				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1894
			}
1895
			u.u_flags |= F_SECGROUP;
1896
			break;
1897
		case 'S':
1898
			while ((u.u_groupv[u.u_groupc] = strsep(&optarg, ",")) != NULL &&
1899
			    u.u_groupc < NGROUPS_MAX - 2) {
1900
				if (u.u_groupv[u.u_groupc][0] != 0) {
1901
					u.u_groupc++;
1902
				}
1903
			}
1904
			if (optarg != NULL) {
1905
				warnx("Truncated list of secondary groups to %d entries", NGROUPS_MAX - 2);
1906
			}
1907
			u.u_flags |= F_SETSECGROUP;
1908
			break;
1909
		case 'U':
1910
			u.u_flags |= F_ACCTUNLOCK;
1911
			break;
1912
		case 'Z':
1913
			u.u_flags |= F_ACCTLOCK;
1914
			break;
1915
		case 'c':
1916
			memsave(&u.u_comment, optarg, strlen(optarg));
1917
			u.u_flags |= F_COMMENT;
1918
			break;
1919
		case 'd':
1920
			memsave(&u.u_home, optarg, strlen(optarg));
1921
			u.u_flags |= F_HOMEDIR;
1922
			break;
1923
		case 'e':
1924
			memsave(&u.u_expire, optarg, strlen(optarg));
1925
			u.u_flags |= F_EXPIRE;
1926
			break;
1927
		case 'f':
1928
			memsave(&u.u_inactive, optarg, strlen(optarg));
1929
			u.u_flags |= F_INACTIVE;
1930
			break;
1931
		case 'g':
1932
			memsave(&u.u_primgrp, optarg, strlen(optarg));
1933
			u.u_flags |= F_GROUP;
1934
			break;
1935
		case 'l':
1936
			if (strlcpy(newuser, optarg, sizeof(newuser)) >=
1937
			    sizeof(newuser))
1938
				errx(EXIT_FAILURE, "username `%s' too long",
1939
				    optarg);
1940
			have_new_user = 1;
1941
			u.u_flags |= F_USERNAME;
1942
			break;
1943
		case 'L':
1944
			memsave(&u.u_class, optarg, strlen(optarg));
1945
			u.u_flags |= F_CLASS;
1946
			break;
1947
		case 'm':
1948
			u.u_flags |= F_MKDIR;
1949
			break;
1950
		case 'o':
1951
			u.u_flags |= F_DUPUID;
1952
			break;
1953
		case 'p':
1954
			memsave(&u.u_password, optarg, strlen(optarg));
1955
			explicit_bzero(optarg, strlen(optarg));
1956
			u.u_flags |= F_PASSWORD;
1957
			break;
1958
		case 's':
1959
			memsave(&u.u_shell, optarg, strlen(optarg));
1960
			u.u_flags |= F_SHELL;
1961
			break;
1962
		case 'u':
1963
			u.u_uid = strtonum(optarg, -1, UID_MAX, &errstr);
1964
			u.u_flags |= F_UID;
1965
			if (errstr != NULL) {
1966
				errx(EXIT_FAILURE, "When using [-u uid], the uid must be numeric");
1967
			}
1968
			break;
1969
		case 'v':
1970
			verbose = 1;
1971
			break;
1972
		default:
1973
			usermgmt_usage("usermod");
1974
		}
1975
	}
1976
1977
	if ((u.u_flags & F_MKDIR) && !(u.u_flags & F_HOMEDIR) &&
1978
	    !(u.u_flags & F_USERNAME)) {
1979
		warnx("option 'm' useless without 'd' or 'l' -- ignored");
1980
		u.u_flags &= ~F_MKDIR;
1981
	}
1982
	if ((u.u_flags & F_SECGROUP) && (u.u_flags & F_SETSECGROUP))
1983
		errx(EXIT_FAILURE, "options 'G' and 'S' are mutually exclusive");
1984
	if ((u.u_flags & F_ACCTLOCK) && (u.u_flags & F_ACCTUNLOCK))
1985
		errx(EXIT_FAILURE, "options 'U' and 'Z' are mutually exclusive");
1986
	if ((u.u_flags & F_PASSWORD) && (u.u_flags & (F_ACCTLOCK | F_ACCTUNLOCK)))
1987
		errx(EXIT_FAILURE, "options 'U' or 'Z' with 'p' are mutually exclusive");
1988
	argc -= optind;
1989
	argv += optind;
1990
	if (argc != 1) {
1991
		usermgmt_usage("usermod");
1992
	}
1993
	checkeuid();
1994
	openlog("usermod", LOG_PID, LOG_USER);
1995
	return moduser(*argv, (have_new_user) ? newuser : *argv, &u) ?
1996
	    EXIT_SUCCESS : EXIT_FAILURE;
1997
}
1998
1999
int
2000
userdel(int argc, char **argv)
2001
{
2002
	struct passwd	*pwp;
2003
	user_t		u;
2004
	int		defaultfield;
2005
	int		rmhome;
2006
	int		bigD;
2007
	int		c;
2008
2009
	memset(&u, 0, sizeof(u));
2010
	read_defaults(&u);
2011
	defaultfield = bigD = rmhome = 0;
2012
	while ((c = getopt(argc, argv, "Dp:rv")) != -1) {
2013
		switch(c) {
2014
		case 'D':
2015
			bigD = 1;
2016
			break;
2017
		case 'p':
2018
			defaultfield = 1;
2019
			u.u_preserve = (strcmp(optarg, "true") == 0) ? 1 :
2020
					(strcmp(optarg, "yes") == 0) ? 1 :
2021
					 strtonum(optarg, INT_MIN, INT_MAX, NULL);
2022
			break;
2023
		case 'r':
2024
			rmhome = 1;
2025
			break;
2026
		case 'v':
2027
			verbose = 1;
2028
			break;
2029
		default:
2030
			usermgmt_usage("userdel");
2031
		}
2032
	}
2033
	if (bigD) {
2034
		if (defaultfield) {
2035
			checkeuid();
2036
			return setdefaults(&u) ? EXIT_SUCCESS : EXIT_FAILURE;
2037
		}
2038
		printf("preserve\t%s\n", (u.u_preserve) ? "true" : "false");
2039
		return EXIT_SUCCESS;
2040
	}
2041
	argc -= optind;
2042
	argv += optind;
2043
	if (argc != 1) {
2044
		usermgmt_usage("userdel");
2045
	}
2046
2047
	if (pledge("stdio rpath wpath cpath fattr flock proc exec getpw id",
2048
	    NULL) == -1)
2049
		err(1, "pledge");
2050
2051
	checkeuid();
2052
	if ((pwp = getpwnam(*argv)) == NULL) {
2053
		warnx("No such user `%s'", *argv);
2054
		return EXIT_FAILURE;
2055
	}
2056
	if (rmhome)
2057
		(void)removehomedir(pwp->pw_name, pwp->pw_uid, pwp->pw_dir);
2058
	if (u.u_preserve) {
2059
		u.u_flags |= F_SHELL;
2060
		memsave(&u.u_shell, NOLOGIN, strlen(NOLOGIN));
2061
		memsave(&u.u_password, "*", strlen("*"));
2062
		u.u_flags |= F_PASSWORD;
2063
		openlog("userdel", LOG_PID, LOG_USER);
2064
		return moduser(*argv, *argv, &u) ? EXIT_SUCCESS : EXIT_FAILURE;
2065
	}
2066
	if (!rm_user_from_groups(*argv)) {
2067
		return 0;
2068
	}
2069
	openlog("userdel", LOG_PID, LOG_USER);
2070
	return moduser(*argv, *argv, NULL) ? EXIT_SUCCESS : EXIT_FAILURE;
2071
}
2072
2073
/* add a group */
2074
int
2075
groupadd(int argc, char **argv)
2076
{
2077
	int	dupgid;
2078
2
	int	gid;
2079
	int	c;
2080
1
	const char *errstr;
2081
2082
1
	gid = GID_MAX;
2083
	dupgid = 0;
2084
3
	while ((c = getopt(argc, argv, "g:ov")) != -1) {
2085

1
		switch(c) {
2086
		case 'g':
2087
1
			gid = strtonum(optarg, -1, GID_MAX, &errstr);
2088
1
			if (errstr != NULL) {
2089
				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2090
			}
2091
			break;
2092
		case 'o':
2093
			dupgid = 1;
2094
			break;
2095
		case 'v':
2096
			verbose = 1;
2097
			break;
2098
		default:
2099
			usermgmt_usage("groupadd");
2100
		}
2101
	}
2102
1
	argc -= optind;
2103
1
	argv += optind;
2104
1
	if (argc != 1) {
2105
		usermgmt_usage("groupadd");
2106
	}
2107
2108
1
	if (pledge("stdio rpath wpath cpath fattr flock getpw", NULL) == -1)
2109
		err(1, "pledge");
2110
2111
1
	checkeuid();
2112
1
	if (!valid_group(*argv)) {
2113
		errx(EXIT_FAILURE, "invalid group name `%s'", *argv);
2114
	}
2115

1
	if (gid < 0 && !getnextgid(&gid, LowGid, HighGid)) {
2116
		errx(EXIT_FAILURE, "can't add group: can't get next gid");
2117
	}
2118

2
	if (!dupgid && getgrgid((gid_t) gid) != NULL) {
2119
		errx(EXIT_FAILURE, "can't add group: gid %d is a duplicate", gid);
2120
	}
2121
1
	openlog("groupadd", LOG_PID, LOG_USER);
2122
1
	if (!creategid(*argv, gid, "")) {
2123
		errx(EXIT_FAILURE, "can't add group: problems with %s file",
2124
		    _PATH_GROUP);
2125
	}
2126
1
	return EXIT_SUCCESS;
2127
1
}
2128
2129
/* remove a group */
2130
int
2131
groupdel(int argc, char **argv)
2132
{
2133
	int	c;
2134
2135
	while ((c = getopt(argc, argv, "v")) != -1) {
2136
		switch(c) {
2137
		case 'v':
2138
			verbose = 1;
2139
			break;
2140
		default:
2141
			usermgmt_usage("groupdel");
2142
		}
2143
	}
2144
	argc -= optind;
2145
	argv += optind;
2146
	if (argc != 1) {
2147
		usermgmt_usage("groupdel");
2148
	}
2149
	checkeuid();
2150
	openlog("groupdel", LOG_PID, LOG_USER);
2151
	if (getgrnam(*argv) == NULL) {
2152
		warnx("No such group: `%s'", *argv);
2153
		return EXIT_FAILURE;
2154
	}
2155
2156
	if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1)
2157
		err(1, "pledge");
2158
2159
	if (!modify_gid(*argv, NULL)) {
2160
		err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2161
	}
2162
	return EXIT_SUCCESS;
2163
}
2164
2165
/* modify a group */
2166
int
2167
groupmod(int argc, char **argv)
2168
{
2169
	struct group	*grp;
2170
	const char	*errstr;
2171
	char		buf[LINE_MAX];
2172
	char		*newname;
2173
	char		**cpp;
2174
	int		dupgid;
2175
	int		gid;
2176
	int		cc;
2177
	int		c;
2178
2179
	gid = GID_MAX;
2180
	dupgid = 0;
2181
	newname = NULL;
2182
	while ((c = getopt(argc, argv, "g:n:ov")) != -1) {
2183
		switch(c) {
2184
		case 'g':
2185
			gid = strtonum(optarg, -1, GID_MAX, &errstr);
2186
			if (errstr != NULL) {
2187
				errx(EXIT_FAILURE, "When using [-g gid], the gid must be numeric");
2188
			}
2189
			break;
2190
		case 'o':
2191
			dupgid = 1;
2192
			break;
2193
		case 'n':
2194
			memsave(&newname, optarg, strlen(optarg));
2195
			break;
2196
		case 'v':
2197
			verbose = 1;
2198
			break;
2199
		default:
2200
			usermgmt_usage("groupmod");
2201
		}
2202
	}
2203
	argc -= optind;
2204
	argv += optind;
2205
	if (argc != 1) {
2206
		usermgmt_usage("groupmod");
2207
	}
2208
	checkeuid();
2209
	if (gid < 0 && newname == NULL) {
2210
		errx(EXIT_FAILURE, "Nothing to change");
2211
	}
2212
	if (dupgid && gid < 0) {
2213
		errx(EXIT_FAILURE, "Duplicate which gid?");
2214
	}
2215
	if ((grp = getgrnam(*argv)) == NULL) {
2216
		errx(EXIT_FAILURE, "can't find group `%s' to modify", *argv);
2217
	}
2218
2219
	if (pledge("stdio rpath wpath cpath fattr flock", NULL) == -1)
2220
		err(1, "pledge");
2221
2222
	if (!is_local(*argv, _PATH_GROUP)) {
2223
		errx(EXIT_FAILURE, "Group `%s' must be a local group", *argv);
2224
	}
2225
	if (newname != NULL && !valid_group(newname)) {
2226
		errx(EXIT_FAILURE, "invalid group name `%s'", newname);
2227
	}
2228
	if ((cc = snprintf(buf, sizeof(buf), "%s:%s:%u:",
2229
	    (newname) ? newname : grp->gr_name, grp->gr_passwd,
2230
	    (gid < 0) ? grp->gr_gid : gid)) >= sizeof(buf) || cc < 0)
2231
		errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2232
2233
	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2234
		cc = strlcat(buf, *cpp, sizeof(buf)) + 1;
2235
		if (cc >= sizeof(buf))
2236
			errx(EXIT_FAILURE, "group `%s' entry too long",
2237
			    grp->gr_name);
2238
		if (cpp[1] != NULL) {
2239
			buf[cc - 1] = ',';
2240
			buf[cc] = '\0';
2241
		}
2242
	}
2243
	cc = strlcat(buf, "\n", sizeof(buf));
2244
	if (cc >= sizeof(buf))
2245
		errx(EXIT_FAILURE, "group `%s' entry too long", grp->gr_name);
2246
2247
	openlog("groupmod", LOG_PID, LOG_USER);
2248
	if (!modify_gid(*argv, buf))
2249
		err(EXIT_FAILURE, "can't change %s file", _PATH_GROUP);
2250
	return EXIT_SUCCESS;
2251
}
2252
2253
/* display user information */
2254
int
2255
userinfo(int argc, char **argv)
2256
{
2257
	struct passwd	*pwp;
2258
	struct group	*grp;
2259
	char		**cpp;
2260
	int		exists;
2261
	int		i;
2262
2263
	exists = 0;
2264
	while ((i = getopt(argc, argv, "ev")) != -1) {
2265
		switch(i) {
2266
		case 'e':
2267
			exists = 1;
2268
			break;
2269
		case 'v':
2270
			verbose = 1;
2271
			break;
2272
		default:
2273
			usermgmt_usage("userinfo");
2274
		}
2275
	}
2276
	argc -= optind;
2277
	argv += optind;
2278
	if (argc != 1) {
2279
		usermgmt_usage("userinfo");
2280
	}
2281
2282
	if (pledge("stdio getpw flock rpath cpath wpath", NULL) == -1)
2283
		err(1, "pledge");
2284
2285
	pwp = find_user_info(*argv);
2286
	if (exists) {
2287
		exit((pwp) ? EXIT_SUCCESS : EXIT_FAILURE);
2288
	}
2289
	if (pwp == NULL) {
2290
		errx(EXIT_FAILURE, "can't find user `%s'", *argv);
2291
	}
2292
	printf("login\t%s\n", pwp->pw_name);
2293
	printf("passwd\t%s\n", pwp->pw_passwd);
2294
	printf("uid\t%u\n", pwp->pw_uid);
2295
	if ((grp = getgrgid(pwp->pw_gid)) == NULL)
2296
		printf("groups\t%u", pwp->pw_gid);
2297
	else
2298
		printf("groups\t%s", grp->gr_name);
2299
	while ((grp = getgrent()) != NULL) {
2300
		for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2301
			if (strcmp(*cpp, pwp->pw_name) == 0 &&
2302
			    grp->gr_gid != pwp->pw_gid)
2303
				printf(" %s", grp->gr_name);
2304
		}
2305
	}
2306
	fputc('\n', stdout);
2307
	printf("change\t%s", pwp->pw_change ? ctime(&pwp->pw_change) : "NEVER\n");
2308
	printf("class\t%s\n", pwp->pw_class);
2309
	printf("gecos\t%s\n", pwp->pw_gecos);
2310
	printf("dir\t%s\n", pwp->pw_dir);
2311
	printf("shell\t%s\n", pwp->pw_shell);
2312
	printf("expire\t%s", pwp->pw_expire ? ctime(&pwp->pw_expire) : "NEVER\n");
2313
	return EXIT_SUCCESS;
2314
}
2315
2316
/* display user information */
2317
int
2318
groupinfo(int argc, char **argv)
2319
{
2320
	struct group	*grp;
2321
	char		**cpp;
2322
	int		exists;
2323
	int		i;
2324
2325
	exists = 0;
2326
	while ((i = getopt(argc, argv, "ev")) != -1) {
2327
		switch(i) {
2328
		case 'e':
2329
			exists = 1;
2330
			break;
2331
		case 'v':
2332
			verbose = 1;
2333
			break;
2334
		default:
2335
			usermgmt_usage("groupinfo");
2336
		}
2337
	}
2338
	argc -= optind;
2339
	argv += optind;
2340
	if (argc != 1) {
2341
		usermgmt_usage("groupinfo");
2342
	}
2343
2344
	if (pledge("stdio getpw flock rpath cpath wpath", NULL) == -1)
2345
		err(1, "pledge");
2346
2347
	grp = find_group_info(*argv);
2348
	if (exists) {
2349
		exit((grp) ? EXIT_SUCCESS : EXIT_FAILURE);
2350
	}
2351
	if (grp == NULL) {
2352
		errx(EXIT_FAILURE, "can't find group `%s'", *argv);
2353
	}
2354
	printf("name\t%s\n", grp->gr_name);
2355
	printf("passwd\t%s\n", grp->gr_passwd);
2356
	printf("gid\t%u\n", grp->gr_gid);
2357
	printf("members\t");
2358
	for (cpp = grp->gr_mem ; *cpp ; cpp++) {
2359
		printf("%s ", *cpp);
2360
	}
2361
	fputc('\n', stdout);
2362
	return EXIT_SUCCESS;
2363
}