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