xref: /freebsd/contrib/pf/authpf/authpf.c (revision 044243fcc9b4c639cf5655e37b98478bcb312590)
1 /*	$OpenBSD: authpf.c,v 1.112 2009/01/10 19:08:53 miod Exp $	*/
2 
3 /*
4  * Copyright (C) 1998 - 2007 Bob Beck (beck@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 THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/file.h>
21 #include <sys/ioctl.h>
22 #include <sys/socket.h>
23 #include <sys/stat.h>
24 #include <sys/time.h>
25 #include <sys/wait.h>
26 
27 #include <net/if.h>
28 #include <net/pfvar.h>
29 #include <arpa/inet.h>
30 
31 #include <err.h>
32 #include <errno.h>
33 #ifdef __FreeBSD__
34 #include <inttypes.h>
35 #endif
36 #include <libpfctl.h>
37 #include <login_cap.h>
38 #include <pwd.h>
39 #include <grp.h>
40 #include <signal.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <syslog.h>
45 #include <unistd.h>
46 
47 #include "pathnames.h"
48 
49 static int	read_config(FILE *);
50 static void	print_message(const char *);
51 static int	allowed_luser(struct passwd *);
52 static int	check_luser(const char *, char *);
53 static int	remove_stale_rulesets(void);
54 static int	recursive_ruleset_purge(char *, char *);
55 static int	change_filter(int, const char *, const char *);
56 static int	change_table(int, const char *);
57 static void	authpf_kill_states(void);
58 
59 struct pfctl_handle	 *pfh;
60 char	anchorname[PF_ANCHOR_NAME_SIZE] = "authpf";
61 char	rulesetname[MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 2];
62 char	tablename[PF_TABLE_NAME_SIZE] = "authpf_users";
63 int	user_ip = 1;	/* controls whether $user_ip is set */
64 
65 FILE	*pidfp;
66 int	pidfd = -1;
67 char	 luser[MAXLOGNAME];	/* username */
68 char	 ipsrc[256];		/* ip as a string */
69 char	 pidfile[MAXPATHLEN];	/* we save pid in this file. */
70 
71 struct timeval	Tstart, Tend;	/* start and end times of session */
72 
73 volatile sig_atomic_t	want_death;
74 static void		need_death(int signo);
75 #ifdef __FreeBSD__
76 static __dead2 void	do_death(int);
77 #else
78 static __dead void	do_death(int);
79 #endif
80 extern char *__progname;	/* program name */
81 
82 /*
83  * User shell for authenticating gateways. Sole purpose is to allow
84  * a user to ssh to a gateway, and have the gateway modify packet
85  * filters to allow access, then remove access when the user finishes
86  * up. Meant to be used only from ssh(1) connections.
87  */
88 int
89 main(void)
90 {
91 	int		 lockcnt = 0, n;
92 	FILE		*config;
93 	struct in6_addr	 ina;
94 	struct passwd	*pw;
95 	char		*cp;
96 	gid_t		 gid;
97 	uid_t		 uid;
98 	const char	*shell;
99 	login_cap_t	*lc;
100 
101 	if (strcmp(__progname, "-authpf-noip") == 0)
102                 user_ip = 0;
103 
104 	config = fopen(PATH_CONFFILE, "r");
105 	if (config == NULL) {
106 		syslog(LOG_ERR, "cannot open %s (%m)", PATH_CONFFILE);
107 		exit(1);
108 	}
109 
110 	if ((cp = getenv("SSH_TTY")) == NULL) {
111 		syslog(LOG_ERR, "non-interactive session connection for authpf");
112 		exit(1);
113 	}
114 
115 	if ((cp = getenv("SSH_CLIENT")) == NULL) {
116 		syslog(LOG_ERR, "cannot determine connection source");
117 		exit(1);
118 	}
119 
120 	if (strlcpy(ipsrc, cp, sizeof(ipsrc)) >= sizeof(ipsrc)) {
121 		syslog(LOG_ERR, "SSH_CLIENT variable too long");
122 		exit(1);
123 	}
124 	cp = strchr(ipsrc, ' ');
125 	if (!cp) {
126 		syslog(LOG_ERR, "corrupt SSH_CLIENT variable %s", ipsrc);
127 		exit(1);
128 	}
129 	*cp = '\0';
130 	if (inet_pton(AF_INET, ipsrc, &ina) != 1 &&
131 	    inet_pton(AF_INET6, ipsrc, &ina) != 1) {
132 		syslog(LOG_ERR,
133 		    "cannot determine IP from SSH_CLIENT %s", ipsrc);
134 		exit(1);
135 	}
136 	/* open the pf device */
137 	pfh = pfctl_open(PATH_DEVFILE);
138 	if (pfh == NULL) {
139 		syslog(LOG_ERR, "cannot open packet filter device (%m)");
140 		goto die;
141 	}
142 
143 	uid = getuid();
144 	pw = getpwuid(uid);
145 	if (pw == NULL) {
146 		syslog(LOG_ERR, "cannot find user for uid %u", uid);
147 		goto die;
148 	}
149 
150 	if ((lc = login_getclass(pw->pw_class)) != NULL)
151 		shell = login_getcapstr(lc, "shell", pw->pw_shell,
152 		    pw->pw_shell);
153 	else
154 		shell = pw->pw_shell;
155 
156 #ifndef __FreeBSD__
157 	login_close(lc);
158 #endif
159 
160 	if (strcmp(shell, PATH_AUTHPF_SHELL) &&
161 	    strcmp(shell, PATH_AUTHPF_SHELL_NOIP)) {
162 		syslog(LOG_ERR, "wrong shell for user %s, uid %u",
163 		    pw->pw_name, pw->pw_uid);
164 #ifdef __FreeBSD__
165 	login_close(lc);
166 #else
167 		if (shell != pw->pw_shell)
168 			free(shell);
169 #endif
170 		goto die;
171 	}
172 
173 #ifdef __FreeBSD__
174 	login_close(lc);
175 #else
176 	if (shell != pw->pw_shell)
177 		free(shell);
178 #endif
179 
180 	/*
181 	 * Paranoia, but this data _does_ come from outside authpf, and
182 	 * truncation would be bad.
183 	 */
184 	if (strlcpy(luser, pw->pw_name, sizeof(luser)) >= sizeof(luser)) {
185 		syslog(LOG_ERR, "username too long: %s", pw->pw_name);
186 		goto die;
187 	}
188 
189 	if ((n = snprintf(rulesetname, sizeof(rulesetname), "%s(%ld)",
190 	    luser, (long)getpid())) < 0 || (u_int)n >= sizeof(rulesetname)) {
191 		syslog(LOG_INFO, "%s(%ld) too large, ruleset name will be %ld",
192 		    luser, (long)getpid(), (long)getpid());
193 		if ((n = snprintf(rulesetname, sizeof(rulesetname), "%ld",
194 		    (long)getpid())) < 0 || (u_int)n >= sizeof(rulesetname)) {
195 			syslog(LOG_ERR, "pid too large for ruleset name");
196 			goto die;
197 		}
198 	}
199 
200 
201 	/* Make our entry in /var/authpf as ipaddr or username */
202 	n = snprintf(pidfile, sizeof(pidfile), "%s/%s",
203 	    PATH_PIDFILE, user_ip ? ipsrc : luser);
204 	if (n < 0 || (u_int)n >= sizeof(pidfile)) {
205 		syslog(LOG_ERR, "path to pidfile too long");
206 		goto die;
207 	}
208 
209 	signal(SIGTERM, need_death);
210 	signal(SIGINT, need_death);
211 	signal(SIGALRM, need_death);
212 	signal(SIGPIPE, need_death);
213 	signal(SIGHUP, need_death);
214 	signal(SIGQUIT, need_death);
215 	signal(SIGTSTP, need_death);
216 
217 	/*
218 	 * If someone else is already using this ip, then this person
219 	 * wants to switch users - so kill the old process and exit
220 	 * as well.
221 	 *
222 	 * Note, we could print a message and tell them to log out, but the
223 	 * usual case of this is that someone has left themselves logged in,
224 	 * with the authenticated connection iconized and someone else walks
225 	 * up to use and automatically logs in before using. If this just
226 	 * gets rid of the old one silently, the new user never knows they
227 	 * could have used someone else's old authentication. If we
228 	 * tell them to log out before switching users it is an invitation
229 	 * for abuse.
230 	 */
231 
232 	do {
233 		int	save_errno, otherpid = -1;
234 		char	otherluser[MAXLOGNAME];
235 
236 		if ((pidfd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1 ||
237 		    (pidfp = fdopen(pidfd, "r+")) == NULL) {
238 			if (pidfd != -1)
239 				close(pidfd);
240 			syslog(LOG_ERR, "cannot open or create %s: %s", pidfile,
241 			    strerror(errno));
242 			goto die;
243 		}
244 
245 		if (flock(fileno(pidfp), LOCK_EX|LOCK_NB) == 0)
246 			break;
247 		save_errno = errno;
248 
249 		/* Mark our pid, and username to our file. */
250 
251 		rewind(pidfp);
252 		/* 31 == MAXLOGNAME - 1 */
253 		if (fscanf(pidfp, "%d\n%31s\n", &otherpid, otherluser) != 2)
254 			otherpid = -1;
255 		syslog(LOG_DEBUG, "tried to lock %s, in use by pid %d: %s",
256 		    pidfile, otherpid, strerror(save_errno));
257 
258 		if (otherpid > 0) {
259 			syslog(LOG_INFO,
260 			    "killing prior auth (pid %d) of %s by user %s",
261 			    otherpid, ipsrc, otherluser);
262 			if (kill((pid_t) otherpid, SIGTERM) == -1) {
263 				syslog(LOG_INFO,
264 				    "could not kill process %d: (%m)",
265 				    otherpid);
266 			}
267 		}
268 
269 		/*
270 		 * We try to kill the previous process and acquire the lock
271 		 * for 10 seconds, trying once a second. if we can't after
272 		 * 10 attempts we log an error and give up.
273 		 */
274 		if (want_death || ++lockcnt > 10) {
275 			if (!want_death)
276 				syslog(LOG_ERR, "cannot kill previous authpf (pid %d)",
277 				    otherpid);
278 			fclose(pidfp);
279 			pidfp = NULL;
280 			pidfd = -1;
281 			goto dogdeath;
282 		}
283 		sleep(1);
284 
285 		/* re-open, and try again. The previous authpf process
286 		 * we killed above should unlink the file and release
287 		 * it's lock, giving us a chance to get it now
288 		 */
289 		fclose(pidfp);
290 		pidfp = NULL;
291 		pidfd = -1;
292 	} while (1);
293 
294 	/* whack the group list */
295 	gid = getegid();
296 	if (setgroups(1, &gid) == -1) {
297 		syslog(LOG_INFO, "setgroups: %s", strerror(errno));
298 		do_death(0);
299 	}
300 
301 	/* revoke privs */
302 	uid = getuid();
303 	if (setresuid(uid, uid, uid) == -1) {
304 		syslog(LOG_INFO, "setresuid: %s", strerror(errno));
305 		do_death(0);
306 	}
307 	openlog("authpf", LOG_PID | LOG_NDELAY, LOG_DAEMON);
308 
309 	if (!check_luser(PATH_BAN_DIR, luser) || !allowed_luser(pw)) {
310 		syslog(LOG_INFO, "user %s prohibited", luser);
311 		do_death(0);
312 	}
313 
314 	if (read_config(config)) {
315 		syslog(LOG_ERR, "invalid config file %s", PATH_CONFFILE);
316 		do_death(0);
317 	}
318 
319 	if (remove_stale_rulesets()) {
320 		syslog(LOG_INFO, "error removing stale rulesets");
321 		do_death(0);
322 	}
323 
324 	/* We appear to be making headway, so actually mark our pid */
325 	rewind(pidfp);
326 	fprintf(pidfp, "%ld\n%s\n", (long)getpid(), luser);
327 	fflush(pidfp);
328 	(void) ftruncate(fileno(pidfp), ftello(pidfp));
329 
330 	if (change_filter(1, luser, ipsrc) == -1) {
331 		printf("Unable to modify filters\r\n");
332 		do_death(0);
333 	}
334 	if (user_ip && change_table(1, ipsrc) == -1) {
335 		printf("Unable to modify table\r\n");
336 		change_filter(0, luser, ipsrc);
337 		do_death(0);
338 	}
339 
340 	while (1) {
341 		printf("\r\nHello %s. ", luser);
342 		printf("You are authenticated from host \"%s\"\r\n", ipsrc);
343 		setproctitle("%s@%s", luser, ipsrc);
344 		print_message(PATH_MESSAGE);
345 		while (1) {
346 			sleep(10);
347 			if (want_death)
348 				do_death(1);
349 		}
350 	}
351 
352 	/* NOTREACHED */
353 dogdeath:
354 	printf("\r\n\r\nSorry, this service is currently unavailable due to ");
355 	printf("technical difficulties\r\n\r\n");
356 	print_message(PATH_PROBLEM);
357 	printf("\r\nYour authentication process (pid %ld) was unable to run\n",
358 	    (long)getpid());
359 	sleep(180); /* them lusers read reaaaaal slow */
360 die:
361 	do_death(0);
362 }
363 
364 /*
365  * reads config file in PATH_CONFFILE to set optional behaviours up
366  */
367 static int
368 read_config(FILE *f)
369 {
370 	char	buf[1024];
371 	int	i = 0;
372 
373 	do {
374 		char	**ap;
375 		char	 *pair[4], *cp, *tp;
376 		int	  len;
377 
378 		if (fgets(buf, sizeof(buf), f) == NULL) {
379 			fclose(f);
380 			return (0);
381 		}
382 		i++;
383 		len = strlen(buf);
384 		if (len == 0)
385 			continue;
386 		if (buf[len - 1] != '\n' && !feof(f)) {
387 			syslog(LOG_ERR, "line %d too long in %s", i,
388 			    PATH_CONFFILE);
389 			return (1);
390 		}
391 		buf[len - 1] = '\0';
392 
393 		for (cp = buf; *cp == ' ' || *cp == '\t'; cp++)
394 			; /* nothing */
395 
396 		if (!*cp || *cp == '#' || *cp == '\n')
397 			continue;
398 
399 		for (ap = pair; ap < &pair[3] &&
400 		    (*ap = strsep(&cp, "=")) != NULL; ) {
401 			if (**ap != '\0')
402 				ap++;
403 		}
404 		if (ap != &pair[2])
405 			goto parse_error;
406 
407 		tp = pair[1] + strlen(pair[1]);
408 		while ((*tp == ' ' || *tp == '\t') && tp >= pair[1])
409 			*tp-- = '\0';
410 
411 		if (strcasecmp(pair[0], "anchor") == 0) {
412 			if (!pair[1][0] || strlcpy(anchorname, pair[1],
413 			    sizeof(anchorname)) >= sizeof(anchorname))
414 				goto parse_error;
415 		}
416 		if (strcasecmp(pair[0], "table") == 0) {
417 			if (!pair[1][0] || strlcpy(tablename, pair[1],
418 			    sizeof(tablename)) >= sizeof(tablename))
419 				goto parse_error;
420 		}
421 	} while (!feof(f) && !ferror(f));
422 	fclose(f);
423 	return (0);
424 
425 parse_error:
426 	fclose(f);
427 	syslog(LOG_ERR, "parse error, line %d of %s", i, PATH_CONFFILE);
428 	return (1);
429 }
430 
431 
432 /*
433  * splatter a file to stdout - max line length of 1024,
434  * used for spitting message files at users to tell them
435  * they've been bad or we're unavailable.
436  */
437 static void
438 print_message(const char *filename)
439 {
440 	char	 buf[1024];
441 	FILE	*f;
442 
443 	if ((f = fopen(filename, "r")) == NULL)
444 		return; /* fail silently, we don't care if it isn't there */
445 
446 	do {
447 		if (fgets(buf, sizeof(buf), f) == NULL) {
448 			fflush(stdout);
449 			fclose(f);
450 			return;
451 		}
452 	} while (fputs(buf, stdout) != EOF && !feof(f));
453 	fflush(stdout);
454 	fclose(f);
455 }
456 
457 /*
458  * allowed_luser checks to see if user "luser" is allowed to
459  * use this gateway by virtue of being listed in an allowed
460  * users file, namely /etc/authpf/authpf.allow .
461  * Users may be listed by <username>, %<group>, or @<login_class>.
462  *
463  * If /etc/authpf/authpf.allow does not exist, then we assume that
464  * all users who are allowed in by sshd(8) are permitted to
465  * use this gateway. If /etc/authpf/authpf.allow does exist, then a
466  * user must be listed if the connection is to continue, else
467  * the session terminates in the same manner as being banned.
468  */
469 static int
470 allowed_luser(struct passwd *pw)
471 {
472 	char *buf,*lbuf;
473 	int	 matched;
474 	size_t	 len;
475 	FILE	*f;
476 
477 	if ((f = fopen(PATH_ALLOWFILE, "r")) == NULL) {
478 		if (errno == ENOENT) {
479 			/*
480 			 * allowfile doesn't exist, thus this gateway
481 			 * isn't restricted to certain users...
482 			 */
483 			return (1);
484 		}
485 
486 		/*
487 		 * luser may in fact be allowed, but we can't open
488 		 * the file even though it's there. probably a config
489 		 * problem.
490 		 */
491 		syslog(LOG_ERR, "cannot open allowed users file %s (%s)",
492 		    PATH_ALLOWFILE, strerror(errno));
493 		return (0);
494 	} else {
495 		/*
496 		 * /etc/authpf/authpf.allow exists, thus we do a linear
497 		 * search to see if they are allowed.
498 		 * also, if username "*" exists, then this is a
499 		 * "public" gateway, such as it is, so let
500 		 * everyone use it.
501 		 */
502 		int gl_init = 0, ngroups = NGROUPS + 1;
503 		gid_t groups[NGROUPS + 1];
504 
505 		lbuf = NULL;
506 		matched = 0;
507 
508 		while ((buf = fgetln(f, &len))) {
509 
510 			if (buf[len - 1] == '\n')
511 				buf[len - 1] = '\0';
512 			else {
513 				if ((lbuf = (char *)malloc(len + 1)) == NULL)
514 					err(1, NULL);
515 				memcpy(lbuf, buf, len);
516 				lbuf[len] = '\0';
517 				buf = lbuf;
518 			}
519 
520 			if (buf[0] == '@') {
521 				/* check login class */
522 				if (strcmp(pw->pw_class, buf + 1) == 0)
523 					matched++;
524 			} else if (buf[0] == '%') {
525 				/* check group membership */
526 				int cnt;
527 				struct group *group;
528 
529 				if ((group = getgrnam(buf + 1)) == NULL) {
530 					syslog(LOG_ERR,
531 					    "invalid group '%s' in %s (%s)",
532 					    buf + 1, PATH_ALLOWFILE,
533 				 	    strerror(errno));
534 					return (0);
535 				}
536 
537 				if (!gl_init) {
538 					(void) getgrouplist(pw->pw_name,
539 					    pw->pw_gid, groups, &ngroups);
540 					gl_init++;
541 				}
542 
543 				for ( cnt = 0; cnt < ngroups; cnt++) {
544 					if (group->gr_gid == groups[cnt]) {
545 						matched++;
546 						break;
547 					}
548 				}
549 			} else {
550 				/* check username and wildcard */
551 				matched = strcmp(pw->pw_name, buf) == 0 ||
552 				    strcmp("*", buf) == 0;
553 			}
554 
555 			if (lbuf != NULL) {
556 				free(lbuf);
557 				lbuf = NULL;
558 			}
559 
560 			if (matched)
561 				return (1); /* matched an allowed user/group */
562 		}
563 		syslog(LOG_INFO, "denied access to %s: not listed in %s",
564 		    pw->pw_name, PATH_ALLOWFILE);
565 
566 		fputs("\n\nSorry, you are not allowed to use this facility!\n",
567 		    stdout);
568 	}
569 	fflush(stdout);
570 	return (0);
571 }
572 
573 /*
574  * check_luser checks to see if user "luser" has been banned
575  * from using us by virtue of having an file of the same name
576  * in the "luserdir" directory.
577  *
578  * If the user has been banned, we copy the contents of the file
579  * to the user's screen. (useful for telling the user what to
580  * do to get un-banned, or just to tell them they aren't
581  * going to be un-banned.)
582  */
583 static int
584 check_luser(const char *luserdir, char *l_user)
585 {
586 	FILE	*f;
587 	int	 n;
588 	char	 tmp[MAXPATHLEN];
589 
590 	n = snprintf(tmp, sizeof(tmp), "%s/%s", luserdir, l_user);
591 	if (n < 0 || (u_int)n >= sizeof(tmp)) {
592 		syslog(LOG_ERR, "provided banned directory line too long (%s)",
593 		    luserdir);
594 		return (0);
595 	}
596 	if ((f = fopen(tmp, "r")) == NULL) {
597 		if (errno == ENOENT) {
598 			/*
599 			 * file or dir doesn't exist, so therefore
600 			 * this luser isn't banned..  all is well
601 			 */
602 			return (1);
603 		} else {
604 			/*
605 			 * luser may in fact be banned, but we can't open the
606 			 * file even though it's there. probably a config
607 			 * problem.
608 			 */
609 			syslog(LOG_ERR, "cannot open banned file %s (%s)",
610 			    tmp, strerror(errno));
611 			return (0);
612 		}
613 	} else {
614 		/*
615 		 * luser is banned - spit the file at them to
616 		 * tell what they can do and where they can go.
617 		 */
618 		syslog(LOG_INFO, "denied access to %s: %s exists",
619 		    l_user, tmp);
620 
621 		/* reuse tmp */
622 		strlcpy(tmp, "\n\n-**- Sorry, you have been banned! -**-\n\n",
623 		    sizeof(tmp));
624 		while (fputs(tmp, stdout) != EOF && !feof(f)) {
625 			if (fgets(tmp, sizeof(tmp), f) == NULL) {
626 				fflush(stdout);
627 				fclose(f);
628 				return (0);
629 			}
630 		}
631 		fclose(f);
632 	}
633 	fflush(stdout);
634 	return (0);
635 }
636 
637 /*
638  * Search for rulesets left by other authpf processes (either because they
639  * died ungracefully or were terminated) and remove them.
640  */
641 static int
642 remove_stale_rulesets(void)
643 {
644 	struct pfioc_ruleset	 prs;
645 	u_int32_t		 nr;
646 
647 	memset(&prs, 0, sizeof(prs));
648 	strlcpy(prs.path, anchorname, sizeof(prs.path));
649 	if (ioctl(pfctl_fd(pfh), DIOCGETRULESETS, &prs)) {
650 		if (errno == EINVAL)
651 			return (0);
652 		else
653 			return (1);
654 	}
655 
656 	nr = prs.nr;
657 	while (nr) {
658 		char	*s, *t;
659 		pid_t	 pid;
660 
661 		prs.nr = nr - 1;
662 		if (ioctl(pfctl_fd(pfh), DIOCGETRULESET, &prs))
663 			return (1);
664 		errno = 0;
665 		if ((t = strchr(prs.name, '(')) == NULL)
666 			t = prs.name;
667 		else
668 			t++;
669 		pid = strtoul(t, &s, 10);
670 		if (!prs.name[0] || errno ||
671 		    (*s && (t == prs.name || *s != ')')))
672 			return (1);
673 		if ((kill(pid, 0) && errno != EPERM) || pid == getpid()) {
674 			if (recursive_ruleset_purge(anchorname, prs.name))
675 				return (1);
676 		}
677 		nr--;
678 	}
679 	return (0);
680 }
681 
682 static int
683 recursive_ruleset_purge(char *an, char *rs)
684 {
685 	struct pfioc_trans_e     *t_e = NULL;
686 	struct pfioc_trans	 *t = NULL;
687 	struct pfioc_ruleset	 *prs = NULL;
688 	int			  i;
689 
690 
691 	/* purge rules */
692 	errno = 0;
693 	if ((t = calloc(1, sizeof(struct pfioc_trans))) == NULL)
694 		goto no_mem;
695 	if ((t_e = calloc(PF_RULESET_MAX+1,
696 	    sizeof(struct pfioc_trans_e))) == NULL)
697 		goto no_mem;
698 	t->size = PF_RULESET_MAX+1;
699 	t->esize = sizeof(struct pfioc_trans_e);
700 	t->array = t_e;
701 	for (i = 0; i < PF_RULESET_MAX+1; ++i) {
702 		t_e[i].rs_num = i;
703 		snprintf(t_e[i].anchor, sizeof(t_e[i].anchor), "%s/%s", an, rs);
704 	}
705 	t_e[PF_RULESET_MAX].rs_num = PF_RULESET_TABLE;
706 	if ((ioctl(pfctl_fd(pfh), DIOCXBEGIN, t) ||
707 	    ioctl(pfctl_fd(pfh), DIOCXCOMMIT, t)) &&
708 	    errno != EINVAL)
709 		goto cleanup;
710 
711 	/* purge any children */
712 	if ((prs = calloc(1, sizeof(struct pfioc_ruleset))) == NULL)
713 		goto no_mem;
714 	snprintf(prs->path, sizeof(prs->path), "%s/%s", an, rs);
715 	if (ioctl(pfctl_fd(pfh), DIOCGETRULESETS, prs)) {
716 		if (errno != EINVAL)
717 			goto cleanup;
718 		errno = 0;
719 	} else {
720 		int nr = prs->nr;
721 
722 		while (nr) {
723 			prs->nr = 0;
724 			if (ioctl(pfctl_fd(pfh), DIOCGETRULESET, prs))
725 				goto cleanup;
726 
727 			if (recursive_ruleset_purge(prs->path, prs->name))
728 				goto cleanup;
729 			nr--;
730 		}
731 	}
732 
733 no_mem:
734 	if (errno == ENOMEM)
735 		syslog(LOG_ERR, "calloc failed");
736 
737 cleanup:
738 	free(t);
739 	free(t_e);
740 	free(prs);
741 	return (errno);
742 }
743 
744 /*
745  * Add/remove filter entries for user "luser" from ip "ipsrc"
746  */
747 static int
748 change_filter(int add, const char *l_user, const char *ip_src)
749 {
750 	char	*fdpath = NULL, *userstr = NULL, *ipstr = NULL;
751 	char	*rsn = NULL, *fn = NULL;
752 	pid_t	pid;
753 	gid_t   gid;
754 	int	s;
755 
756 	if (add) {
757 		struct stat sb;
758 		char *pargv[13] = {
759 			"pfctl", "-p", "/dev/pf", "-q", "-a", "anchor/ruleset",
760 			"-D", "user_id=X", "-D", "user_ip=X", "-f", "file", NULL
761 		};
762 
763 		if (l_user == NULL || !l_user[0] || ip_src == NULL || !ip_src[0]) {
764 			syslog(LOG_ERR, "invalid luser/ipsrc");
765 			goto error;
766 		}
767 
768 		if (asprintf(&rsn, "%s/%s", anchorname, rulesetname) == -1)
769 			goto no_mem;
770 		if (asprintf(&fdpath, "/dev/fd/%d", pfctl_fd(pfh)) == -1)
771 			goto no_mem;
772 		if (asprintf(&ipstr, "user_ip=%s", ip_src) == -1)
773 			goto no_mem;
774 		if (asprintf(&userstr, "user_id=%s", l_user) == -1)
775 			goto no_mem;
776 		if (asprintf(&fn, "%s/%s/authpf.rules",
777 		    PATH_USER_DIR, l_user) == -1)
778 			goto no_mem;
779 		if (stat(fn, &sb) == -1) {
780 			free(fn);
781 			if ((fn = strdup(PATH_PFRULES)) == NULL)
782 				goto no_mem;
783 		}
784 		pargv[2] = fdpath;
785 		pargv[5] = rsn;
786 		pargv[7] = userstr;
787 		if (user_ip) {
788 			pargv[9] = ipstr;
789 			pargv[11] = fn;
790 		} else {
791 			pargv[8] = "-f";
792 			pargv[9] = fn;
793 			pargv[10] = NULL;
794 		}
795 
796 		switch (pid = fork()) {
797 		case -1:
798 			syslog(LOG_ERR, "fork failed");
799 			goto error;
800 		case 0:
801 			/* revoke group privs before exec */
802 			gid = getgid();
803 			if (setregid(gid, gid) == -1) {
804 				err(1, "setregid");
805 			}
806 			execvp(PATH_PFCTL, pargv);
807 			warn("exec of %s failed", PATH_PFCTL);
808 			_exit(1);
809 		}
810 
811 		/* parent */
812 		waitpid(pid, &s, 0);
813 		if (s != 0) {
814 			syslog(LOG_ERR, "pfctl exited abnormally");
815 			goto error;
816 		}
817 
818 		gettimeofday(&Tstart, NULL);
819 		syslog(LOG_INFO, "allowing %s, user %s", ip_src, l_user);
820 	} else {
821 		remove_stale_rulesets();
822 
823 		gettimeofday(&Tend, NULL);
824 		syslog(LOG_INFO, "removed %s, user %s - duration %ju seconds",
825 		    ip_src, l_user, (uintmax_t)(Tend.tv_sec - Tstart.tv_sec));
826 	}
827 	return (0);
828 no_mem:
829 	syslog(LOG_ERR, "malloc failed");
830 error:
831 	free(fdpath);
832 	free(rsn);
833 	free(userstr);
834 	free(ipstr);
835 	free(fn);
836 	return (-1);
837 }
838 
839 /*
840  * Add/remove this IP from the "authpf_users" table.
841  */
842 static int
843 change_table(int add, const char *ip_src)
844 {
845 	struct pfioc_table	io;
846 	struct pfr_addr		addr;
847 
848 	bzero(&io, sizeof(io));
849 	strlcpy(io.pfrio_table.pfrt_name, tablename,
850 	    sizeof(io.pfrio_table.pfrt_name));
851 	io.pfrio_buffer = &addr;
852 	io.pfrio_esize = sizeof(addr);
853 	io.pfrio_size = 1;
854 
855 	bzero(&addr, sizeof(addr));
856 	if (ip_src == NULL || !ip_src[0])
857 		return (-1);
858 	if (inet_pton(AF_INET, ip_src, &addr.pfra_ip4addr) == 1) {
859 		addr.pfra_af = AF_INET;
860 		addr.pfra_net = 32;
861 	} else if (inet_pton(AF_INET6, ip_src, &addr.pfra_ip6addr) == 1) {
862 		addr.pfra_af = AF_INET6;
863 		addr.pfra_net = 128;
864 	} else {
865 		syslog(LOG_ERR, "invalid ipsrc");
866 		return (-1);
867 	}
868 
869 	if (ioctl(pfctl_fd(pfh), add ? DIOCRADDADDRS : DIOCRDELADDRS, &io) &&
870 	    errno != ESRCH) {
871 		syslog(LOG_ERR, "cannot %s %s from table %s: %s",
872 		    add ? "add" : "remove", ip_src, tablename,
873 		    strerror(errno));
874 		return (-1);
875 	}
876 	return (0);
877 }
878 
879 /*
880  * This is to kill off states that would otherwise be left behind stateful
881  * rules. This means we don't need to allow in more traffic than we really
882  * want to, since we don't have to worry about any luser sessions lasting
883  * longer than their ssh session. This function is based on
884  * pfctl_kill_states from pfctl.
885  */
886 static void
887 authpf_kill_states(void)
888 {
889 	struct pfctl_kill kill;
890 	struct pf_addr target;
891 
892 	memset(&kill, 0, sizeof(kill));
893 	memset(&target, 0, sizeof(target));
894 
895 	if (inet_pton(AF_INET, ipsrc, &target.v4) == 1)
896 		kill.af = AF_INET;
897 	else if (inet_pton(AF_INET6, ipsrc, &target.v6) == 1)
898 		kill.af = AF_INET6;
899 	else {
900 		syslog(LOG_ERR, "inet_pton(%s) failed", ipsrc);
901 		return;
902 	}
903 
904 	/* Kill all states from ipsrc */
905 	memcpy(&kill.src.addr.v.a.addr, &target,
906 	    sizeof(kill.src.addr.v.a.addr));
907 	memset(&kill.src.addr.v.a.mask, 0xff,
908 	    sizeof(kill.src.addr.v.a.mask));
909 	if (pfctl_kill_states_h(pfh, &kill, NULL))
910 		syslog(LOG_ERR, "pfctl_kill_states() failed (%m)");
911 
912 	/* Kill all states to ipsrc */
913 	memset(&kill.src, 0, sizeof(kill.src));
914 	memcpy(&kill.dst.addr.v.a.addr, &target,
915 	    sizeof(kill.dst.addr.v.a.addr));
916 	memset(&kill.dst.addr.v.a.mask, 0xff,
917 	    sizeof(kill.dst.addr.v.a.mask));
918 	if (pfctl_kill_states_h(pfh, &kill, NULL))
919 		syslog(LOG_ERR, "pfctl_kill_states() failed (%m)");
920 }
921 
922 /* signal handler that makes us go away properly */
923 static void
924 need_death(int signo __unused)
925 {
926 	want_death = 1;
927 }
928 
929 /*
930  * function that removes our stuff when we go away.
931  */
932 #ifdef __FreeBSD__
933 static __dead2 void
934 #else
935 static __dead void
936 #endif
937 do_death(int active)
938 {
939 	int	ret = 0;
940 
941 	if (active) {
942 		change_filter(0, luser, ipsrc);
943 		if (user_ip) {
944 			change_table(0, ipsrc);
945 			authpf_kill_states();
946 		}
947 	}
948 	if (pidfile[0] && pidfd != -1)
949 		if (unlink(pidfile) == -1)
950 			syslog(LOG_ERR, "cannot unlink %s (%m)", pidfile);
951 	exit(ret);
952 }
953