pw_user.c revision 285398
1254721Semaste/*-
2254721Semaste * Copyright (C) 1996
3254721Semaste *	David L. Nugent.  All rights reserved.
4254721Semaste *
5254721Semaste * Redistribution and use in source and binary forms, with or without
6254721Semaste * modification, are permitted provided that the following conditions
7254721Semaste * are met:
8254721Semaste * 1. Redistributions of source code must retain the above copyright
9254721Semaste *    notice, this list of conditions and the following disclaimer.
10254721Semaste * 2. Redistributions in binary form must reproduce the above copyright
11254721Semaste *    notice, this list of conditions and the following disclaimer in the
12254721Semaste *    documentation and/or other materials provided with the distribution.
13254721Semaste *
14254721Semaste * THIS SOFTWARE IS PROVIDED BY DAVID L. NUGENT AND CONTRIBUTORS ``AS IS'' AND
15254721Semaste * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16254721Semaste * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17254721Semaste * ARE DISCLAIMED.  IN NO EVENT SHALL DAVID L. NUGENT OR CONTRIBUTORS BE LIABLE
18254721Semaste * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19254721Semaste * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20254721Semaste * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21254721Semaste * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22254721Semaste * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23254721Semaste * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24254721Semaste * SUCH DAMAGE.
25254721Semaste *
26254721Semaste */
27254721Semaste
28254721Semaste#ifndef lint
29254721Semastestatic const char rcsid[] =
30254721Semaste  "$FreeBSD: head/usr.sbin/pw/pw_user.c 285398 2015-07-11 18:09:27Z bapt $";
31254721Semaste#endif /* not lint */
32254721Semaste
33254721Semaste#include <ctype.h>
34254721Semaste#include <err.h>
35254721Semaste#include <fcntl.h>
36254721Semaste#include <sys/param.h>
37254721Semaste#include <dirent.h>
38254721Semaste#include <paths.h>
39254721Semaste#include <termios.h>
40254721Semaste#include <sys/types.h>
41254721Semaste#include <sys/time.h>
42254721Semaste#include <sys/resource.h>
43254721Semaste#include <login_cap.h>
44254721Semaste#include <pwd.h>
45254721Semaste#include <grp.h>
46254721Semaste#include <libutil.h>
47254721Semaste#include "pw.h"
48254721Semaste#include "bitmap.h"
49254721Semaste
50254721Semaste#define LOGNAMESIZE (MAXLOGNAME-1)
51254721Semaste
52254721Semastestatic		char locked_str[] = "*LOCKED*";
53254721Semaste
54254721Semastestatic int	delete_user(struct userconf *cnf, struct passwd *pwd,
55254721Semaste		    char *name, int delete, int mode);
56254721Semastestatic int	print_user(struct passwd * pwd);
57254721Semastestatic uid_t    pw_uidpolicy(struct userconf * cnf, long id);
58254721Semastestatic uid_t    pw_gidpolicy(struct cargs * args, char *nam, gid_t prefer);
59254721Semastestatic time_t   pw_pwdpolicy(struct userconf * cnf, struct cargs * args);
60254721Semastestatic time_t   pw_exppolicy(struct userconf * cnf, struct cargs * args);
61254721Semastestatic char    *pw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user);
62254721Semastestatic char    *pw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell);
63254721Semastestatic char    *pw_password(struct userconf * cnf, struct cargs * args, char const * user);
64254721Semastestatic char    *shell_path(char const * path, char *shells[], char *sh);
65254721Semastestatic void     rmat(uid_t uid);
66254721Semastestatic void     rmopie(char const * name);
67254721Semaste
68254721Semastestatic void
69254721Semastecreate_and_populate_homedir(int mode, struct passwd *pwd)
70254721Semaste{
71254721Semaste	char *homedir, *dotdir;
72254721Semaste	struct userconf *cnf = conf.userconf;
73254721Semaste
74254721Semaste	homedir = dotdir = NULL;
75254721Semaste
76254721Semaste	if (conf.rootdir[0] != '\0') {
77254721Semaste		asprintf(&homedir, "%s/%s", conf.rootdir, pwd->pw_dir);
78254721Semaste		if (homedir == NULL)
79254721Semaste			errx(EX_OSERR, "out of memory");
80254721Semaste		asprintf(&dotdir, "%s/%s", conf.rootdir, cnf->dotdir);
81254721Semaste	}
82254721Semaste
83254721Semaste	copymkdir(homedir ? homedir : pwd->pw_dir, dotdir ? dotdir: cnf->dotdir,
84254721Semaste	    cnf->homemode, pwd->pw_uid, pwd->pw_gid);
85254721Semaste	pw_log(cnf, mode, W_USER, "%s(%u) home %s made", pwd->pw_name,
86254721Semaste	    pwd->pw_uid, pwd->pw_dir);
87254721Semaste}
88254721Semaste
89254721Semastestatic int
90254721Semasteset_passwd(struct passwd *pwd, bool update)
91254721Semaste{
92254721Semaste	int		 b, istty;
93254721Semaste	struct termios	 t, n;
94254721Semaste	login_cap_t	*lc;
95254721Semaste	char		line[_PASSWORD_LEN+1];
96254721Semaste	char		*p;
97254721Semaste
98254721Semaste	if (conf.fd == '-') {
99254721Semaste		if (!pwd->pw_passwd || *pwd->pw_passwd != '*') {
100254721Semaste			pwd->pw_passwd = "*";	/* No access */
101254721Semaste			return (1);
102254721Semaste		}
103254721Semaste		return (0);
104254721Semaste	}
105254721Semaste
106254721Semaste	if ((istty = isatty(conf.fd))) {
107254721Semaste		if (tcgetattr(conf.fd, &t) == -1)
108254721Semaste			istty = 0;
109254721Semaste		else {
110254721Semaste			n = t;
111254721Semaste			n.c_lflag &= ~(ECHO);
112254721Semaste			tcsetattr(conf.fd, TCSANOW, &n);
113254721Semaste			printf("%s%spassword for user %s:",
114254721Semaste			    update ? "new " : "",
115254721Semaste			    conf.precrypted ? "encrypted " : "",
116254721Semaste			    pwd->pw_name);
117254721Semaste			fflush(stdout);
118254721Semaste		}
119254721Semaste	}
120254721Semaste	b = read(conf.fd, line, sizeof(line) - 1);
121254721Semaste	if (istty) {	/* Restore state */
122254721Semaste		tcsetattr(conf.fd, TCSANOW, &t);
123254721Semaste		fputc('\n', stdout);
124254721Semaste		fflush(stdout);
125254721Semaste	}
126254721Semaste
127254721Semaste	if (b < 0)
128254721Semaste		err(EX_IOERR, "-%c file descriptor",
129254721Semaste		    conf.precrypted ? 'H' : 'h');
130254721Semaste	line[b] = '\0';
131254721Semaste	if ((p = strpbrk(line, "\r\n")) != NULL)
132254721Semaste		*p = '\0';
133254721Semaste	if (!*line)
134254721Semaste		errx(EX_DATAERR, "empty password read on file descriptor %d",
135254721Semaste		    conf.fd);
136254721Semaste	if (conf.precrypted) {
137254721Semaste		if (strchr(line, ':') != NULL)
138254721Semaste			errx(EX_DATAERR, "bad encrypted password");
139254721Semaste		pwd->pw_passwd = line;
140254721Semaste	} else {
141254721Semaste		lc = login_getpwclass(pwd);
142254721Semaste		if (lc == NULL ||
143254721Semaste				login_setcryptfmt(lc, "sha512", NULL) == NULL)
144254721Semaste			warn("setting crypt(3) format");
145254721Semaste		login_close(lc);
146254721Semaste		pwd->pw_passwd = pw_pwcrypt(line);
147254721Semaste	}
148254721Semaste	return (1);
149254721Semaste}
150254721Semaste
151254721Semasteint
152254721Semastepw_usernext(struct userconf *cnf, bool quiet)
153254721Semaste{
154254721Semaste	uid_t next = pw_uidpolicy(cnf, -1);
155254721Semaste
156254721Semaste	if (quiet)
157254721Semaste		return (next);
158254721Semaste
159254721Semaste	printf("%u:", next);
160254721Semaste	pw_groupnext(cnf, quiet);
161254721Semaste
162254721Semaste	return (EXIT_SUCCESS);
163254721Semaste}
164254721Semaste
165254721Semastestatic int
166254721Semastepw_usershow(char *name, long id, struct passwd *fakeuser)
167254721Semaste{
168254721Semaste	struct passwd *pwd = NULL;
169254721Semaste
170254721Semaste	if (id < 0 && name == NULL && !conf.all)
171254721Semaste		errx(EX_DATAERR, "username or id or '-a' required");
172254721Semaste
173254721Semaste	if (conf.all) {
174254721Semaste		SETPWENT();
175254721Semaste		while ((pwd = GETPWENT()) != NULL)
176254721Semaste			print_user(pwd);
177254721Semaste		ENDPWENT();
178254721Semaste		return (EXIT_SUCCESS);
179254721Semaste	}
180254721Semaste
181254721Semaste	pwd = (name != NULL) ? GETPWNAM(pw_checkname(name, 0)) : GETPWUID(id);
182254721Semaste	if (pwd == NULL) {
183254721Semaste		if (conf.force) {
184254721Semaste			pwd = fakeuser;
185254721Semaste		} else {
186254721Semaste			if (name == NULL)
187254721Semaste				errx(EX_NOUSER, "no such uid `%ld'", id);
188254721Semaste			errx(EX_NOUSER, "no such user `%s'", name);
189254721Semaste		}
190254721Semaste	}
191254721Semaste
192254721Semaste	return (print_user(pwd));
193254721Semaste}
194254721Semaste
195254721Semaste/*-
196254721Semaste * -C config      configuration file
197254721Semaste * -q             quiet operation
198254721Semaste * -n name        login name
199254721Semaste * -u uid         user id
200254721Semaste * -c comment     user name/comment
201254721Semaste * -d directory   home directory
202254721Semaste * -e date        account expiry date
203254721Semaste * -p date        password expiry date
204254721Semaste * -g grp         primary group
205254721Semaste * -G grp1,grp2   additional groups
206254721Semaste * -m [ -k dir ]  create and set up home
207254721Semaste * -s shell       name of login shell
208254721Semaste * -o             duplicate uid ok
209254721Semaste * -L class       user class
210254721Semaste * -l name        new login name
211254721Semaste * -h fd          password filehandle
212254721Semaste * -H fd          encrypted password filehandle
213254721Semaste * -F             force print or add
214254721Semaste *   Setting defaults:
215254721Semaste * -D             set user defaults
216254721Semaste * -b dir         default home root dir
217254721Semaste * -e period      default expiry period
218254721Semaste * -p period      default password change period
219254721Semaste * -g group       default group
220254721Semaste * -G             grp1,grp2.. default additional groups
221254721Semaste * -L class       default login class
222254721Semaste * -k dir         default home skeleton
223254721Semaste * -s shell       default shell
224254721Semaste * -w method      default password method
225254721Semaste */
226254721Semaste
227254721Semasteint
228254721Semastepw_user(int mode, char *name, long id, struct cargs * args)
229254721Semaste{
230254721Semaste	int	        rc, edited = 0;
231254721Semaste	char           *p = NULL;
232254721Semaste	char					 *passtmp;
233254721Semaste	struct carg    *arg;
234254721Semaste	struct passwd  *pwd = NULL;
235254721Semaste	struct group   *grp;
236254721Semaste	struct stat     st;
237254721Semaste	struct userconf	*cnf;
238254721Semaste	char            line[_PASSWORD_LEN+1];
239254721Semaste	char		path[MAXPATHLEN];
240254721Semaste	FILE	       *fp;
241254721Semaste	char *dmode_c;
242254721Semaste	void *set = NULL;
243254721Semaste
244254721Semaste	static struct passwd fakeuser =
245254721Semaste	{
246254721Semaste		"nouser",
247254721Semaste		"*",
248254721Semaste		-1,
249254721Semaste		-1,
250254721Semaste		0,
251254721Semaste		"",
252254721Semaste		"User &",
253254721Semaste		"/nonexistent",
254254721Semaste		"/bin/sh",
255254721Semaste		0
256254721Semaste#if defined(__FreeBSD__)
257254721Semaste		,0
258254721Semaste#endif
259254721Semaste	};
260254721Semaste
261254721Semaste	cnf = conf.userconf;
262254721Semaste
263254721Semaste	if (mode == M_NEXT)
264254721Semaste		return (pw_usernext(cnf, conf.quiet));
265254721Semaste
266254721Semaste	if (mode == M_PRINT)
267254721Semaste		return (pw_usershow(name, id, &fakeuser));
268254721Semaste
269254721Semaste	/*
270254721Semaste	 * We can do all of the common legwork here
271254721Semaste	 */
272254721Semaste
273254721Semaste	if ((arg = getarg(args, 'b')) != NULL) {
274254721Semaste		cnf->home = arg->val;
275254721Semaste	}
276254721Semaste
277254721Semaste	if ((arg = getarg(args, 'M')) != NULL) {
278254721Semaste		dmode_c = arg->val;
279254721Semaste		if ((set = setmode(dmode_c)) == NULL)
280254721Semaste			errx(EX_DATAERR, "invalid directory creation mode '%s'",
281254721Semaste			    dmode_c);
282254721Semaste		cnf->homemode = getmode(set, _DEF_DIRMODE);
283254721Semaste		free(set);
284254721Semaste	}
285254721Semaste
286254721Semaste	/*
287254721Semaste	 * If we'll need to use it or we're updating it,
288254721Semaste	 * then create the base home directory if necessary
289254721Semaste	 */
290254721Semaste	if (arg != NULL || getarg(args, 'm') != NULL) {
291254721Semaste		int	l = strlen(cnf->home);
292254721Semaste
293254721Semaste		if (l > 1 && cnf->home[l-1] == '/')	/* Shave off any trailing path delimiter */
294254721Semaste			cnf->home[--l] = '\0';
295254721Semaste
296254721Semaste		if (l < 2 || *cnf->home != '/')		/* Check for absolute path name */
297254721Semaste			errx(EX_DATAERR, "invalid base directory for home '%s'", cnf->home);
298254721Semaste
299254721Semaste		if (stat(cnf->home, &st) == -1) {
300254721Semaste			char	dbuf[MAXPATHLEN];
301254721Semaste
302254721Semaste			/*
303254721Semaste			 * This is a kludge especially for Joerg :)
304254721Semaste			 * If the home directory would be created in the root partition, then
305254721Semaste			 * we really create it under /usr which is likely to have more space.
306254721Semaste			 * But we create a symlink from cnf->home -> "/usr" -> cnf->home
307254721Semaste			 */
308254721Semaste			if (strchr(cnf->home+1, '/') == NULL) {
309254721Semaste				snprintf(dbuf, MAXPATHLEN, "/usr%s", cnf->home);
310254721Semaste				if (mkdir(dbuf, _DEF_DIRMODE) != -1 || errno == EEXIST) {
311254721Semaste					chown(dbuf, 0, 0);
312254721Semaste					/*
313254721Semaste					 * Skip first "/" and create symlink:
314254721Semaste					 * /home -> usr/home
315254721Semaste					 */
316254721Semaste					symlink(dbuf+1, cnf->home);
317254721Semaste				}
318254721Semaste				/* If this falls, fall back to old method */
319254721Semaste			}
320254721Semaste			strlcpy(dbuf, cnf->home, sizeof(dbuf));
321254721Semaste			p = dbuf;
322254721Semaste			if (stat(dbuf, &st) == -1) {
323254721Semaste				while ((p = strchr(p + 1, '/')) != NULL) {
324254721Semaste					*p = '\0';
325254721Semaste					if (stat(dbuf, &st) == -1) {
326254721Semaste						if (mkdir(dbuf, _DEF_DIRMODE) == -1)
327254721Semaste							goto direrr;
328254721Semaste						chown(dbuf, 0, 0);
329254721Semaste					} else if (!S_ISDIR(st.st_mode))
330254721Semaste						errx(EX_OSFILE, "'%s' (root home parent) is not a directory", dbuf);
331254721Semaste					*p = '/';
332254721Semaste				}
333254721Semaste			}
334254721Semaste			if (stat(dbuf, &st) == -1) {
335254721Semaste				if (mkdir(dbuf, _DEF_DIRMODE) == -1) {
336254721Semaste				direrr:	err(EX_OSFILE, "mkdir '%s'", dbuf);
337254721Semaste				}
338254721Semaste				chown(dbuf, 0, 0);
339254721Semaste			}
340254721Semaste		} else if (!S_ISDIR(st.st_mode))
341254721Semaste			errx(EX_OSFILE, "root home `%s' is not a directory", cnf->home);
342254721Semaste	}
343254721Semaste
344254721Semaste	if ((arg = getarg(args, 'e')) != NULL)
345254721Semaste		cnf->expire_days = atoi(arg->val);
346254721Semaste
347254721Semaste	if ((arg = getarg(args, 'y')) != NULL)
348254721Semaste		cnf->nispasswd = arg->val;
349254721Semaste
350254721Semaste	if ((arg = getarg(args, 'p')) != NULL && arg->val)
351254721Semaste		cnf->password_days = atoi(arg->val);
352254721Semaste
353254721Semaste	if ((arg = getarg(args, 'g')) != NULL) {
354254721Semaste		if (!*(p = arg->val))	/* Handle empty group list specially */
355254721Semaste			cnf->default_group = "";
356254721Semaste		else {
357254721Semaste			if ((grp = GETGRNAM(p)) == NULL) {
358254721Semaste				if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL)
359254721Semaste					errx(EX_NOUSER, "group `%s' does not exist", p);
360254721Semaste			}
361254721Semaste			cnf->default_group = newstr(grp->gr_name);
362254721Semaste		}
363254721Semaste	}
364254721Semaste	if ((arg = getarg(args, 'L')) != NULL)
365254721Semaste		cnf->default_class = pw_checkname(arg->val, 0);
366254721Semaste
367254721Semaste	if ((arg = getarg(args, 'G')) != NULL && arg->val) {
368254721Semaste		int i = 0;
369254721Semaste
370254721Semaste		for (p = strtok(arg->val, ", \t"); p != NULL; p = strtok(NULL, ", \t")) {
371254721Semaste			if ((grp = GETGRNAM(p)) == NULL) {
372254721Semaste				if (!isdigit((unsigned char)*p) || (grp = GETGRGID((gid_t) atoi(p))) == NULL)
373254721Semaste					errx(EX_NOUSER, "group `%s' does not exist", p);
374254721Semaste			}
375254721Semaste			if (extendarray(&cnf->groups, &cnf->numgroups, i + 2) != -1)
376254721Semaste				cnf->groups[i++] = newstr(grp->gr_name);
377254721Semaste		}
378254721Semaste		while (i < cnf->numgroups)
379254721Semaste			cnf->groups[i++] = NULL;
380254721Semaste	}
381254721Semaste
382254721Semaste	if ((arg = getarg(args, 'k')) != NULL) {
383254721Semaste		if (stat(cnf->dotdir = arg->val, &st) == -1 || !S_ISDIR(st.st_mode))
384254721Semaste			errx(EX_OSFILE, "skeleton `%s' is not a directory or does not exist", cnf->dotdir);
385254721Semaste	}
386254721Semaste
387254721Semaste	if ((arg = getarg(args, 's')) != NULL)
388254721Semaste		cnf->shell_default = arg->val;
389254721Semaste
390254721Semaste	if ((arg = getarg(args, 'w')) != NULL)
391254721Semaste		cnf->default_password = boolean_val(arg->val, cnf->default_password);
392254721Semaste	if (mode == M_ADD && getarg(args, 'D')) {
393254721Semaste		if (name != NULL)
394254721Semaste			errx(EX_DATAERR, "can't combine `-D' with `-n name'");
395254721Semaste		if ((arg = getarg(args, 'u')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
396254721Semaste			if ((cnf->min_uid = (uid_t) atoi(p)) == 0)
397254721Semaste				cnf->min_uid = 1000;
398254721Semaste			if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_uid = (uid_t) atoi(p)) < cnf->min_uid)
399254721Semaste				cnf->max_uid = 32000;
400254721Semaste		}
401254721Semaste		if ((arg = getarg(args, 'i')) != NULL && (p = strtok(arg->val, ", \t")) != NULL) {
402254721Semaste			if ((cnf->min_gid = (gid_t) atoi(p)) == 0)
403254721Semaste				cnf->min_gid = 1000;
404254721Semaste			if ((p = strtok(NULL, " ,\t")) == NULL || (cnf->max_gid = (gid_t) atoi(p)) < cnf->min_gid)
405254721Semaste				cnf->max_gid = 32000;
406254721Semaste		}
407254721Semaste
408254721Semaste		if (write_userconfig(conf.config))
409254721Semaste			return (EXIT_SUCCESS);
410254721Semaste		err(EX_IOERR, "config udpate");
411254721Semaste	}
412254721Semaste
413254721Semaste	if (name != NULL)
414254721Semaste		pwd = GETPWNAM(pw_checkname(name, 0));
415254721Semaste
416254721Semaste	if (id < 0 && name == NULL)
417254721Semaste		errx(EX_DATAERR, "user name or id required");
418254721Semaste
419254721Semaste	/*
420254721Semaste	 * Update, delete & print require that the user exists
421254721Semaste	 */
422254721Semaste	if (mode == M_UPDATE || mode == M_DELETE ||
423254721Semaste	    mode == M_LOCK   || mode == M_UNLOCK) {
424254721Semaste
425254721Semaste		if (name == NULL && pwd == NULL)	/* Try harder */
426254721Semaste			pwd = GETPWUID(id);
427254721Semaste
428254721Semaste		if (pwd == NULL) {
429254721Semaste			if (name == NULL)
430254721Semaste				errx(EX_NOUSER, "no such uid `%ld'", id);
431254721Semaste			errx(EX_NOUSER, "no such user `%s'", name);
432254721Semaste		}
433254721Semaste
434254721Semaste		if (name == NULL)
435254721Semaste			name = pwd->pw_name;
436254721Semaste
437254721Semaste		/*
438254721Semaste		 * The M_LOCK and M_UNLOCK functions simply add or remove
439254721Semaste		 * a "*LOCKED*" prefix from in front of the password to
440254721Semaste		 * prevent it decoding correctly, and therefore prevents
441254721Semaste		 * access. Of course, this only prevents access via
442254721Semaste		 * password authentication (not ssh, kerberos or any
443254721Semaste		 * other method that does not use the UNIX password) but
444254721Semaste		 * that is a known limitation.
445254721Semaste		 */
446254721Semaste
447254721Semaste		if (mode == M_LOCK) {
448254721Semaste			if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) == 0)
449254721Semaste				errx(EX_DATAERR, "user '%s' is already locked", pwd->pw_name);
450254721Semaste			asprintf(&passtmp, "%s%s", locked_str, pwd->pw_passwd);
451254721Semaste			if (passtmp == NULL)	/* disaster */
452254721Semaste				errx(EX_UNAVAILABLE, "out of memory");
453254721Semaste			pwd->pw_passwd = passtmp;
454254721Semaste			edited = 1;
455254721Semaste		} else if (mode == M_UNLOCK) {
456254721Semaste			if (strncmp(pwd->pw_passwd, locked_str, sizeof(locked_str)-1) != 0)
457254721Semaste				errx(EX_DATAERR, "user '%s' is not locked", pwd->pw_name);
458254721Semaste			pwd->pw_passwd += sizeof(locked_str)-1;
459254721Semaste			edited = 1;
460254721Semaste		} else if (mode == M_DELETE)
461254721Semaste			return (delete_user(cnf, pwd, name,
462254721Semaste				    getarg(args, 'r') != NULL, mode));
463254721Semaste
464254721Semaste		/*
465254721Semaste		 * The rest is edit code
466254721Semaste		 */
467254721Semaste		if (conf.newname != NULL) {
468254721Semaste			if (strcmp(pwd->pw_name, "root") == 0)
469254721Semaste				errx(EX_DATAERR, "can't rename `root' account");
470254721Semaste			pwd->pw_name = pw_checkname(conf.newname, 0);
471254721Semaste			edited = 1;
472254721Semaste		}
473254721Semaste
474254721Semaste		if (id > 0 && isdigit((unsigned char)*arg->val)) {
475254721Semaste			pwd->pw_uid = (uid_t)id;
476254721Semaste			edited = 1;
477254721Semaste			if (pwd->pw_uid != 0 && strcmp(pwd->pw_name, "root") == 0)
478254721Semaste				errx(EX_DATAERR, "can't change uid of `root' account");
479254721Semaste			if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
480254721Semaste				warnx("WARNING: account `%s' will have a uid of 0 (superuser access!)", pwd->pw_name);
481254721Semaste		}
482254721Semaste
483254721Semaste		if ((arg = getarg(args, 'g')) != NULL && pwd->pw_uid != 0) {	/* Already checked this */
484254721Semaste			gid_t newgid = (gid_t) GETGRNAM(cnf->default_group)->gr_gid;
485254721Semaste			if (newgid != pwd->pw_gid) {
486254721Semaste				edited = 1;
487254721Semaste				pwd->pw_gid = newgid;
488254721Semaste			}
489254721Semaste		}
490254721Semaste
491254721Semaste		if ((arg = getarg(args, 'p')) != NULL) {
492254721Semaste			if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) {
493254721Semaste				if (pwd->pw_change != 0) {
494254721Semaste					pwd->pw_change = 0;
495254721Semaste					edited = 1;
496254721Semaste				}
497254721Semaste			}
498254721Semaste			else {
499254721Semaste				time_t          now = time(NULL);
500254721Semaste				time_t          expire = parse_date(now, arg->val);
501254721Semaste
502254721Semaste				if (pwd->pw_change != expire) {
503254721Semaste					pwd->pw_change = expire;
504254721Semaste					edited = 1;
505254721Semaste				}
506254721Semaste			}
507254721Semaste		}
508254721Semaste
509254721Semaste		if ((arg = getarg(args, 'e')) != NULL) {
510254721Semaste			if (*arg->val == '\0' || strcmp(arg->val, "0") == 0) {
511254721Semaste				if (pwd->pw_expire != 0) {
512254721Semaste					pwd->pw_expire = 0;
513254721Semaste					edited = 1;
514254721Semaste				}
515254721Semaste			}
516254721Semaste			else {
517254721Semaste				time_t          now = time(NULL);
518254721Semaste				time_t          expire = parse_date(now, arg->val);
519254721Semaste
520254721Semaste				if (pwd->pw_expire != expire) {
521254721Semaste					pwd->pw_expire = expire;
522254721Semaste					edited = 1;
523254721Semaste				}
524254721Semaste			}
525254721Semaste		}
526254721Semaste
527254721Semaste		if ((arg = getarg(args, 's')) != NULL) {
528254721Semaste			char *shell = shell_path(cnf->shelldir, cnf->shells, arg->val);
529254721Semaste			if (shell == NULL)
530254721Semaste				shell = "";
531254721Semaste			if (strcmp(shell, pwd->pw_shell) != 0) {
532254721Semaste				pwd->pw_shell = shell;
533254721Semaste				edited = 1;
534254721Semaste			}
535254721Semaste		}
536254721Semaste
537254721Semaste		if (getarg(args, 'L')) {
538254721Semaste			if (cnf->default_class == NULL)
539254721Semaste				cnf->default_class = "";
540254721Semaste			if (strcmp(pwd->pw_class, cnf->default_class) != 0) {
541254721Semaste				pwd->pw_class = cnf->default_class;
542254721Semaste				edited = 1;
543254721Semaste			}
544254721Semaste		}
545254721Semaste
546254721Semaste		if ((arg  = getarg(args, 'd')) != NULL) {
547254721Semaste			if (strcmp(pwd->pw_dir, arg->val))
548254721Semaste				edited = 1;
549254721Semaste			if (stat(pwd->pw_dir = arg->val, &st) == -1) {
550254721Semaste				if (getarg(args, 'm') == NULL && strcmp(pwd->pw_dir, "/nonexistent") != 0)
551254721Semaste				  warnx("WARNING: home `%s' does not exist", pwd->pw_dir);
552254721Semaste			} else if (!S_ISDIR(st.st_mode))
553254721Semaste				warnx("WARNING: home `%s' is not a directory", pwd->pw_dir);
554254721Semaste		}
555254721Semaste
556254721Semaste		if ((arg = getarg(args, 'w')) != NULL && conf.fd == -1) {
557254721Semaste			login_cap_t *lc;
558254721Semaste
559254721Semaste			lc = login_getpwclass(pwd);
560254721Semaste			if (lc == NULL ||
561254721Semaste			    login_setcryptfmt(lc, "sha512", NULL) == NULL)
562254721Semaste				warn("setting crypt(3) format");
563254721Semaste			login_close(lc);
564254721Semaste			pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
565254721Semaste			edited = 1;
566254721Semaste		}
567254721Semaste
568254721Semaste	} else {
569254721Semaste		login_cap_t *lc;
570254721Semaste
571254721Semaste		/*
572254721Semaste		 * Add code
573254721Semaste		 */
574254721Semaste
575254721Semaste		if (name == NULL)	/* Required */
576254721Semaste			errx(EX_DATAERR, "login name required");
577254721Semaste		else if ((pwd = GETPWNAM(name)) != NULL)	/* Exists */
578254721Semaste			errx(EX_DATAERR, "login name `%s' already exists", name);
579254721Semaste
580254721Semaste		/*
581254721Semaste		 * Now, set up defaults for a new user
582254721Semaste		 */
583254721Semaste		pwd = &fakeuser;
584254721Semaste		pwd->pw_name = name;
585254721Semaste		pwd->pw_class = cnf->default_class ? cnf->default_class : "";
586254721Semaste		pwd->pw_uid = pw_uidpolicy(cnf, id);
587254721Semaste		pwd->pw_gid = pw_gidpolicy(args, pwd->pw_name, (gid_t) pwd->pw_uid);
588254721Semaste		pwd->pw_change = pw_pwdpolicy(cnf, args);
589254721Semaste		pwd->pw_expire = pw_exppolicy(cnf, args);
590254721Semaste		pwd->pw_dir = pw_homepolicy(cnf, args, pwd->pw_name);
591254721Semaste		pwd->pw_shell = pw_shellpolicy(cnf, args, NULL);
592254721Semaste		lc = login_getpwclass(pwd);
593254721Semaste		if (lc == NULL || login_setcryptfmt(lc, "sha512", NULL) == NULL)
594254721Semaste			warn("setting crypt(3) format");
595254721Semaste		login_close(lc);
596254721Semaste		pwd->pw_passwd = pw_password(cnf, args, pwd->pw_name);
597254721Semaste		edited = 1;
598254721Semaste
599254721Semaste		if (pwd->pw_uid == 0 && strcmp(pwd->pw_name, "root") != 0)
600254721Semaste			warnx("WARNING: new account `%s' has a uid of 0 (superuser access!)", pwd->pw_name);
601254721Semaste	}
602254721Semaste
603254721Semaste	/*
604254721Semaste	 * Shared add/edit code
605254721Semaste	 */
606254721Semaste	if ((arg = getarg(args, 'c')) != NULL) {
607254721Semaste		char	*gecos = pw_checkname(arg->val, 1);
608254721Semaste		if (strcmp(pwd->pw_gecos, gecos) != 0) {
609254721Semaste			pwd->pw_gecos = gecos;
610254721Semaste			edited = 1;
611254721Semaste		}
612254721Semaste	}
613254721Semaste
614254721Semaste	if (conf.fd != -1)
615254721Semaste		edited = set_passwd(pwd, mode == M_UPDATE);
616254721Semaste
617254721Semaste	/*
618254721Semaste	 * Special case: -N only displays & exits
619254721Semaste	 */
620254721Semaste	if (conf.dryrun)
621254721Semaste		return print_user(pwd);
622254721Semaste
623254721Semaste	if (mode == M_ADD) {
624254721Semaste		edited = 1;	/* Always */
625254721Semaste		rc = addpwent(pwd);
626254721Semaste		if (rc == -1)
627254721Semaste			errx(EX_IOERR, "user '%s' already exists",
628254721Semaste			    pwd->pw_name);
629254721Semaste		else if (rc != 0)
630254721Semaste			err(EX_IOERR, "passwd file update");
631254721Semaste		if (cnf->nispasswd && *cnf->nispasswd=='/') {
632254721Semaste			rc = addnispwent(cnf->nispasswd, pwd);
633254721Semaste			if (rc == -1)
634254721Semaste				warnx("User '%s' already exists in NIS passwd", pwd->pw_name);
635254721Semaste			else
636254721Semaste				warn("NIS passwd update");
637254721Semaste			/* NOTE: we treat NIS-only update errors as non-fatal */
638254721Semaste		}
639254721Semaste	} else if (mode == M_UPDATE || mode == M_LOCK || mode == M_UNLOCK) {
640254721Semaste		if (edited) {	/* Only updated this if required */
641254721Semaste			rc = chgpwent(name, pwd);
642254721Semaste			if (rc == -1)
643254721Semaste				errx(EX_IOERR, "user '%s' does not exist (NIS?)", pwd->pw_name);
644254721Semaste			else if (rc != 0)
645254721Semaste				err(EX_IOERR, "passwd file update");
646254721Semaste			if ( cnf->nispasswd && *cnf->nispasswd=='/') {
647254721Semaste				rc = chgnispwent(cnf->nispasswd, name, pwd);
648254721Semaste				if (rc == -1)
649254721Semaste					warn("User '%s' not found in NIS passwd", pwd->pw_name);
650254721Semaste				else
651254721Semaste					warn("NIS passwd update");
652254721Semaste				/* NOTE: NIS-only update errors are not fatal */
653254721Semaste			}
654254721Semaste		}
655254721Semaste	}
656254721Semaste
657254721Semaste	/*
658254721Semaste	 * Ok, user is created or changed - now edit group file
659254721Semaste	 */
660254721Semaste
661254721Semaste	if (mode == M_ADD || getarg(args, 'G') != NULL) {
662254721Semaste		int i, j;
663254721Semaste		/* First remove the user from all group */
664254721Semaste		SETGRENT();
665254721Semaste		while ((grp = GETGRENT()) != NULL) {
666254721Semaste			char group[MAXLOGNAME];
667254721Semaste			if (grp->gr_mem == NULL)
668254721Semaste				continue;
669254721Semaste			for (i = 0; grp->gr_mem[i] != NULL; i++) {
670254721Semaste				if (strcmp(grp->gr_mem[i] , pwd->pw_name) != 0)
671254721Semaste					continue;
672254721Semaste				for (j = i; grp->gr_mem[j] != NULL ; j++)
673254721Semaste					grp->gr_mem[j] = grp->gr_mem[j+1];
674254721Semaste				strlcpy(group, grp->gr_name, MAXLOGNAME);
675254721Semaste				chggrent(group, grp);
676254721Semaste			}
677254721Semaste		}
678254721Semaste		ENDGRENT();
679254721Semaste
680254721Semaste		/* now add to group where needed */
681254721Semaste		for (i = 0; cnf->groups[i] != NULL; i++) {
682254721Semaste			grp = GETGRNAM(cnf->groups[i]);
683254721Semaste			grp = gr_add(grp, pwd->pw_name);
684254721Semaste			/*
685254721Semaste			 * grp can only be NULL in 2 cases:
686254721Semaste			 * - the new member is already a member
687254721Semaste			 * - a problem with memory occurs
688254721Semaste			 * in both cases we want to skip now.
689254721Semaste			 */
690254721Semaste			if (grp == NULL)
691254721Semaste				continue;
692254721Semaste			chggrent(cnf->groups[i], grp);
693254721Semaste			free(grp);
694254721Semaste		}
695254721Semaste	}
696254721Semaste
697254721Semaste
698254721Semaste	/* go get a current version of pwd */
699254721Semaste	pwd = GETPWNAM(name);
700254721Semaste	if (pwd == NULL) {
701254721Semaste		/* This will fail when we rename, so special case that */
702254721Semaste		if (mode == M_UPDATE && conf.newname != NULL) {
703254721Semaste			name = conf.newname;		/* update new name */
704254721Semaste			pwd = GETPWNAM(name);	/* refetch renamed rec */
705254721Semaste		}
706254721Semaste	}
707254721Semaste	if (pwd == NULL)	/* can't go on without this */
708254721Semaste		errx(EX_NOUSER, "user '%s' disappeared during update", name);
709254721Semaste
710254721Semaste	grp = GETGRGID(pwd->pw_gid);
711254721Semaste	pw_log(cnf, mode, W_USER, "%s(%u):%s(%u):%s:%s:%s",
712254721Semaste	       pwd->pw_name, pwd->pw_uid,
713254721Semaste	    grp ? grp->gr_name : "unknown", (grp ? grp->gr_gid : (uid_t)-1),
714254721Semaste	       pwd->pw_gecos, pwd->pw_dir, pwd->pw_shell);
715254721Semaste
716254721Semaste	/*
717254721Semaste	 * If adding, let's touch and chown the user's mail file. This is not
718254721Semaste	 * strictly necessary under BSD with a 0755 maildir but it also
719254721Semaste	 * doesn't hurt anything to create the empty mailfile
720254721Semaste	 */
721254721Semaste	if (mode == M_ADD) {
722254721Semaste		if (PWALTDIR() != PWF_ALT) {
723254721Semaste			arg = getarg(args, 'R');
724254721Semaste			snprintf(path, sizeof(path), "%s%s/%s",
725254721Semaste			    arg ? arg->val : "", _PATH_MAILDIR, pwd->pw_name);
726254721Semaste			close(open(path, O_RDWR | O_CREAT, 0600));	/* Preserve contents &
727254721Semaste									 * mtime */
728254721Semaste			chown(path, pwd->pw_uid, pwd->pw_gid);
729254721Semaste		}
730254721Semaste	}
731254721Semaste
732254721Semaste	/*
733254721Semaste	 * Let's create and populate the user's home directory. Note
734254721Semaste	 * that this also `works' for editing users if -m is used, but
735254721Semaste	 * existing files will *not* be overwritten.
736254721Semaste	 */
737254721Semaste	if (PWALTDIR() != PWF_ALT && getarg(args, 'm') != NULL && pwd->pw_dir &&
738254721Semaste	    *pwd->pw_dir == '/' && pwd->pw_dir[1])
739254721Semaste		create_and_populate_homedir(mode, pwd);
740254721Semaste
741254721Semaste	/*
742254721Semaste	 * Finally, send mail to the new user as well, if we are asked to
743254721Semaste	 */
744254721Semaste	if (mode == M_ADD && !PWALTDIR() && cnf->newmail && *cnf->newmail && (fp = fopen(cnf->newmail, "r")) != NULL) {
745254721Semaste		FILE           *pfp = popen(_PATH_SENDMAIL " -t", "w");
746254721Semaste
747254721Semaste		if (pfp == NULL)
748254721Semaste			warn("sendmail");
749254721Semaste		else {
750254721Semaste			fprintf(pfp, "From: root\n" "To: %s\n" "Subject: Welcome!\n\n", pwd->pw_name);
751254721Semaste			while (fgets(line, sizeof(line), fp) != NULL) {
752254721Semaste				/* Do substitutions? */
753254721Semaste				fputs(line, pfp);
754254721Semaste			}
755254721Semaste			pclose(pfp);
756254721Semaste			pw_log(cnf, mode, W_USER, "%s(%u) new user mail sent",
757254721Semaste			    pwd->pw_name, pwd->pw_uid);
758254721Semaste		}
759254721Semaste		fclose(fp);
760254721Semaste	}
761254721Semaste
762254721Semaste	return EXIT_SUCCESS;
763254721Semaste}
764254721Semaste
765254721Semaste
766254721Semastestatic          uid_t
767254721Semastepw_uidpolicy(struct userconf * cnf, long id)
768254721Semaste{
769254721Semaste	struct passwd  *pwd;
770254721Semaste	uid_t           uid = (uid_t) - 1;
771254721Semaste
772254721Semaste	/*
773254721Semaste	 * Check the given uid, if any
774254721Semaste	 */
775254721Semaste	if (id > 0) {
776254721Semaste		uid = (uid_t) id;
777254721Semaste
778254721Semaste		if ((pwd = GETPWUID(uid)) != NULL && conf.checkduplicate)
779254721Semaste			errx(EX_DATAERR, "uid `%u' has already been allocated", pwd->pw_uid);
780254721Semaste	} else {
781254721Semaste		struct bitmap   bm;
782254721Semaste
783254721Semaste		/*
784254721Semaste		 * We need to allocate the next available uid under one of
785254721Semaste		 * two policies a) Grab the first unused uid b) Grab the
786254721Semaste		 * highest possible unused uid
787254721Semaste		 */
788254721Semaste		if (cnf->min_uid >= cnf->max_uid) {	/* Sanity
789254721Semaste							 * claus^H^H^H^Hheck */
790254721Semaste			cnf->min_uid = 1000;
791254721Semaste			cnf->max_uid = 32000;
792254721Semaste		}
793254721Semaste		bm = bm_alloc(cnf->max_uid - cnf->min_uid + 1);
794254721Semaste
795254721Semaste		/*
796254721Semaste		 * Now, let's fill the bitmap from the password file
797254721Semaste		 */
798254721Semaste		SETPWENT();
799254721Semaste		while ((pwd = GETPWENT()) != NULL)
800254721Semaste			if (pwd->pw_uid >= (uid_t) cnf->min_uid && pwd->pw_uid <= (uid_t) cnf->max_uid)
801254721Semaste				bm_setbit(&bm, pwd->pw_uid - cnf->min_uid);
802254721Semaste		ENDPWENT();
803254721Semaste
804254721Semaste		/*
805254721Semaste		 * Then apply the policy, with fallback to reuse if necessary
806254721Semaste		 */
807254721Semaste		if (cnf->reuse_uids || (uid = (uid_t) (bm_lastset(&bm) + cnf->min_uid + 1)) > cnf->max_uid)
808254721Semaste			uid = (uid_t) (bm_firstunset(&bm) + cnf->min_uid);
809254721Semaste
810254721Semaste		/*
811254721Semaste		 * Another sanity check
812254721Semaste		 */
813254721Semaste		if (uid < cnf->min_uid || uid > cnf->max_uid)
814254721Semaste			errx(EX_SOFTWARE, "unable to allocate a new uid - range fully used");
815254721Semaste		bm_dealloc(&bm);
816254721Semaste	}
817254721Semaste	return uid;
818254721Semaste}
819254721Semaste
820254721Semaste
821254721Semastestatic          uid_t
822254721Semastepw_gidpolicy(struct cargs * args, char *nam, gid_t prefer)
823254721Semaste{
824254721Semaste	struct group   *grp;
825254721Semaste	gid_t           gid = (uid_t) - 1;
826254721Semaste	struct carg    *a_gid = getarg(args, 'g');
827254721Semaste	struct userconf	*cnf = conf.userconf;
828254721Semaste
829254721Semaste	/*
830254721Semaste	 * If no arg given, see if default can help out
831254721Semaste	 */
832254721Semaste	if (a_gid == NULL && cnf->default_group && *cnf->default_group)
833254721Semaste		a_gid = addarg(args, 'g', cnf->default_group);
834254721Semaste
835254721Semaste	/*
836254721Semaste	 * Check the given gid, if any
837254721Semaste	 */
838254721Semaste	SETGRENT();
839254721Semaste	if (a_gid != NULL) {
840254721Semaste		if ((grp = GETGRNAM(a_gid->val)) == NULL) {
841254721Semaste			gid = (gid_t) atol(a_gid->val);
842254721Semaste			if ((gid == 0 && !isdigit((unsigned char)*a_gid->val)) || (grp = GETGRGID(gid)) == NULL)
843254721Semaste				errx(EX_NOUSER, "group `%s' is not defined", a_gid->val);
844254721Semaste		}
845254721Semaste		gid = grp->gr_gid;
846254721Semaste	} else if ((grp = GETGRNAM(nam)) != NULL &&
847254721Semaste	    (grp->gr_mem == NULL || grp->gr_mem[0] == NULL)) {
848254721Semaste		gid = grp->gr_gid;  /* Already created? Use it anyway... */
849254721Semaste	} else {
850254721Semaste		struct cargs    grpargs;
851254721Semaste		char            tmp[32];
852254721Semaste
853254721Semaste		LIST_INIT(&grpargs);
854254721Semaste
855254721Semaste		/*
856254721Semaste		 * We need to auto-create a group with the user's name. We
857254721Semaste		 * can send all the appropriate output to our sister routine
858254721Semaste		 * bit first see if we can create a group with gid==uid so we
859254721Semaste		 * can keep the user and group ids in sync. We purposely do
860254721Semaste		 * NOT check the gid range if we can force the sync. If the
861254721Semaste		 * user's name dups an existing group, then the group add
862254721Semaste		 * function will happily handle that case for us and exit.
863254721Semaste		 */
864254721Semaste		if (GETGRGID(prefer) == NULL) {
865254721Semaste			snprintf(tmp, sizeof(tmp), "%u", prefer);
866254721Semaste			addarg(&grpargs, 'g', tmp);
867254721Semaste		}
868254721Semaste		if (conf.dryrun) {
869254721Semaste			gid = pw_groupnext(cnf, true);
870254721Semaste		} else {
871254721Semaste			pw_group(M_ADD, nam, -1, &grpargs);
872254721Semaste			if ((grp = GETGRNAM(nam)) != NULL)
873254721Semaste				gid = grp->gr_gid;
874254721Semaste		}
875254721Semaste		a_gid = LIST_FIRST(&grpargs);
876254721Semaste		while (a_gid != NULL) {
877254721Semaste			struct carg    *t = LIST_NEXT(a_gid, list);
878254721Semaste			LIST_REMOVE(a_gid, list);
879254721Semaste			a_gid = t;
880254721Semaste		}
881254721Semaste	}
882254721Semaste	ENDGRENT();
883254721Semaste	return gid;
884254721Semaste}
885254721Semaste
886254721Semaste
887254721Semastestatic          time_t
888254721Semastepw_pwdpolicy(struct userconf * cnf, struct cargs * args)
889254721Semaste{
890254721Semaste	time_t          result = 0;
891254721Semaste	time_t          now = time(NULL);
892254721Semaste	struct carg    *arg = getarg(args, 'p');
893254721Semaste
894254721Semaste	if (arg != NULL) {
895254721Semaste		if ((result = parse_date(now, arg->val)) == now)
896254721Semaste			errx(EX_DATAERR, "invalid date/time `%s'", arg->val);
897254721Semaste	} else if (cnf->password_days > 0)
898254721Semaste		result = now + ((long) cnf->password_days * 86400L);
899254721Semaste	return result;
900254721Semaste}
901254721Semaste
902254721Semaste
903254721Semastestatic          time_t
904254721Semastepw_exppolicy(struct userconf * cnf, struct cargs * args)
905254721Semaste{
906254721Semaste	time_t          result = 0;
907254721Semaste	time_t          now = time(NULL);
908254721Semaste	struct carg    *arg = getarg(args, 'e');
909254721Semaste
910254721Semaste	if (arg != NULL) {
911254721Semaste		if ((result = parse_date(now, arg->val)) == now)
912254721Semaste			errx(EX_DATAERR, "invalid date/time `%s'", arg->val);
913254721Semaste	} else if (cnf->expire_days > 0)
914254721Semaste		result = now + ((long) cnf->expire_days * 86400L);
915254721Semaste	return result;
916254721Semaste}
917254721Semaste
918254721Semaste
919254721Semastestatic char    *
920254721Semastepw_homepolicy(struct userconf * cnf, struct cargs * args, char const * user)
921254721Semaste{
922254721Semaste	struct carg    *arg = getarg(args, 'd');
923254721Semaste	static char     home[128];
924254721Semaste
925254721Semaste	if (arg)
926254721Semaste		return (arg->val);
927254721Semaste
928254721Semaste	if (cnf->home == NULL || *cnf->home == '\0')
929254721Semaste		errx(EX_CONFIG, "no base home directory set");
930254721Semaste	snprintf(home, sizeof(home), "%s/%s", cnf->home, user);
931254721Semaste
932254721Semaste	return (home);
933254721Semaste}
934254721Semaste
935254721Semastestatic char    *
936254721Semasteshell_path(char const * path, char *shells[], char *sh)
937254721Semaste{
938254721Semaste	if (sh != NULL && (*sh == '/' || *sh == '\0'))
939254721Semaste		return sh;	/* specified full path or forced none */
940254721Semaste	else {
941254721Semaste		char           *p;
942254721Semaste		char            paths[_UC_MAXLINE];
943254721Semaste
944254721Semaste		/*
945254721Semaste		 * We need to search paths
946254721Semaste		 */
947254721Semaste		strlcpy(paths, path, sizeof(paths));
948254721Semaste		for (p = strtok(paths, ": \t\r\n"); p != NULL; p = strtok(NULL, ": \t\r\n")) {
949254721Semaste			int             i;
950254721Semaste			static char     shellpath[256];
951254721Semaste
952254721Semaste			if (sh != NULL) {
953254721Semaste				snprintf(shellpath, sizeof(shellpath), "%s/%s", p, sh);
954254721Semaste				if (access(shellpath, X_OK) == 0)
955254721Semaste					return shellpath;
956254721Semaste			} else
957254721Semaste				for (i = 0; i < _UC_MAXSHELLS && shells[i] != NULL; i++) {
958254721Semaste					snprintf(shellpath, sizeof(shellpath), "%s/%s", p, shells[i]);
959254721Semaste					if (access(shellpath, X_OK) == 0)
960254721Semaste						return shellpath;
961254721Semaste				}
962254721Semaste		}
963254721Semaste		if (sh == NULL)
964254721Semaste			errx(EX_OSFILE, "can't find shell `%s' in shell paths", sh);
965254721Semaste		errx(EX_CONFIG, "no default shell available or defined");
966254721Semaste		return NULL;
967254721Semaste	}
968254721Semaste}
969254721Semaste
970254721Semaste
971254721Semastestatic char    *
972254721Semastepw_shellpolicy(struct userconf * cnf, struct cargs * args, char *newshell)
973254721Semaste{
974254721Semaste	char           *sh = newshell;
975254721Semaste	struct carg    *arg = getarg(args, 's');
976254721Semaste
977254721Semaste	if (newshell == NULL && arg != NULL)
978254721Semaste		sh = arg->val;
979254721Semaste	return shell_path(cnf->shelldir, cnf->shells, sh ? sh : cnf->shell_default);
980254721Semaste}
981254721Semaste
982254721Semaste#define	SALTSIZE	32
983254721Semaste
984254721Semastestatic char const chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ./";
985254721Semaste
986254721Semastechar           *
987254721Semastepw_pwcrypt(char *password)
988254721Semaste{
989254721Semaste	int             i;
990254721Semaste	char            salt[SALTSIZE + 1];
991254721Semaste	char		*cryptpw;
992254721Semaste
993254721Semaste	static char     buf[256];
994254721Semaste
995254721Semaste	/*
996254721Semaste	 * Calculate a salt value
997254721Semaste	 */
998254721Semaste	for (i = 0; i < SALTSIZE; i++)
999254721Semaste		salt[i] = chars[arc4random_uniform(sizeof(chars) - 1)];
1000254721Semaste	salt[SALTSIZE] = '\0';
1001254721Semaste
1002254721Semaste	cryptpw = crypt(password, salt);
1003254721Semaste	if (cryptpw == NULL)
1004254721Semaste		errx(EX_CONFIG, "crypt(3) failure");
1005254721Semaste	return strcpy(buf, cryptpw);
1006254721Semaste}
1007254721Semaste
1008254721Semaste
1009254721Semastestatic char    *
1010254721Semastepw_password(struct userconf * cnf, struct cargs * args, char const * user)
1011254721Semaste{
1012254721Semaste	int             i, l;
1013254721Semaste	char            pwbuf[32];
1014254721Semaste
1015254721Semaste	switch (cnf->default_password) {
1016254721Semaste	case -1:		/* Random password */
1017254721Semaste		l = (arc4random() % 8 + 8);	/* 8 - 16 chars */
1018254721Semaste		for (i = 0; i < l; i++)
1019254721Semaste			pwbuf[i] = chars[arc4random_uniform(sizeof(chars)-1)];
1020254721Semaste		pwbuf[i] = '\0';
1021254721Semaste
1022254721Semaste		/*
1023254721Semaste		 * We give this information back to the user
1024254721Semaste		 */
1025254721Semaste		if (conf.fd == -1 && !conf.dryrun) {
1026254721Semaste			if (isatty(STDOUT_FILENO))
1027254721Semaste				printf("Password for '%s' is: ", user);
1028254721Semaste			printf("%s\n", pwbuf);
1029254721Semaste			fflush(stdout);
1030254721Semaste		}
1031254721Semaste		break;
1032254721Semaste
1033254721Semaste	case -2:		/* No password at all! */
1034254721Semaste		return "";
1035254721Semaste
1036254721Semaste	case 0:		/* No login - default */
1037254721Semaste	default:
1038254721Semaste		return "*";
1039254721Semaste
1040254721Semaste	case 1:		/* user's name */
1041254721Semaste		strlcpy(pwbuf, user, sizeof(pwbuf));
1042254721Semaste		break;
1043254721Semaste	}
1044254721Semaste	return pw_pwcrypt(pwbuf);
1045254721Semaste}
1046254721Semaste
1047254721Semastestatic int
1048254721Semastedelete_user(struct userconf *cnf, struct passwd *pwd, char *name,
1049254721Semaste    int delete, int mode)
1050254721Semaste{
1051254721Semaste	char		 file[MAXPATHLEN];
1052254721Semaste	char		 home[MAXPATHLEN];
1053254721Semaste	uid_t		 uid = pwd->pw_uid;
1054254721Semaste	struct group	*gr, *grp;
1055254721Semaste	char		 grname[LOGNAMESIZE];
1056254721Semaste	int		 rc;
1057254721Semaste	struct stat	 st;
1058254721Semaste
1059254721Semaste	if (strcmp(pwd->pw_name, "root") == 0)
1060254721Semaste		errx(EX_DATAERR, "cannot remove user 'root'");
1061254721Semaste
1062254721Semaste	if (!PWALTDIR()) {
1063254721Semaste		/*
1064254721Semaste		 * Remove opie record from /etc/opiekeys
1065254721Semaste		*/
1066254721Semaste
1067254721Semaste		rmopie(pwd->pw_name);
1068254721Semaste
1069254721Semaste		/*
1070254721Semaste		 * Remove crontabs
1071254721Semaste		 */
1072254721Semaste		snprintf(file, sizeof(file), "/var/cron/tabs/%s", pwd->pw_name);
1073254721Semaste		if (access(file, F_OK) == 0) {
1074254721Semaste			snprintf(file, sizeof(file), "crontab -u %s -r", pwd->pw_name);
1075254721Semaste			system(file);
1076254721Semaste		}
1077254721Semaste	}
1078254721Semaste	/*
1079254721Semaste	 * Save these for later, since contents of pwd may be
1080254721Semaste	 * invalidated by deletion
1081254721Semaste	 */
1082254721Semaste	snprintf(file, sizeof(file), "%s/%s", _PATH_MAILDIR, pwd->pw_name);
1083254721Semaste	strlcpy(home, pwd->pw_dir, sizeof(home));
1084254721Semaste	gr = GETGRGID(pwd->pw_gid);
1085254721Semaste	if (gr != NULL)
1086254721Semaste		strlcpy(grname, gr->gr_name, LOGNAMESIZE);
1087254721Semaste	else
1088254721Semaste		grname[0] = '\0';
1089254721Semaste
1090254721Semaste	rc = delpwent(pwd);
1091254721Semaste	if (rc == -1)
1092254721Semaste		err(EX_IOERR, "user '%s' does not exist", pwd->pw_name);
1093254721Semaste	else if (rc != 0)
1094254721Semaste		err(EX_IOERR, "passwd update");
1095254721Semaste
1096254721Semaste	if (cnf->nispasswd && *cnf->nispasswd=='/') {
1097254721Semaste		rc = delnispwent(cnf->nispasswd, name);
1098254721Semaste		if (rc == -1)
1099254721Semaste			warnx("WARNING: user '%s' does not exist in NIS passwd", pwd->pw_name);
1100254721Semaste		else if (rc != 0)
1101254721Semaste			warn("WARNING: NIS passwd update");
1102254721Semaste		/* non-fatal */
1103254721Semaste	}
1104254721Semaste
1105254721Semaste	grp = GETGRNAM(name);
1106254721Semaste	if (grp != NULL &&
1107254721Semaste	    (grp->gr_mem == NULL || *grp->gr_mem == NULL) &&
1108254721Semaste	    strcmp(name, grname) == 0)
1109254721Semaste		delgrent(GETGRNAM(name));
1110254721Semaste	SETGRENT();
1111254721Semaste	while ((grp = GETGRENT()) != NULL) {
1112254721Semaste		int i, j;
1113254721Semaste		char group[MAXLOGNAME];
1114254721Semaste		if (grp->gr_mem == NULL)
1115254721Semaste			continue;
1116254721Semaste
1117254721Semaste		for (i = 0; grp->gr_mem[i] != NULL; i++) {
1118254721Semaste			if (strcmp(grp->gr_mem[i], name) != 0)
1119254721Semaste				continue;
1120254721Semaste
1121254721Semaste			for (j = i; grp->gr_mem[j] != NULL; j++)
1122254721Semaste				grp->gr_mem[j] = grp->gr_mem[j+1];
1123254721Semaste			strlcpy(group, grp->gr_name, MAXLOGNAME);
1124254721Semaste			chggrent(group, grp);
1125254721Semaste		}
1126254721Semaste	}
1127254721Semaste	ENDGRENT();
1128254721Semaste
1129254721Semaste	pw_log(cnf, mode, W_USER, "%s(%u) account removed", name, uid);
1130254721Semaste
1131254721Semaste	if (!PWALTDIR()) {
1132254721Semaste		/*
1133254721Semaste		 * Remove mail file
1134254721Semaste		 */
1135254721Semaste		remove(file);
1136254721Semaste
1137254721Semaste		/*
1138254721Semaste		 * Remove at jobs
1139254721Semaste		 */
1140254721Semaste		if (getpwuid(uid) == NULL)
1141254721Semaste			rmat(uid);
1142254721Semaste
1143254721Semaste		/*
1144254721Semaste		 * Remove home directory and contents
1145254721Semaste		 */
1146254721Semaste		if (delete && *home == '/' && getpwuid(uid) == NULL &&
1147254721Semaste		    stat(home, &st) != -1) {
1148254721Semaste			rm_r(home, uid);
1149254721Semaste			pw_log(cnf, mode, W_USER, "%s(%u) home '%s' %sremoved",
1150254721Semaste			       name, uid, home,
1151254721Semaste			       stat(home, &st) == -1 ? "" : "not completely ");
1152254721Semaste		}
1153254721Semaste	}
1154254721Semaste
1155269024Semaste	return (EXIT_SUCCESS);
1156269024Semaste}
1157269024Semaste
1158254721Semastestatic int
1159254721Semasteprint_user(struct passwd * pwd)
1160254721Semaste{
1161254721Semaste	if (!conf.pretty) {
1162254721Semaste		char            *buf;
1163269024Semaste
1164254721Semaste		buf = conf.v7 ? pw_make_v7(pwd) : pw_make(pwd);
1165269024Semaste		printf("%s\n", buf);
1166269024Semaste		free(buf);
1167269024Semaste	} else {
1168254721Semaste		int		j;
1169254721Semaste		char           *p;
1170254721Semaste		struct group   *grp = GETGRGID(pwd->pw_gid);
1171254721Semaste		char            uname[60] = "User &", office[60] = "[None]",
1172254721Semaste		                wphone[60] = "[None]", hphone[60] = "[None]";
1173254721Semaste		char		acexpire[32] = "[None]", pwexpire[32] = "[None]";
1174254721Semaste		struct tm *    tptr;
1175254721Semaste
1176254721Semaste		if ((p = strtok(pwd->pw_gecos, ",")) != NULL) {
1177254721Semaste			strlcpy(uname, p, sizeof(uname));
1178254721Semaste			if ((p = strtok(NULL, ",")) != NULL) {
1179254721Semaste				strlcpy(office, p, sizeof(office));
1180254721Semaste				if ((p = strtok(NULL, ",")) != NULL) {
1181254721Semaste					strlcpy(wphone, p, sizeof(wphone));
1182254721Semaste					if ((p = strtok(NULL, "")) != NULL) {
1183254721Semaste						strlcpy(hphone, p,
1184254721Semaste						    sizeof(hphone));
1185254721Semaste					}
1186254721Semaste				}
1187254721Semaste			}
1188254721Semaste		}
1189254721Semaste		/*
1190254721Semaste		 * Handle '&' in gecos field
1191254721Semaste		 */
1192254721Semaste		if ((p = strchr(uname, '&')) != NULL) {
1193254721Semaste			int             l = strlen(pwd->pw_name);
1194254721Semaste			int             m = strlen(p);
1195254721Semaste
1196254721Semaste			memmove(p + l, p + 1, m);
1197254721Semaste			memmove(p, pwd->pw_name, l);
1198254721Semaste			*p = (char) toupper((unsigned char)*p);
1199254721Semaste		}
1200254721Semaste		if (pwd->pw_expire > (time_t)0 && (tptr = localtime(&pwd->pw_expire)) != NULL)
1201254721Semaste			strftime(acexpire, sizeof acexpire, "%c", tptr);
1202254721Semaste		if (pwd->pw_change > (time_t)0 && (tptr = localtime(&pwd->pw_change)) != NULL)
1203254721Semaste			strftime(pwexpire, sizeof pwexpire, "%c", tptr);
1204254721Semaste		printf("Login Name: %-15s   #%-12u Group: %-15s   #%u\n"
1205254721Semaste		       " Full Name: %s\n"
1206254721Semaste		       "      Home: %-26.26s      Class: %s\n"
1207254721Semaste		       "     Shell: %-26.26s     Office: %s\n"
1208254721Semaste		       "Work Phone: %-26.26s Home Phone: %s\n"
1209254721Semaste		       "Acc Expire: %-26.26s Pwd Expire: %s\n",
1210254721Semaste		       pwd->pw_name, pwd->pw_uid,
1211254721Semaste		       grp ? grp->gr_name : "(invalid)", pwd->pw_gid,
1212254721Semaste		       uname, pwd->pw_dir, pwd->pw_class,
1213254721Semaste		       pwd->pw_shell, office, wphone, hphone,
1214254721Semaste		       acexpire, pwexpire);
1215254721Semaste	        SETGRENT();
1216254721Semaste		j = 0;
1217254721Semaste		while ((grp=GETGRENT()) != NULL)
1218254721Semaste		{
1219254721Semaste			int     i = 0;
1220254721Semaste			if (grp->gr_mem != NULL) {
1221254721Semaste				while (grp->gr_mem[i] != NULL)
1222254721Semaste				{
1223254721Semaste					if (strcmp(grp->gr_mem[i], pwd->pw_name)==0)
1224254721Semaste					{
1225254721Semaste						printf(j++ == 0 ? "    Groups: %s" : ",%s", grp->gr_name);
1226254721Semaste						break;
1227254721Semaste					}
1228254721Semaste					++i;
1229254721Semaste				}
1230254721Semaste			}
1231254721Semaste		}
1232254721Semaste		ENDGRENT();
1233254721Semaste		printf("%s", j ? "\n" : "");
1234254721Semaste	}
1235254721Semaste	return EXIT_SUCCESS;
1236254721Semaste}
1237254721Semaste
1238254721Semastechar *
1239254721Semastepw_checkname(char *name, int gecos)
1240254721Semaste{
1241254721Semaste	char showch[8];
1242254721Semaste	const char *badchars, *ch, *showtype;
1243254721Semaste	int reject;
1244254721Semaste
1245254721Semaste	ch = name;
1246254721Semaste	reject = 0;
1247254721Semaste	if (gecos) {
1248254721Semaste		/* See if the name is valid as a gecos (comment) field. */
1249254721Semaste		badchars = ":!@";
1250254721Semaste		showtype = "gecos field";
1251254721Semaste	} else {
1252254721Semaste		/* See if the name is valid as a userid or group. */
1253254721Semaste		badchars = " ,\t:+&#%$^()!@~*?<>=|\\/\"";
1254254721Semaste		showtype = "userid/group name";
1255254721Semaste		/* Userids and groups can not have a leading '-'. */
1256254721Semaste		if (*ch == '-')
1257254721Semaste			reject = 1;
1258254721Semaste	}
1259254721Semaste	if (!reject) {
1260254721Semaste		while (*ch) {
1261254721Semaste			if (strchr(badchars, *ch) != NULL || *ch < ' ' ||
1262254721Semaste			    *ch == 127) {
1263254721Semaste				reject = 1;
1264254721Semaste				break;
1265254721Semaste			}
1266254721Semaste			/* 8-bit characters are only allowed in GECOS fields */
1267254721Semaste			if (!gecos && (*ch & 0x80)) {
1268254721Semaste				reject = 1;
1269254721Semaste				break;
1270254721Semaste			}
1271254721Semaste			ch++;
1272254721Semaste		}
1273254721Semaste	}
1274254721Semaste	/*
1275254721Semaste	 * A `$' is allowed as the final character for userids and groups,
1276254721Semaste	 * mainly for the benefit of samba.
1277254721Semaste	 */
1278254721Semaste	if (reject && !gecos) {
1279254721Semaste		if (*ch == '$' && *(ch + 1) == '\0') {
1280254721Semaste			reject = 0;
1281254721Semaste			ch++;
1282254721Semaste		}
1283254721Semaste	}
1284254721Semaste	if (reject) {
1285254721Semaste		snprintf(showch, sizeof(showch), (*ch >= ' ' && *ch < 127)
1286254721Semaste		    ? "`%c'" : "0x%02x", *ch);
1287254721Semaste		errx(EX_DATAERR, "invalid character %s at position %td in %s",
1288254721Semaste		    showch, (ch - name), showtype);
1289254721Semaste	}
1290254721Semaste	if (!gecos && (ch - name) > LOGNAMESIZE)
1291254721Semaste		errx(EX_DATAERR, "name too long `%s' (max is %d)", name,
1292254721Semaste		    LOGNAMESIZE);
1293254721Semaste
1294254721Semaste	return (name);
1295254721Semaste}
1296254721Semaste
1297254721Semaste
1298254721Semastestatic void
1299254721Semastermat(uid_t uid)
1300254721Semaste{
1301254721Semaste	DIR            *d = opendir("/var/at/jobs");
1302254721Semaste
1303254721Semaste	if (d != NULL) {
1304254721Semaste		struct dirent  *e;
1305254721Semaste
1306254721Semaste		while ((e = readdir(d)) != NULL) {
1307254721Semaste			struct stat     st;
1308254721Semaste
1309254721Semaste			if (strncmp(e->d_name, ".lock", 5) != 0 &&
1310254721Semaste			    stat(e->d_name, &st) == 0 &&
1311254721Semaste			    !S_ISDIR(st.st_mode) &&
1312254721Semaste			    st.st_uid == uid) {
1313254721Semaste				char            tmp[MAXPATHLEN];
1314254721Semaste
1315254721Semaste				snprintf(tmp, sizeof(tmp), "/usr/bin/atrm %s", e->d_name);
1316254721Semaste				system(tmp);
1317254721Semaste			}
1318254721Semaste		}
1319254721Semaste		closedir(d);
1320254721Semaste	}
1321254721Semaste}
1322254721Semaste
1323254721Semastestatic void
1324254721Semastermopie(char const * name)
1325254721Semaste{
1326254721Semaste	static const char etcopie[] = "/etc/opiekeys";
1327254721Semaste	FILE   *fp = fopen(etcopie, "r+");
1328254721Semaste
1329254721Semaste	if (fp != NULL) {
1330254721Semaste		char	tmp[1024];
1331254721Semaste		off_t	atofs = 0;
1332254721Semaste		int	length = strlen(name);
1333254721Semaste
1334254721Semaste		while (fgets(tmp, sizeof tmp, fp) != NULL) {
1335254721Semaste			if (strncmp(name, tmp, length) == 0 && tmp[length]==' ') {
1336254721Semaste				if (fseek(fp, atofs, SEEK_SET) == 0) {
1337254721Semaste					fwrite("#", 1, 1, fp);	/* Comment username out */
1338254721Semaste				}
1339254721Semaste				break;
1340254721Semaste			}
1341254721Semaste			atofs = ftell(fp);
1342254721Semaste		}
1343254721Semaste		/*
1344254721Semaste		 * If we got an error of any sort, don't update!
1345254721Semaste		 */
1346254721Semaste		fclose(fp);
1347254721Semaste	}
1348254721Semaste}
1349254721Semaste
1350254721Semaste