1/*	$OpenBSD: skeyaudit.c,v 1.29 2019/06/28 13:35:03 deraadt Exp $	*/
2
3/*
4 * Copyright (c) 1997, 2000, 2003 Todd C. Miller <millert@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND TODD C. MILLER DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
12 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL TODD C. MILLER BE LIABLE
13 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
15 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 *
18 * Sponsored in part by the Defense Advanced Research Projects
19 * Agency (DARPA) and Air Force Research Laboratory, Air Force
20 * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21 */
22
23#include <sys/wait.h>
24
25#include <err.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <limits.h>
29#include <login_cap.h>
30#include <paths.h>
31#include <pwd.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <string.h>
35#include <unistd.h>
36#include <skey.h>
37
38void notify(struct passwd *, int, int);
39void sanitise_stdfd(void);
40FILE *runsendmail(struct passwd *, int *);
41__dead void usage(void);
42
43void
44sanitise_stdfd(void)
45{
46	int nullfd, dupfd;
47
48	if ((nullfd = dupfd = open(_PATH_DEVNULL, O_RDWR)) == -1) {
49		fprintf(stderr, "Couldn't open /dev/null: %s\n",
50		    strerror(errno));
51		exit(1);
52	}
53	while (++dupfd <= STDERR_FILENO) {
54		/* Only populate closed fds. */
55		if (fcntl(dupfd, F_GETFL) == -1 && errno == EBADF) {
56			if (dup2(nullfd, dupfd) == -1) {
57				fprintf(stderr, "dup2: %s\n", strerror(errno));
58				exit(1);
59			}
60		}
61	}
62	if (nullfd > STDERR_FILENO)
63		close(nullfd);
64}
65
66int
67main(int argc, char **argv)
68{
69	struct passwd *pw;
70	struct skey key;
71	char *name;
72	int ch, left, aflag, iflag, limit;
73
74	if (pledge("stdio rpath wpath flock getpw proc exec id", NULL) == -1)
75		err(1, "pledge");
76
77	aflag = iflag = 0;
78	limit = 12;
79	while ((ch = getopt(argc, argv, "ail:")) != -1)
80		switch(ch) {
81		case 'a':
82			if (getuid() != 0)
83				errx(1, "only root may use the -a flag");
84			aflag = 1;
85			break;
86		case 'i':
87			iflag = 1;
88			break;
89		case 'l':
90			errno = 0;
91			if ((limit = (int)strtol(optarg, NULL, 10)) == 0)
92				errno = ERANGE;
93			if (errno) {
94				warn("key limit");
95				usage();
96			}
97			break;
98		default:
99			usage();
100	}
101
102	if (iflag) {
103		if (pledge("stdio rpath wpath flock getpw", NULL) == -1)
104			err(1, "pledge");
105	}
106
107	 /* If we are in interactive mode, STDOUT_FILENO *must* be open. */
108	if (iflag && fcntl(STDOUT_FILENO, F_GETFL) == -1 && errno == EBADF)
109		exit(1);
110
111	/*
112	 * Make sure STDIN_FILENO, STDOUT_FILENO, and STDERR_FILENO are open.
113	 * If not, open /dev/null in their place or bail.
114	 */
115	sanitise_stdfd();
116
117	if (argc - optind > 0)
118		usage();
119
120	/* Need key.keyfile zero'd at the very least */
121	(void)memset(&key, 0, sizeof(key));
122
123	left = 0;
124	if (aflag) {
125		while ((ch = skeygetnext(&key)) == 0) {
126			left = key.n - 1;
127			if ((pw = getpwnam(key.logname)) == NULL)
128				continue;
129			if (left >= limit)
130				continue;
131			(void)fclose(key.keyfile);
132			key.keyfile = NULL;
133			notify(pw, left, iflag);
134		}
135		if (ch == -1)
136			errx(-1, "cannot open %s", _PATH_SKEYDIR);
137	} else {
138		if ((pw = getpwuid(getuid())) == NULL)
139			errx(1, "no passwd entry for uid %u", getuid());
140		if ((name = strdup(pw->pw_name)) == NULL)
141			err(1, "cannot allocate memory");
142		sevenbit(name);
143
144		switch (skeylookup(&key, name)) {
145			case 0:		/* Success! */
146				left = key.n - 1;
147				break;
148			case -1:	/* File error */
149				errx(1, "cannot open %s/%s", _PATH_SKEYDIR,
150				    name);
151				break;
152			case 1:		/* Unknown user */
153				errx(1, "user %s is not listed in %s", name,
154				    _PATH_SKEYDIR);
155		}
156		(void)fclose(key.keyfile);
157
158		if (left < limit)
159			notify(pw, left, iflag);
160	}
161
162	exit(0);
163}
164
165void
166notify(struct passwd *pw, int seq, int interactive)
167{
168	static char hostname[HOST_NAME_MAX+1];
169	pid_t pid;
170	FILE *out;
171
172	/* Only set this once */
173	if (hostname[0] == '\0' && gethostname(hostname, sizeof(hostname)) == -1)
174		strlcpy(hostname, "unknown", sizeof(hostname));
175
176	if (interactive)
177		out = stdout;
178	else
179		out = runsendmail(pw, &pid);
180
181	if (!interactive)
182		(void)fprintf(out,
183		   "Auto-Submitted: auto-generated\n"
184		   "To: %s\nSubject: IMPORTANT action required\n", pw->pw_name);
185
186	if (seq)
187		(void)fprintf(out,
188"\nYou are nearing the end of your current S/Key sequence for account\n\
189%s on system %s.\n\n\
190Your S/Key sequence number is now %d.  When it reaches zero\n\
191you will no longer be able to use S/Key to log into the system.\n\n",
192pw->pw_name, hostname, seq);
193	else
194		(void)fprintf(out,
195"\nYou are at the end of your current S/Key sequence for account\n\
196%s on system %s.\n\n\
197At this point you can no longer use S/Key to log into the system.\n\n",
198pw->pw_name, hostname);
199	(void)fprintf(out,
200"Type \"skeyinit -s\" to reinitialize your sequence number.\n\n");
201
202	if (!interactive) {
203		(void)fclose(out);
204		(void)waitpid(pid, NULL, 0);
205	}
206}
207
208FILE *
209runsendmail(struct passwd *pw, pid_t *pidp)
210{
211	FILE *fp;
212	int pfd[2];
213	pid_t pid;
214
215	if (pipe(pfd) == -1)
216		return(NULL);
217
218	switch (pid = fork()) {
219	case -1:			/* fork(2) failed */
220		(void)close(pfd[0]);
221		(void)close(pfd[1]);
222		return(NULL);
223	case 0:				/* In child */
224		(void)close(pfd[1]);
225		(void)dup2(pfd[0], STDIN_FILENO);
226		(void)close(pfd[0]);
227
228		/* Run sendmail as target user not root */
229		if (getuid() == 0 &&
230		    setusercontext(NULL, pw, pw->pw_uid, LOGIN_SETALL) != 0) {
231			warn("cannot set user context");
232			_exit(127);
233		}
234
235		execl(_PATH_SENDMAIL, "sendmail", "-t", (char *)NULL);
236		warn("cannot run \"%s -t\"", _PATH_SENDMAIL);
237		_exit(127);
238	}
239
240	/* In parent */
241	*pidp = pid;
242	fp = fdopen(pfd[1], "w");
243	(void)close(pfd[0]);
244
245	return(fp);
246}
247
248__dead void
249usage(void)
250{
251	extern char *__progname;
252
253	(void)fprintf(stderr, "usage: %s [-ai] [-l limit]\n",
254	    __progname);
255	exit(1);
256}
257