diff --git a/contrib/pf/authpf/authpf.c b/contrib/pf/authpf/authpf.c index 5ffa5b9cfe43..9858c1c50ced 100644 --- a/contrib/pf/authpf/authpf.c +++ b/contrib/pf/authpf/authpf.c @@ -1,955 +1,952 @@ /* $OpenBSD: authpf.c,v 1.112 2009/01/10 19:08:53 miod Exp $ */ /* * Copyright (C) 1998 - 2007 Bob Beck (beck@openbsd.org). * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -__FBSDID("$FreeBSD$"); - #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #endif #include #include #include #include #include #include #include #include #include #include #include "pathnames.h" static int read_config(FILE *); static void print_message(const char *); static int allowed_luser(struct passwd *); static int check_luser(const char *, char *); static int remove_stale_rulesets(void); static int recursive_ruleset_purge(char *, char *); static int change_filter(int, const char *, const char *); static int change_table(int, const char *); static void authpf_kill_states(void); int dev; /* pf device */ char anchorname[PF_ANCHOR_NAME_SIZE] = "authpf"; char rulesetname[MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 2]; char tablename[PF_TABLE_NAME_SIZE] = "authpf_users"; int user_ip = 1; /* controls whether $user_ip is set */ FILE *pidfp; int pidfd = -1; char luser[MAXLOGNAME]; /* username */ char ipsrc[256]; /* ip as a string */ char pidfile[MAXPATHLEN]; /* we save pid in this file. */ struct timeval Tstart, Tend; /* start and end times of session */ volatile sig_atomic_t want_death; static void need_death(int signo); #ifdef __FreeBSD__ static __dead2 void do_death(int); #else static __dead void do_death(int); #endif extern char *__progname; /* program name */ /* * User shell for authenticating gateways. Sole purpose is to allow * a user to ssh to a gateway, and have the gateway modify packet * filters to allow access, then remove access when the user finishes * up. Meant to be used only from ssh(1) connections. */ int main(void) { int lockcnt = 0, n; FILE *config; struct in6_addr ina; struct passwd *pw; char *cp; gid_t gid; uid_t uid; const char *shell; login_cap_t *lc; if (strcmp(__progname, "-authpf-noip") == 0) user_ip = 0; config = fopen(PATH_CONFFILE, "r"); if (config == NULL) { syslog(LOG_ERR, "cannot open %s (%m)", PATH_CONFFILE); exit(1); } if ((cp = getenv("SSH_TTY")) == NULL) { syslog(LOG_ERR, "non-interactive session connection for authpf"); exit(1); } if ((cp = getenv("SSH_CLIENT")) == NULL) { syslog(LOG_ERR, "cannot determine connection source"); exit(1); } if (strlcpy(ipsrc, cp, sizeof(ipsrc)) >= sizeof(ipsrc)) { syslog(LOG_ERR, "SSH_CLIENT variable too long"); exit(1); } cp = strchr(ipsrc, ' '); if (!cp) { syslog(LOG_ERR, "corrupt SSH_CLIENT variable %s", ipsrc); exit(1); } *cp = '\0'; if (inet_pton(AF_INET, ipsrc, &ina) != 1 && inet_pton(AF_INET6, ipsrc, &ina) != 1) { syslog(LOG_ERR, "cannot determine IP from SSH_CLIENT %s", ipsrc); exit(1); } /* open the pf device */ dev = open(PATH_DEVFILE, O_RDWR); if (dev == -1) { syslog(LOG_ERR, "cannot open packet filter device (%m)"); goto die; } uid = getuid(); pw = getpwuid(uid); if (pw == NULL) { syslog(LOG_ERR, "cannot find user for uid %u", uid); goto die; } if ((lc = login_getclass(pw->pw_class)) != NULL) shell = login_getcapstr(lc, "shell", pw->pw_shell, pw->pw_shell); else shell = pw->pw_shell; #ifndef __FreeBSD__ login_close(lc); #endif if (strcmp(shell, PATH_AUTHPF_SHELL) && strcmp(shell, PATH_AUTHPF_SHELL_NOIP)) { syslog(LOG_ERR, "wrong shell for user %s, uid %u", pw->pw_name, pw->pw_uid); #ifdef __FreeBSD__ login_close(lc); #else if (shell != pw->pw_shell) free(shell); #endif goto die; } #ifdef __FreeBSD__ login_close(lc); #else if (shell != pw->pw_shell) free(shell); #endif /* * Paranoia, but this data _does_ come from outside authpf, and * truncation would be bad. */ if (strlcpy(luser, pw->pw_name, sizeof(luser)) >= sizeof(luser)) { syslog(LOG_ERR, "username too long: %s", pw->pw_name); goto die; } if ((n = snprintf(rulesetname, sizeof(rulesetname), "%s(%ld)", luser, (long)getpid())) < 0 || (u_int)n >= sizeof(rulesetname)) { syslog(LOG_INFO, "%s(%ld) too large, ruleset name will be %ld", luser, (long)getpid(), (long)getpid()); if ((n = snprintf(rulesetname, sizeof(rulesetname), "%ld", (long)getpid())) < 0 || (u_int)n >= sizeof(rulesetname)) { syslog(LOG_ERR, "pid too large for ruleset name"); goto die; } } /* Make our entry in /var/authpf as ipaddr or username */ n = snprintf(pidfile, sizeof(pidfile), "%s/%s", PATH_PIDFILE, user_ip ? ipsrc : luser); if (n < 0 || (u_int)n >= sizeof(pidfile)) { syslog(LOG_ERR, "path to pidfile too long"); goto die; } signal(SIGTERM, need_death); signal(SIGINT, need_death); signal(SIGALRM, need_death); signal(SIGPIPE, need_death); signal(SIGHUP, need_death); signal(SIGQUIT, need_death); signal(SIGTSTP, need_death); /* * If someone else is already using this ip, then this person * wants to switch users - so kill the old process and exit * as well. * * Note, we could print a message and tell them to log out, but the * usual case of this is that someone has left themselves logged in, * with the authenticated connection iconized and someone else walks * up to use and automatically logs in before using. If this just * gets rid of the old one silently, the new user never knows they * could have used someone else's old authentication. If we * tell them to log out before switching users it is an invitation * for abuse. */ do { int save_errno, otherpid = -1; char otherluser[MAXLOGNAME]; if ((pidfd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1 || (pidfp = fdopen(pidfd, "r+")) == NULL) { if (pidfd != -1) close(pidfd); syslog(LOG_ERR, "cannot open or create %s: %s", pidfile, strerror(errno)); goto die; } if (flock(fileno(pidfp), LOCK_EX|LOCK_NB) == 0) break; save_errno = errno; /* Mark our pid, and username to our file. */ rewind(pidfp); /* 31 == MAXLOGNAME - 1 */ if (fscanf(pidfp, "%d\n%31s\n", &otherpid, otherluser) != 2) otherpid = -1; syslog(LOG_DEBUG, "tried to lock %s, in use by pid %d: %s", pidfile, otherpid, strerror(save_errno)); if (otherpid > 0) { syslog(LOG_INFO, "killing prior auth (pid %d) of %s by user %s", otherpid, ipsrc, otherluser); if (kill((pid_t) otherpid, SIGTERM) == -1) { syslog(LOG_INFO, "could not kill process %d: (%m)", otherpid); } } /* * We try to kill the previous process and acquire the lock * for 10 seconds, trying once a second. if we can't after * 10 attempts we log an error and give up. */ if (want_death || ++lockcnt > 10) { if (!want_death) syslog(LOG_ERR, "cannot kill previous authpf (pid %d)", otherpid); fclose(pidfp); pidfp = NULL; pidfd = -1; goto dogdeath; } sleep(1); /* re-open, and try again. The previous authpf process * we killed above should unlink the file and release * it's lock, giving us a chance to get it now */ fclose(pidfp); pidfp = NULL; pidfd = -1; } while (1); /* whack the group list */ gid = getegid(); if (setgroups(1, &gid) == -1) { syslog(LOG_INFO, "setgroups: %s", strerror(errno)); do_death(0); } /* revoke privs */ uid = getuid(); if (setresuid(uid, uid, uid) == -1) { syslog(LOG_INFO, "setresuid: %s", strerror(errno)); do_death(0); } openlog("authpf", LOG_PID | LOG_NDELAY, LOG_DAEMON); if (!check_luser(PATH_BAN_DIR, luser) || !allowed_luser(pw)) { syslog(LOG_INFO, "user %s prohibited", luser); do_death(0); } if (read_config(config)) { syslog(LOG_ERR, "invalid config file %s", PATH_CONFFILE); do_death(0); } if (remove_stale_rulesets()) { syslog(LOG_INFO, "error removing stale rulesets"); do_death(0); } /* We appear to be making headway, so actually mark our pid */ rewind(pidfp); fprintf(pidfp, "%ld\n%s\n", (long)getpid(), luser); fflush(pidfp); (void) ftruncate(fileno(pidfp), ftello(pidfp)); if (change_filter(1, luser, ipsrc) == -1) { printf("Unable to modify filters\r\n"); do_death(0); } if (user_ip && change_table(1, ipsrc) == -1) { printf("Unable to modify table\r\n"); change_filter(0, luser, ipsrc); do_death(0); } while (1) { printf("\r\nHello %s. ", luser); printf("You are authenticated from host \"%s\"\r\n", ipsrc); setproctitle("%s@%s", luser, ipsrc); print_message(PATH_MESSAGE); while (1) { sleep(10); if (want_death) do_death(1); } } /* NOTREACHED */ dogdeath: printf("\r\n\r\nSorry, this service is currently unavailable due to "); printf("technical difficulties\r\n\r\n"); print_message(PATH_PROBLEM); printf("\r\nYour authentication process (pid %ld) was unable to run\n", (long)getpid()); sleep(180); /* them lusers read reaaaaal slow */ die: do_death(0); } /* * reads config file in PATH_CONFFILE to set optional behaviours up */ static int read_config(FILE *f) { char buf[1024]; int i = 0; do { char **ap; char *pair[4], *cp, *tp; int len; if (fgets(buf, sizeof(buf), f) == NULL) { fclose(f); return (0); } i++; len = strlen(buf); if (len == 0) continue; if (buf[len - 1] != '\n' && !feof(f)) { syslog(LOG_ERR, "line %d too long in %s", i, PATH_CONFFILE); return (1); } buf[len - 1] = '\0'; for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) ; /* nothing */ if (!*cp || *cp == '#' || *cp == '\n') continue; for (ap = pair; ap < &pair[3] && (*ap = strsep(&cp, "=")) != NULL; ) { if (**ap != '\0') ap++; } if (ap != &pair[2]) goto parse_error; tp = pair[1] + strlen(pair[1]); while ((*tp == ' ' || *tp == '\t') && tp >= pair[1]) *tp-- = '\0'; if (strcasecmp(pair[0], "anchor") == 0) { if (!pair[1][0] || strlcpy(anchorname, pair[1], sizeof(anchorname)) >= sizeof(anchorname)) goto parse_error; } if (strcasecmp(pair[0], "table") == 0) { if (!pair[1][0] || strlcpy(tablename, pair[1], sizeof(tablename)) >= sizeof(tablename)) goto parse_error; } } while (!feof(f) && !ferror(f)); fclose(f); return (0); parse_error: fclose(f); syslog(LOG_ERR, "parse error, line %d of %s", i, PATH_CONFFILE); return (1); } /* * splatter a file to stdout - max line length of 1024, * used for spitting message files at users to tell them * they've been bad or we're unavailable. */ static void print_message(const char *filename) { char buf[1024]; FILE *f; if ((f = fopen(filename, "r")) == NULL) return; /* fail silently, we don't care if it isn't there */ do { if (fgets(buf, sizeof(buf), f) == NULL) { fflush(stdout); fclose(f); return; } } while (fputs(buf, stdout) != EOF && !feof(f)); fflush(stdout); fclose(f); } /* * allowed_luser checks to see if user "luser" is allowed to * use this gateway by virtue of being listed in an allowed * users file, namely /etc/authpf/authpf.allow . * Users may be listed by , %, or @. * * If /etc/authpf/authpf.allow does not exist, then we assume that * all users who are allowed in by sshd(8) are permitted to * use this gateway. If /etc/authpf/authpf.allow does exist, then a * user must be listed if the connection is to continue, else * the session terminates in the same manner as being banned. */ static int allowed_luser(struct passwd *pw) { char *buf,*lbuf; int matched; size_t len; FILE *f; if ((f = fopen(PATH_ALLOWFILE, "r")) == NULL) { if (errno == ENOENT) { /* * allowfile doesn't exist, thus this gateway * isn't restricted to certain users... */ return (1); } /* * luser may in fact be allowed, but we can't open * the file even though it's there. probably a config * problem. */ syslog(LOG_ERR, "cannot open allowed users file %s (%s)", PATH_ALLOWFILE, strerror(errno)); return (0); } else { /* * /etc/authpf/authpf.allow exists, thus we do a linear * search to see if they are allowed. * also, if username "*" exists, then this is a * "public" gateway, such as it is, so let * everyone use it. */ int gl_init = 0, ngroups = NGROUPS + 1; gid_t groups[NGROUPS + 1]; lbuf = NULL; matched = 0; while ((buf = fgetln(f, &len))) { if (buf[len - 1] == '\n') buf[len - 1] = '\0'; else { if ((lbuf = (char *)malloc(len + 1)) == NULL) err(1, NULL); memcpy(lbuf, buf, len); lbuf[len] = '\0'; buf = lbuf; } if (buf[0] == '@') { /* check login class */ if (strcmp(pw->pw_class, buf + 1) == 0) matched++; } else if (buf[0] == '%') { /* check group membership */ int cnt; struct group *group; if ((group = getgrnam(buf + 1)) == NULL) { syslog(LOG_ERR, "invalid group '%s' in %s (%s)", buf + 1, PATH_ALLOWFILE, strerror(errno)); return (0); } if (!gl_init) { (void) getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups); gl_init++; } for ( cnt = 0; cnt < ngroups; cnt++) { if (group->gr_gid == groups[cnt]) { matched++; break; } } } else { /* check username and wildcard */ matched = strcmp(pw->pw_name, buf) == 0 || strcmp("*", buf) == 0; } if (lbuf != NULL) { free(lbuf); lbuf = NULL; } if (matched) return (1); /* matched an allowed user/group */ } syslog(LOG_INFO, "denied access to %s: not listed in %s", pw->pw_name, PATH_ALLOWFILE); fputs("\n\nSorry, you are not allowed to use this facility!\n", stdout); } fflush(stdout); return (0); } /* * check_luser checks to see if user "luser" has been banned * from using us by virtue of having an file of the same name * in the "luserdir" directory. * * If the user has been banned, we copy the contents of the file * to the user's screen. (useful for telling the user what to * do to get un-banned, or just to tell them they aren't * going to be un-banned.) */ static int check_luser(const char *luserdir, char *l_user) { FILE *f; int n; char tmp[MAXPATHLEN]; n = snprintf(tmp, sizeof(tmp), "%s/%s", luserdir, l_user); if (n < 0 || (u_int)n >= sizeof(tmp)) { syslog(LOG_ERR, "provided banned directory line too long (%s)", luserdir); return (0); } if ((f = fopen(tmp, "r")) == NULL) { if (errno == ENOENT) { /* * file or dir doesn't exist, so therefore * this luser isn't banned.. all is well */ return (1); } else { /* * luser may in fact be banned, but we can't open the * file even though it's there. probably a config * problem. */ syslog(LOG_ERR, "cannot open banned file %s (%s)", tmp, strerror(errno)); return (0); } } else { /* * luser is banned - spit the file at them to * tell what they can do and where they can go. */ syslog(LOG_INFO, "denied access to %s: %s exists", l_user, tmp); /* reuse tmp */ strlcpy(tmp, "\n\n-**- Sorry, you have been banned! -**-\n\n", sizeof(tmp)); while (fputs(tmp, stdout) != EOF && !feof(f)) { if (fgets(tmp, sizeof(tmp), f) == NULL) { fflush(stdout); fclose(f); return (0); } } fclose(f); } fflush(stdout); return (0); } /* * Search for rulesets left by other authpf processes (either because they * died ungracefully or were terminated) and remove them. */ static int remove_stale_rulesets(void) { struct pfioc_ruleset prs; u_int32_t nr; memset(&prs, 0, sizeof(prs)); strlcpy(prs.path, anchorname, sizeof(prs.path)); if (ioctl(dev, DIOCGETRULESETS, &prs)) { if (errno == EINVAL) return (0); else return (1); } nr = prs.nr; while (nr) { char *s, *t; pid_t pid; prs.nr = nr - 1; if (ioctl(dev, DIOCGETRULESET, &prs)) return (1); errno = 0; if ((t = strchr(prs.name, '(')) == NULL) t = prs.name; else t++; pid = strtoul(t, &s, 10); if (!prs.name[0] || errno || (*s && (t == prs.name || *s != ')'))) return (1); if ((kill(pid, 0) && errno != EPERM) || pid == getpid()) { if (recursive_ruleset_purge(anchorname, prs.name)) return (1); } nr--; } return (0); } static int recursive_ruleset_purge(char *an, char *rs) { struct pfioc_trans_e *t_e = NULL; struct pfioc_trans *t = NULL; struct pfioc_ruleset *prs = NULL; int i; /* purge rules */ errno = 0; if ((t = calloc(1, sizeof(struct pfioc_trans))) == NULL) goto no_mem; if ((t_e = calloc(PF_RULESET_MAX+1, sizeof(struct pfioc_trans_e))) == NULL) goto no_mem; t->size = PF_RULESET_MAX+1; t->esize = sizeof(struct pfioc_trans_e); t->array = t_e; for (i = 0; i < PF_RULESET_MAX+1; ++i) { t_e[i].rs_num = i; snprintf(t_e[i].anchor, sizeof(t_e[i].anchor), "%s/%s", an, rs); } t_e[PF_RULESET_MAX].rs_num = PF_RULESET_TABLE; if ((ioctl(dev, DIOCXBEGIN, t) || ioctl(dev, DIOCXCOMMIT, t)) && errno != EINVAL) goto cleanup; /* purge any children */ if ((prs = calloc(1, sizeof(struct pfioc_ruleset))) == NULL) goto no_mem; snprintf(prs->path, sizeof(prs->path), "%s/%s", an, rs); if (ioctl(dev, DIOCGETRULESETS, prs)) { if (errno != EINVAL) goto cleanup; errno = 0; } else { int nr = prs->nr; while (nr) { prs->nr = 0; if (ioctl(dev, DIOCGETRULESET, prs)) goto cleanup; if (recursive_ruleset_purge(prs->path, prs->name)) goto cleanup; nr--; } } no_mem: if (errno == ENOMEM) syslog(LOG_ERR, "calloc failed"); cleanup: free(t); free(t_e); free(prs); return (errno); } /* * Add/remove filter entries for user "luser" from ip "ipsrc" */ static int change_filter(int add, const char *l_user, const char *ip_src) { char *fdpath = NULL, *userstr = NULL, *ipstr = NULL; char *rsn = NULL, *fn = NULL; pid_t pid; gid_t gid; int s; if (add) { struct stat sb; char *pargv[13] = { "pfctl", "-p", "/dev/pf", "-q", "-a", "anchor/ruleset", "-D", "user_id=X", "-D", "user_ip=X", "-f", "file", NULL }; if (l_user == NULL || !l_user[0] || ip_src == NULL || !ip_src[0]) { syslog(LOG_ERR, "invalid luser/ipsrc"); goto error; } if (asprintf(&rsn, "%s/%s", anchorname, rulesetname) == -1) goto no_mem; if (asprintf(&fdpath, "/dev/fd/%d", dev) == -1) goto no_mem; if (asprintf(&ipstr, "user_ip=%s", ip_src) == -1) goto no_mem; if (asprintf(&userstr, "user_id=%s", l_user) == -1) goto no_mem; if (asprintf(&fn, "%s/%s/authpf.rules", PATH_USER_DIR, l_user) == -1) goto no_mem; if (stat(fn, &sb) == -1) { free(fn); if ((fn = strdup(PATH_PFRULES)) == NULL) goto no_mem; } pargv[2] = fdpath; pargv[5] = rsn; pargv[7] = userstr; if (user_ip) { pargv[9] = ipstr; pargv[11] = fn; } else { pargv[8] = "-f"; pargv[9] = fn; pargv[10] = NULL; } switch (pid = fork()) { case -1: syslog(LOG_ERR, "fork failed"); goto error; case 0: /* revoke group privs before exec */ gid = getgid(); if (setregid(gid, gid) == -1) { err(1, "setregid"); } execvp(PATH_PFCTL, pargv); warn("exec of %s failed", PATH_PFCTL); _exit(1); } /* parent */ waitpid(pid, &s, 0); if (s != 0) { syslog(LOG_ERR, "pfctl exited abnormally"); goto error; } gettimeofday(&Tstart, NULL); syslog(LOG_INFO, "allowing %s, user %s", ip_src, l_user); } else { remove_stale_rulesets(); gettimeofday(&Tend, NULL); syslog(LOG_INFO, "removed %s, user %s - duration %ju seconds", ip_src, l_user, (uintmax_t)(Tend.tv_sec - Tstart.tv_sec)); } return (0); no_mem: syslog(LOG_ERR, "malloc failed"); error: free(fdpath); free(rsn); free(userstr); free(ipstr); free(fn); return (-1); } /* * Add/remove this IP from the "authpf_users" table. */ static int change_table(int add, const char *ip_src) { struct pfioc_table io; struct pfr_addr addr; bzero(&io, sizeof(io)); strlcpy(io.pfrio_table.pfrt_name, tablename, sizeof(io.pfrio_table.pfrt_name)); io.pfrio_buffer = &addr; io.pfrio_esize = sizeof(addr); io.pfrio_size = 1; bzero(&addr, sizeof(addr)); if (ip_src == NULL || !ip_src[0]) return (-1); if (inet_pton(AF_INET, ip_src, &addr.pfra_ip4addr) == 1) { addr.pfra_af = AF_INET; addr.pfra_net = 32; } else if (inet_pton(AF_INET6, ip_src, &addr.pfra_ip6addr) == 1) { addr.pfra_af = AF_INET6; addr.pfra_net = 128; } else { syslog(LOG_ERR, "invalid ipsrc"); return (-1); } if (ioctl(dev, add ? DIOCRADDADDRS : DIOCRDELADDRS, &io) && errno != ESRCH) { syslog(LOG_ERR, "cannot %s %s from table %s: %s", add ? "add" : "remove", ip_src, tablename, strerror(errno)); return (-1); } return (0); } /* * This is to kill off states that would otherwise be left behind stateful * rules. This means we don't need to allow in more traffic than we really * want to, since we don't have to worry about any luser sessions lasting * longer than their ssh session. This function is based on * pfctl_kill_states from pfctl. */ static void authpf_kill_states(void) { struct pfctl_kill kill; struct pf_addr target; memset(&kill, 0, sizeof(kill)); memset(&target, 0, sizeof(target)); if (inet_pton(AF_INET, ipsrc, &target.v4) == 1) kill.af = AF_INET; else if (inet_pton(AF_INET6, ipsrc, &target.v6) == 1) kill.af = AF_INET6; else { syslog(LOG_ERR, "inet_pton(%s) failed", ipsrc); return; } /* Kill all states from ipsrc */ memcpy(&kill.src.addr.v.a.addr, &target, sizeof(kill.src.addr.v.a.addr)); memset(&kill.src.addr.v.a.mask, 0xff, sizeof(kill.src.addr.v.a.mask)); if (pfctl_kill_states(dev, &kill, NULL)) syslog(LOG_ERR, "pfctl_kill_states() failed (%m)"); /* Kill all states to ipsrc */ memset(&kill.src, 0, sizeof(kill.src)); memcpy(&kill.dst.addr.v.a.addr, &target, sizeof(kill.dst.addr.v.a.addr)); memset(&kill.dst.addr.v.a.mask, 0xff, sizeof(kill.dst.addr.v.a.mask)); if (pfctl_kill_states(dev, &kill, NULL)) syslog(LOG_ERR, "pfctl_kill_states() failed (%m)"); } /* signal handler that makes us go away properly */ static void need_death(int signo __unused) { want_death = 1; } /* * function that removes our stuff when we go away. */ #ifdef __FreeBSD__ static __dead2 void #else static __dead void #endif do_death(int active) { int ret = 0; if (active) { change_filter(0, luser, ipsrc); if (user_ip) { change_table(0, ipsrc); authpf_kill_states(); } } if (pidfile[0] && pidfd != -1) if (unlink(pidfile) == -1) syslog(LOG_ERR, "cannot unlink %s (%m)", pidfile); exit(ret); } diff --git a/contrib/pf/ftp-proxy/ftp-proxy.c b/contrib/pf/ftp-proxy/ftp-proxy.c index 04d749dd0fff..d8f8b8e67d1f 100644 --- a/contrib/pf/ftp-proxy/ftp-proxy.c +++ b/contrib/pf/ftp-proxy/ftp-proxy.c @@ -1,1142 +1,1139 @@ /* $OpenBSD: ftp-proxy.c,v 1.19 2008/06/13 07:25:26 claudio Exp $ */ /* * Copyright (c) 2004, 2005 Camiel Dobbelaar, * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -__FBSDID("$FreeBSD$"); - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filter.h" #define CONNECT_TIMEOUT 30 #define MIN_PORT 1024 #define MAX_LINE 500 #define MAX_LOGLINE 300 #define NTOP_BUFS 3 #define TCP_BACKLOG 10 #define CHROOT_DIR "/var/empty" #define NOPRIV_USER "proxy" /* pfctl standard NAT range. */ #define PF_NAT_PROXY_PORT_LOW 50001 #define PF_NAT_PROXY_PORT_HIGH 65535 #ifndef LIST_END #define LIST_END(a) NULL #endif #ifndef getrtable #define getrtable(a) 0 #endif #define sstosa(ss) ((struct sockaddr *)(ss)) enum { CMD_NONE = 0, CMD_PORT, CMD_EPRT, CMD_PASV, CMD_EPSV }; struct session { u_int32_t id; struct sockaddr_storage client_ss; struct sockaddr_storage proxy_ss; struct sockaddr_storage server_ss; struct sockaddr_storage orig_server_ss; struct bufferevent *client_bufev; struct bufferevent *server_bufev; int client_fd; int server_fd; char cbuf[MAX_LINE]; size_t cbuf_valid; char sbuf[MAX_LINE]; size_t sbuf_valid; int cmd; u_int16_t port; u_int16_t proxy_port; LIST_ENTRY(session) entry; }; LIST_HEAD(, session) sessions = LIST_HEAD_INITIALIZER(sessions); void client_error(struct bufferevent *, short, void *); int client_parse(struct session *s); int client_parse_anon(struct session *s); int client_parse_cmd(struct session *s); void client_read(struct bufferevent *, void *); int drop_privs(void); void end_session(struct session *); void exit_daemon(void); int get_line(char *, size_t *); void handle_connection(const int, short, void *); void handle_signal(int, short, void *); struct session * init_session(void); void logmsg(int, const char *, ...); u_int16_t parse_port(int); u_int16_t pick_proxy_port(void); void proxy_reply(int, struct sockaddr *, u_int16_t); void server_error(struct bufferevent *, short, void *); int server_parse(struct session *s); int allow_data_connection(struct session *s); void server_read(struct bufferevent *, void *); const char *sock_ntop(struct sockaddr *); void usage(void); char linebuf[MAX_LINE + 1]; size_t linelen; char ntop_buf[NTOP_BUFS][INET6_ADDRSTRLEN]; struct sockaddr_storage fixed_server_ss, fixed_proxy_ss; const char *fixed_server, *fixed_server_port, *fixed_proxy, *listen_ip, *listen_port, *qname, *tagname; int anonymous_only, daemonize, id_count, ipv6_mode, loglevel, max_sessions, rfc_mode, session_count, timeout, verbose; extern char *__progname; void client_error(struct bufferevent *bufev __unused, short what, void *arg) { struct session *s = arg; if (what & EVBUFFER_EOF) logmsg(LOG_INFO, "#%d client close", s->id); else if (what == (EVBUFFER_ERROR | EVBUFFER_READ)) logmsg(LOG_ERR, "#%d client reset connection", s->id); else if (what & EVBUFFER_TIMEOUT) logmsg(LOG_ERR, "#%d client timeout", s->id); else if (what & EVBUFFER_WRITE) logmsg(LOG_ERR, "#%d client write error: %d", s->id, what); else logmsg(LOG_ERR, "#%d abnormal client error: %d", s->id, what); end_session(s); } int client_parse(struct session *s) { /* Reset any previous command. */ s->cmd = CMD_NONE; s->port = 0; /* Commands we are looking for are at least 4 chars long. */ if (linelen < 4) return (1); if (linebuf[0] == 'P' || linebuf[0] == 'p' || linebuf[0] == 'E' || linebuf[0] == 'e') { if (!client_parse_cmd(s)) return (0); /* * Allow active mode connections immediately, instead of * waiting for a positive reply from the server. Some * rare servers/proxies try to probe or setup the data * connection before an actual transfer request. */ if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) return (allow_data_connection(s)); } if (anonymous_only && (linebuf[0] == 'U' || linebuf[0] == 'u')) return (client_parse_anon(s)); return (1); } int client_parse_anon(struct session *s) { if (strcasecmp("USER ftp\r\n", linebuf) != 0 && strcasecmp("USER anonymous\r\n", linebuf) != 0) { snprintf(linebuf, sizeof linebuf, "500 Only anonymous FTP allowed\r\n"); logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); /* Talk back to the client ourself. */ linelen = strlen(linebuf); bufferevent_write(s->client_bufev, linebuf, linelen); /* Clear buffer so it's not sent to the server. */ linebuf[0] = '\0'; linelen = 0; } return (1); } int client_parse_cmd(struct session *s) { if (strncasecmp("PASV", linebuf, 4) == 0) s->cmd = CMD_PASV; else if (strncasecmp("PORT ", linebuf, 5) == 0) s->cmd = CMD_PORT; else if (strncasecmp("EPSV", linebuf, 4) == 0) s->cmd = CMD_EPSV; else if (strncasecmp("EPRT ", linebuf, 5) == 0) s->cmd = CMD_EPRT; else return (1); if (ipv6_mode && (s->cmd == CMD_PASV || s->cmd == CMD_PORT)) { logmsg(LOG_CRIT, "PASV and PORT not allowed with IPv6"); return (0); } if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) { s->port = parse_port(s->cmd); if (s->port < MIN_PORT) { logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id, linebuf); return (0); } s->proxy_port = pick_proxy_port(); proxy_reply(s->cmd, sstosa(&s->proxy_ss), s->proxy_port); logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); } return (1); } void client_read(struct bufferevent *bufev, void *arg) { struct session *s = arg; size_t buf_avail, clientread; int n; do { buf_avail = sizeof s->cbuf - s->cbuf_valid; clientread = bufferevent_read(bufev, s->cbuf + s->cbuf_valid, buf_avail); s->cbuf_valid += clientread; while ((n = get_line(s->cbuf, &s->cbuf_valid)) > 0) { logmsg(LOG_DEBUG, "#%d client: %s", s->id, linebuf); if (!client_parse(s)) { end_session(s); return; } bufferevent_write(s->server_bufev, linebuf, linelen); } if (n == -1) { logmsg(LOG_ERR, "#%d client command too long or not" " clean", s->id); end_session(s); return; } } while (clientread == buf_avail); } int drop_privs(void) { struct passwd *pw; pw = getpwnam(NOPRIV_USER); if (pw == NULL) return (0); tzset(); if (chroot(CHROOT_DIR) != 0 || chdir("/") != 0 || setgroups(1, &pw->pw_gid) != 0 || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0 || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) return (0); return (1); } void end_session(struct session *s) { int serr; logmsg(LOG_INFO, "#%d ending session", s->id); /* Flush output buffers. */ if (s->client_bufev && s->client_fd != -1) evbuffer_write(s->client_bufev->output, s->client_fd); if (s->server_bufev && s->server_fd != -1) evbuffer_write(s->server_bufev->output, s->server_fd); if (s->client_fd != -1) close(s->client_fd); if (s->server_fd != -1) close(s->server_fd); if (s->client_bufev) bufferevent_free(s->client_bufev); if (s->server_bufev) bufferevent_free(s->server_bufev); /* Remove rulesets by commiting empty ones. */ serr = 0; if (prepare_commit(s->id) == -1) serr = errno; else if (do_commit() == -1) { serr = errno; do_rollback(); } if (serr) logmsg(LOG_ERR, "#%d pf rule removal failed: %s", s->id, strerror(serr)); LIST_REMOVE(s, entry); free(s); session_count--; } void exit_daemon(void) { struct session *s, *next; for (s = LIST_FIRST(&sessions); s != LIST_END(&sessions); s = next) { next = LIST_NEXT(s, entry); end_session(s); } if (daemonize) closelog(); exit(0); } int get_line(char *buf, size_t *valid) { size_t i; if (*valid > MAX_LINE) return (-1); /* Copy to linebuf while searching for a newline. */ for (i = 0; i < *valid; i++) { linebuf[i] = buf[i]; if (buf[i] == '\0') return (-1); if (buf[i] == '\n') break; } if (i == *valid) { /* No newline found. */ linebuf[0] = '\0'; linelen = 0; if (i < MAX_LINE) return (0); return (-1); } linelen = i + 1; linebuf[linelen] = '\0'; *valid -= linelen; /* Move leftovers to the start. */ if (*valid != 0) bcopy(buf + linelen, buf, *valid); return ((int)linelen); } void handle_connection(const int listen_fd, short event __unused, void *ev __unused) { struct sockaddr_storage tmp_ss; struct sockaddr *client_sa, *server_sa, *fixed_server_sa; struct sockaddr *client_to_proxy_sa, *proxy_to_server_sa; struct session *s; socklen_t len; int client_fd, fc, on; /* * We _must_ accept the connection, otherwise libevent will keep * coming back, and we will chew up all CPU. */ client_sa = sstosa(&tmp_ss); len = sizeof(struct sockaddr_storage); if ((client_fd = accept(listen_fd, client_sa, &len)) < 0) { logmsg(LOG_CRIT, "accept failed: %s", strerror(errno)); return; } /* Refuse connection if the maximum is reached. */ if (session_count >= max_sessions) { logmsg(LOG_ERR, "client limit (%d) reached, refusing " "connection from %s", max_sessions, sock_ntop(client_sa)); close(client_fd); return; } /* Allocate session and copy back the info from the accept(). */ s = init_session(); if (s == NULL) { logmsg(LOG_CRIT, "init_session failed"); close(client_fd); return; } s->client_fd = client_fd; memcpy(sstosa(&s->client_ss), client_sa, client_sa->sa_len); /* Cast it once, and be done with it. */ client_sa = sstosa(&s->client_ss); server_sa = sstosa(&s->server_ss); client_to_proxy_sa = sstosa(&tmp_ss); proxy_to_server_sa = sstosa(&s->proxy_ss); fixed_server_sa = sstosa(&fixed_server_ss); /* Log id/client early to ease debugging. */ logmsg(LOG_DEBUG, "#%d accepted connection from %s", s->id, sock_ntop(client_sa)); /* * Find out the real server and port that the client wanted. */ len = sizeof(struct sockaddr_storage); if ((getsockname(s->client_fd, client_to_proxy_sa, &len)) < 0) { logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id, strerror(errno)); goto fail; } if (server_lookup(client_sa, client_to_proxy_sa, server_sa) != 0) { logmsg(LOG_CRIT, "#%d server lookup failed (no rdr?)", s->id); goto fail; } if (fixed_server) { memcpy(sstosa(&s->orig_server_ss), server_sa, server_sa->sa_len); memcpy(server_sa, fixed_server_sa, fixed_server_sa->sa_len); } /* XXX: check we are not connecting to ourself. */ /* * Setup socket and connect to server. */ if ((s->server_fd = socket(server_sa->sa_family, SOCK_STREAM, IPPROTO_TCP)) < 0) { logmsg(LOG_CRIT, "#%d server socket failed: %s", s->id, strerror(errno)); goto fail; } if (fixed_proxy && bind(s->server_fd, sstosa(&fixed_proxy_ss), fixed_proxy_ss.ss_len) != 0) { logmsg(LOG_CRIT, "#%d cannot bind fixed proxy address: %s", s->id, strerror(errno)); goto fail; } /* Use non-blocking connect(), see CONNECT_TIMEOUT below. */ if ((fc = fcntl(s->server_fd, F_GETFL)) == -1 || fcntl(s->server_fd, F_SETFL, fc | O_NONBLOCK) == -1) { logmsg(LOG_CRIT, "#%d cannot mark socket non-blocking: %s", s->id, strerror(errno)); goto fail; } if (connect(s->server_fd, server_sa, server_sa->sa_len) < 0 && errno != EINPROGRESS) { logmsg(LOG_CRIT, "#%d proxy cannot connect to server %s: %s", s->id, sock_ntop(server_sa), strerror(errno)); goto fail; } len = sizeof(struct sockaddr_storage); if ((getsockname(s->server_fd, proxy_to_server_sa, &len)) < 0) { logmsg(LOG_CRIT, "#%d getsockname failed: %s", s->id, strerror(errno)); goto fail; } logmsg(LOG_INFO, "#%d FTP session %d/%d started: client %s to server " "%s via proxy %s ", s->id, session_count, max_sessions, sock_ntop(client_sa), sock_ntop(server_sa), sock_ntop(proxy_to_server_sa)); /* Keepalive is nice, but don't care if it fails. */ on = 1; setsockopt(s->client_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof on); setsockopt(s->server_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof on); /* * Setup buffered events. */ s->client_bufev = bufferevent_new(s->client_fd, &client_read, NULL, &client_error, s); if (s->client_bufev == NULL) { logmsg(LOG_CRIT, "#%d bufferevent_new client failed", s->id); goto fail; } bufferevent_settimeout(s->client_bufev, timeout, 0); bufferevent_enable(s->client_bufev, EV_READ | EV_TIMEOUT); s->server_bufev = bufferevent_new(s->server_fd, &server_read, NULL, &server_error, s); if (s->server_bufev == NULL) { logmsg(LOG_CRIT, "#%d bufferevent_new server failed", s->id); goto fail; } bufferevent_settimeout(s->server_bufev, CONNECT_TIMEOUT, 0); bufferevent_enable(s->server_bufev, EV_READ | EV_TIMEOUT); return; fail: end_session(s); } void handle_signal(int sig, short event __unused, void *arg __unused) { /* * Signal handler rules don't apply, libevent decouples for us. */ logmsg(LOG_ERR, "exiting on signal %d", sig); exit_daemon(); } struct session * init_session(void) { struct session *s; s = calloc(1, sizeof(struct session)); if (s == NULL) return (NULL); s->id = id_count++; s->client_fd = -1; s->server_fd = -1; s->cbuf[0] = '\0'; s->cbuf_valid = 0; s->sbuf[0] = '\0'; s->sbuf_valid = 0; s->client_bufev = NULL; s->server_bufev = NULL; s->cmd = CMD_NONE; s->port = 0; LIST_INSERT_HEAD(&sessions, s, entry); session_count++; return (s); } void logmsg(int pri, const char *message, ...) { va_list ap; if (pri > loglevel) return; va_start(ap, message); if (daemonize) /* syslog does its own vissing. */ vsyslog(pri, message, ap); else { char buf[MAX_LOGLINE]; char visbuf[2 * MAX_LOGLINE]; /* We don't care about truncation. */ vsnprintf(buf, sizeof buf, message, ap); #ifdef __FreeBSD__ strvis(visbuf, buf, VIS_CSTYLE | VIS_NL); #else strnvis(visbuf, buf, sizeof visbuf, VIS_CSTYLE | VIS_NL); #endif fprintf(stderr, "%s\n", visbuf); } va_end(ap); } int main(int argc, char *argv[]) { struct rlimit rlp; struct addrinfo hints, *res; struct event ev, ev_sighup, ev_sigint, ev_sigterm; int ch, error, listenfd, on; const char *errstr; /* Defaults. */ anonymous_only = 0; daemonize = 1; fixed_proxy = NULL; fixed_server = NULL; fixed_server_port = "21"; ipv6_mode = 0; listen_ip = NULL; listen_port = "8021"; loglevel = LOG_NOTICE; max_sessions = 100; qname = NULL; rfc_mode = 0; tagname = NULL; timeout = 24 * 3600; verbose = 0; /* Other initialization. */ id_count = 1; session_count = 0; while ((ch = getopt(argc, argv, "6Aa:b:D:dm:P:p:q:R:rT:t:v")) != -1) { switch (ch) { case '6': ipv6_mode = 1; break; case 'A': anonymous_only = 1; break; case 'a': fixed_proxy = optarg; break; case 'b': listen_ip = optarg; break; case 'D': loglevel = strtonum(optarg, LOG_EMERG, LOG_DEBUG, &errstr); if (errstr) errx(1, "loglevel %s", errstr); break; case 'd': daemonize = 0; break; case 'm': max_sessions = strtonum(optarg, 1, 500, &errstr); if (errstr) errx(1, "max sessions %s", errstr); break; case 'P': fixed_server_port = optarg; break; case 'p': listen_port = optarg; break; case 'q': if (strlen(optarg) >= PF_QNAME_SIZE) errx(1, "queuename too long"); qname = optarg; break; case 'R': fixed_server = optarg; break; case 'r': rfc_mode = 1; break; case 'T': if (strlen(optarg) >= PF_TAG_NAME_SIZE) errx(1, "tagname too long"); tagname = optarg; break; case 't': timeout = strtonum(optarg, 0, 86400, &errstr); if (errstr) errx(1, "timeout %s", errstr); break; case 'v': verbose++; if (verbose > 2) usage(); break; default: usage(); } } if (listen_ip == NULL) listen_ip = ipv6_mode ? "::1" : "127.0.0.1"; /* Check for root to save the user from cryptic failure messages. */ if (getuid() != 0) errx(1, "needs to start as root"); /* Raise max. open files limit to satisfy max. sessions. */ rlp.rlim_cur = rlp.rlim_max = (2 * max_sessions) + 10; if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) err(1, "setrlimit"); if (fixed_proxy) { memset(&hints, 0, sizeof hints); hints.ai_flags = AI_NUMERICHOST; hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(fixed_proxy, NULL, &hints, &res); if (error) errx(1, "getaddrinfo fixed proxy address failed: %s", gai_strerror(error)); memcpy(&fixed_proxy_ss, res->ai_addr, res->ai_addrlen); logmsg(LOG_INFO, "using %s to connect to servers", sock_ntop(sstosa(&fixed_proxy_ss))); freeaddrinfo(res); } if (fixed_server) { memset(&hints, 0, sizeof hints); hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(fixed_server, fixed_server_port, &hints, &res); if (error) errx(1, "getaddrinfo fixed server address failed: %s", gai_strerror(error)); memcpy(&fixed_server_ss, res->ai_addr, res->ai_addrlen); logmsg(LOG_INFO, "using fixed server %s", sock_ntop(sstosa(&fixed_server_ss))); freeaddrinfo(res); } /* Setup listener. */ memset(&hints, 0, sizeof hints); hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE; hints.ai_family = ipv6_mode ? AF_INET6 : AF_INET; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(listen_ip, listen_port, &hints, &res); if (error) errx(1, "getaddrinfo listen address failed: %s", gai_strerror(error)); if ((listenfd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP)) == -1) errx(1, "socket failed"); on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof on) != 0) err(1, "setsockopt failed"); if (bind(listenfd, (struct sockaddr *)res->ai_addr, (socklen_t)res->ai_addrlen) != 0) err(1, "bind failed"); if (listen(listenfd, TCP_BACKLOG) != 0) err(1, "listen failed"); freeaddrinfo(res); /* Initialize pf. */ init_filter(qname, tagname, verbose); if (daemonize) { if (daemon(0, 0) == -1) err(1, "cannot daemonize"); openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); } /* Use logmsg for output from here on. */ if (!drop_privs()) { logmsg(LOG_ERR, "cannot drop privileges: %s", strerror(errno)); exit(1); } event_init(); /* Setup signal handler. */ signal(SIGPIPE, SIG_IGN); signal_set(&ev_sighup, SIGHUP, handle_signal, NULL); signal_set(&ev_sigint, SIGINT, handle_signal, NULL); signal_set(&ev_sigterm, SIGTERM, handle_signal, NULL); signal_add(&ev_sighup, NULL); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); event_set(&ev, listenfd, EV_READ | EV_PERSIST, handle_connection, &ev); event_add(&ev, NULL); logmsg(LOG_NOTICE, "listening on %s port %s", listen_ip, listen_port); /* Vroom, vroom. */ event_dispatch(); logmsg(LOG_ERR, "event_dispatch error: %s", strerror(errno)); exit_daemon(); /* NOTREACHED */ return (1); } u_int16_t parse_port(int mode) { unsigned int port, v[6]; int n; char *p; /* Find the last space or left-parenthesis. */ for (p = linebuf + linelen; p > linebuf; p--) if (*p == ' ' || *p == '(') break; if (p == linebuf) return (0); switch (mode) { case CMD_PORT: n = sscanf(p, " %u,%u,%u,%u,%u,%u", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]); if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 && v[3] < 256 && v[4] < 256 && v[5] < 256) return ((v[4] << 8) | v[5]); break; case CMD_PASV: n = sscanf(p, "(%u,%u,%u,%u,%u,%u)", &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]); if (n == 6 && v[0] < 256 && v[1] < 256 && v[2] < 256 && v[3] < 256 && v[4] < 256 && v[5] < 256) return ((v[4] << 8) | v[5]); break; case CMD_EPSV: n = sscanf(p, "(|||%u|)", &port); if (n == 1 && port < 65536) return (port); break; case CMD_EPRT: n = sscanf(p, " |1|%u.%u.%u.%u|%u|", &v[0], &v[1], &v[2], &v[3], &port); if (n == 5 && v[0] < 256 && v[1] < 256 && v[2] < 256 && v[3] < 256 && port < 65536) return (port); n = sscanf(p, " |2|%*[a-fA-F0-9:]|%u|", &port); if (n == 1 && port < 65536) return (port); break; default: return (0); } return (0); } u_int16_t pick_proxy_port(void) { /* Random should be good enough for avoiding port collisions. */ return (IPPORT_HIFIRSTAUTO + arc4random_uniform(IPPORT_HILASTAUTO - IPPORT_HIFIRSTAUTO)); } void proxy_reply(int cmd, struct sockaddr *sa, u_int16_t port) { u_int i; int r = 0; switch (cmd) { case CMD_PORT: r = snprintf(linebuf, sizeof linebuf, "PORT %s,%u,%u\r\n", sock_ntop(sa), port / 256, port % 256); break; case CMD_PASV: r = snprintf(linebuf, sizeof linebuf, "227 Entering Passive Mode (%s,%u,%u)\r\n", sock_ntop(sa), port / 256, port % 256); break; case CMD_EPRT: if (sa->sa_family == AF_INET) r = snprintf(linebuf, sizeof linebuf, "EPRT |1|%s|%u|\r\n", sock_ntop(sa), port); else if (sa->sa_family == AF_INET6) r = snprintf(linebuf, sizeof linebuf, "EPRT |2|%s|%u|\r\n", sock_ntop(sa), port); break; case CMD_EPSV: r = snprintf(linebuf, sizeof linebuf, "229 Entering Extended Passive Mode (|||%u|)\r\n", port); break; } if (r < 0 || ((u_int)r) >= sizeof linebuf) { logmsg(LOG_ERR, "proxy_reply failed: %d", r); linebuf[0] = '\0'; linelen = 0; return; } linelen = (size_t)r; if (cmd == CMD_PORT || cmd == CMD_PASV) { /* Replace dots in IP address with commas. */ for (i = 0; i < linelen; i++) if (linebuf[i] == '.') linebuf[i] = ','; } } void server_error(struct bufferevent *bufev __unused, short what, void *arg) { struct session *s = arg; if (what & EVBUFFER_EOF) logmsg(LOG_INFO, "#%d server close", s->id); else if (what == (EVBUFFER_ERROR | EVBUFFER_READ)) logmsg(LOG_ERR, "#%d server refused connection", s->id); else if (what & EVBUFFER_WRITE) logmsg(LOG_ERR, "#%d server write error: %d", s->id, what); else if (what & EVBUFFER_TIMEOUT) logmsg(LOG_NOTICE, "#%d server timeout", s->id); else logmsg(LOG_ERR, "#%d abnormal server error: %d", s->id, what); end_session(s); } int server_parse(struct session *s) { if (s->cmd == CMD_NONE || linelen < 4 || linebuf[0] != '2') goto out; if ((s->cmd == CMD_PASV && strncmp("227 ", linebuf, 4) == 0) || (s->cmd == CMD_EPSV && strncmp("229 ", linebuf, 4) == 0)) return (allow_data_connection(s)); out: s->cmd = CMD_NONE; s->port = 0; return (1); } int allow_data_connection(struct session *s) { struct sockaddr *client_sa, *orig_sa, *proxy_sa, *server_sa; int prepared = 0; /* * The pf rules below do quite some NAT rewriting, to keep up * appearances. Points to keep in mind: * 1) The client must think it's talking to the real server, * for both control and data connections. Transparently. * 2) The server must think that the proxy is the client. * 3) Source and destination ports are rewritten to minimize * port collisions, to aid security (some systems pick weak * ports) or to satisfy RFC requirements (source port 20). */ /* Cast this once, to make code below it more readable. */ client_sa = sstosa(&s->client_ss); server_sa = sstosa(&s->server_ss); proxy_sa = sstosa(&s->proxy_ss); if (fixed_server) /* Fixed server: data connections must appear to come from / go to the original server, not the fixed one. */ orig_sa = sstosa(&s->orig_server_ss); else /* Server not fixed: orig_server == server. */ orig_sa = sstosa(&s->server_ss); /* Passive modes. */ if (s->cmd == CMD_PASV || s->cmd == CMD_EPSV) { s->port = parse_port(s->cmd); if (s->port < MIN_PORT) { logmsg(LOG_CRIT, "#%d bad port in '%s'", s->id, linebuf); return (0); } s->proxy_port = pick_proxy_port(); logmsg(LOG_INFO, "#%d passive: client to server port %d" " via port %d", s->id, s->port, s->proxy_port); if (prepare_commit(s->id) == -1) goto fail; prepared = 1; proxy_reply(s->cmd, orig_sa, s->proxy_port); logmsg(LOG_DEBUG, "#%d proxy: %s", s->id, linebuf); /* rdr from $client to $orig_server port $proxy_port -> $server port $port */ if (add_rdr(s->id, client_sa, orig_sa, s->proxy_port, server_sa, s->port) == -1) goto fail; /* nat from $client to $server port $port -> $proxy */ if (add_nat(s->id, client_sa, server_sa, s->port, proxy_sa, PF_NAT_PROXY_PORT_LOW, PF_NAT_PROXY_PORT_HIGH) == -1) goto fail; /* pass in from $client to $server port $port */ if (add_filter(s->id, PF_IN, client_sa, server_sa, s->port) == -1) goto fail; /* pass out from $proxy to $server port $port */ if (add_filter(s->id, PF_OUT, proxy_sa, server_sa, s->port) == -1) goto fail; } /* Active modes. */ if (s->cmd == CMD_PORT || s->cmd == CMD_EPRT) { logmsg(LOG_INFO, "#%d active: server to client port %d" " via port %d", s->id, s->port, s->proxy_port); if (prepare_commit(s->id) == -1) goto fail; prepared = 1; /* rdr from $server to $proxy port $proxy_port -> $client port $port */ if (add_rdr(s->id, server_sa, proxy_sa, s->proxy_port, client_sa, s->port) == -1) goto fail; /* nat from $server to $client port $port -> $orig_server port $natport */ if (rfc_mode && s->cmd == CMD_PORT) { /* Rewrite sourceport to RFC mandated 20. */ if (add_nat(s->id, server_sa, client_sa, s->port, orig_sa, 20, 20) == -1) goto fail; } else { /* Let pf pick a source port from the standard range. */ if (add_nat(s->id, server_sa, client_sa, s->port, orig_sa, PF_NAT_PROXY_PORT_LOW, PF_NAT_PROXY_PORT_HIGH) == -1) goto fail; } /* pass in from $server to $client port $port */ if (add_filter(s->id, PF_IN, server_sa, client_sa, s->port) == -1) goto fail; /* pass out from $orig_server to $client port $port */ if (add_filter(s->id, PF_OUT, orig_sa, client_sa, s->port) == -1) goto fail; } /* Commit rules if they were prepared. */ if (prepared && (do_commit() == -1)) { if (errno != EBUSY) goto fail; /* One more try if busy. */ usleep(5000); if (do_commit() == -1) goto fail; } s->cmd = CMD_NONE; s->port = 0; return (1); fail: logmsg(LOG_CRIT, "#%d pf operation failed: %s", s->id, strerror(errno)); if (prepared) do_rollback(); return (0); } void server_read(struct bufferevent *bufev, void *arg) { struct session *s = arg; size_t buf_avail, srvread; int n; bufferevent_settimeout(bufev, timeout, 0); do { buf_avail = sizeof s->sbuf - s->sbuf_valid; srvread = bufferevent_read(bufev, s->sbuf + s->sbuf_valid, buf_avail); s->sbuf_valid += srvread; while ((n = get_line(s->sbuf, &s->sbuf_valid)) > 0) { logmsg(LOG_DEBUG, "#%d server: %s", s->id, linebuf); if (!server_parse(s)) { end_session(s); return; } bufferevent_write(s->client_bufev, linebuf, linelen); } if (n == -1) { logmsg(LOG_ERR, "#%d server reply too long or not" " clean", s->id); end_session(s); return; } } while (srvread == buf_avail); } const char * sock_ntop(struct sockaddr *sa) { static int n = 0; /* Cycle to next buffer. */ n = (n + 1) % NTOP_BUFS; ntop_buf[n][0] = '\0'; if (sa->sa_family == AF_INET) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; return (inet_ntop(AF_INET, &sin->sin_addr, ntop_buf[n], sizeof ntop_buf[0])); } if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; return (inet_ntop(AF_INET6, &sin6->sin6_addr, ntop_buf[n], sizeof ntop_buf[0])); } return (NULL); } void usage(void) { fprintf(stderr, "usage: %s [-6Adrv] [-a address] [-b address]" " [-D level] [-m maxsessions]\n [-P port]" " [-p port] [-q queue] [-R address] [-T tag]\n" " [-t timeout]\n", __progname); exit(1); } diff --git a/contrib/pf/pflogd/pflogd.c b/contrib/pf/pflogd/pflogd.c index 2b1b0df769a7..6df97f8b84f4 100644 --- a/contrib/pf/pflogd/pflogd.c +++ b/contrib/pf/pflogd/pflogd.c @@ -1,816 +1,813 @@ /* $OpenBSD: pflogd.c,v 1.46 2008/10/22 08:16:49 henning Exp $ */ /* * Copyright (c) 2001 Theo de Raadt * Copyright (c) 2001 Can Erkin Acar * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ -#include -__FBSDID("$FreeBSD$"); - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __FreeBSD__ #include #include "pidfile.h" #else #include #endif #include "pflogd.h" pcap_t *hpcap; static FILE *dpcap; int Debug = 0; static int snaplen = DEF_SNAPLEN; static int cur_snaplen = DEF_SNAPLEN; volatile sig_atomic_t gotsig_close, gotsig_alrm, gotsig_hup, gotsig_usr1; char *filename = PFLOGD_LOG_FILE; char *interface = PFLOGD_DEFAULT_IF; char *filter = NULL; char errbuf[PCAP_ERRBUF_SIZE]; int log_debug = 0; unsigned int delay = FLUSH_DELAY; char *copy_argv(char * const *); void dump_packet(u_char *, const struct pcap_pkthdr *, const u_char *); void dump_packet_nobuf(u_char *, const struct pcap_pkthdr *, const u_char *); void log_pcap_stats(void); int flush_buffer(FILE *); int if_exists(char *); int init_pcap(void); void logmsg(int, const char *, ...); void purge_buffer(void); int reset_dump(int); int scan_dump(FILE *, off_t); int set_snaplen(int); void set_suspended(int); void sig_alrm(int); void sig_usr1(int); void sig_close(int); void sig_hup(int); void usage(void); static int try_reset_dump(int); /* buffer must always be greater than snaplen */ static int bufpkt = 0; /* number of packets in buffer */ static int buflen = 0; /* allocated size of buffer */ static char *buffer = NULL; /* packet buffer */ static char *bufpos = NULL; /* position in buffer */ static int bufleft = 0; /* bytes left in buffer */ /* if error, stop logging but count dropped packets */ static int suspended = -1; static long packets_dropped = 0; void set_suspended(int s) { if (suspended == s) return; suspended = s; setproctitle("[%s] -s %d -i %s -f %s", suspended ? "suspended" : "running", cur_snaplen, interface, filename); } char * copy_argv(char * const *argv) { size_t len = 0, n; char *buf; if (argv == NULL) return (NULL); for (n = 0; argv[n]; n++) len += strlen(argv[n])+1; if (len == 0) return (NULL); buf = malloc(len); if (buf == NULL) return (NULL); strlcpy(buf, argv[0], len); for (n = 1; argv[n]; n++) { strlcat(buf, " ", len); strlcat(buf, argv[n], len); } return (buf); } void logmsg(int pri, const char *message, ...) { va_list ap; va_start(ap, message); if (log_debug) { vfprintf(stderr, message, ap); fprintf(stderr, "\n"); } else vsyslog(pri, message, ap); va_end(ap); } #ifdef __FreeBSD__ __dead2 void #else __dead void #endif usage(void) { fprintf(stderr, "usage: pflogd [-Dx] [-d delay] [-f filename]"); fprintf(stderr, " [-i interface] [-p pidfile]\n"); fprintf(stderr, " [-s snaplen] [expression]\n"); exit(1); } void sig_close(int sig) { gotsig_close = 1; } void sig_hup(int sig) { gotsig_hup = 1; } void sig_alrm(int sig) { gotsig_alrm = 1; } void sig_usr1(int sig) { gotsig_usr1 = 1; } void set_pcap_filter(void) { struct bpf_program bprog; if (pcap_compile(hpcap, &bprog, filter, PCAP_OPT_FIL, 0) < 0) logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); else { if (pcap_setfilter(hpcap, &bprog) < 0) logmsg(LOG_WARNING, "%s", pcap_geterr(hpcap)); pcap_freecode(&bprog); } } int if_exists(char *ifname) { #ifdef __FreeBSD__ struct ifaddrs *ifdata, *mb; int exists = 0; getifaddrs(&ifdata); if (ifdata == NULL) return (0); for (mb = ifdata; mb != NULL; mb = mb->ifa_next) { if (mb == NULL) continue; if (strlen(ifname) != strlen(mb->ifa_name)) continue; if (strncmp(ifname, mb->ifa_name, strlen(ifname)) != 0) continue; exists = 1; break; } freeifaddrs(ifdata); return (exists); #else int s; struct ifreq ifr; struct if_data ifrdat; if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) err(1, "socket"); bzero(&ifr, sizeof(ifr)); if (strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)) >= sizeof(ifr.ifr_name)) errx(1, "main ifr_name: strlcpy"); ifr.ifr_data = (caddr_t)&ifrdat; if (ioctl(s, SIOCGIFDATA, (caddr_t)&ifr) == -1) return (0); if (close(s)) err(1, "close"); return (1); #endif } int init_pcap(void) { hpcap = pcap_open_live(interface, snaplen, 1, PCAP_TO_MS, errbuf); if (hpcap == NULL) { logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); return (-1); } if (pcap_datalink(hpcap) != DLT_PFLOG) { logmsg(LOG_ERR, "Invalid datalink type"); pcap_close(hpcap); hpcap = NULL; return (-1); } set_pcap_filter(); cur_snaplen = snaplen = pcap_snapshot(hpcap); /* lock */ if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) { logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); return (-1); } return (0); } int set_snaplen(int snap) { if (priv_set_snaplen(snap)) return (1); if (cur_snaplen > snap) purge_buffer(); cur_snaplen = snap; return (0); } int reset_dump(int nomove) { int ret; for (;;) { ret = try_reset_dump(nomove); if (ret <= 0) break; } return (ret); } /* * tries to (re)open log file, nomove flag is used with -x switch * returns 0: success, 1: retry (log moved), -1: error */ int try_reset_dump(int nomove) { struct pcap_file_header hdr; struct stat st; int fd; FILE *fp; if (hpcap == NULL) return (-1); if (dpcap) { flush_buffer(dpcap); fclose(dpcap); dpcap = NULL; } /* * Basically reimplement pcap_dump_open() because it truncates * files and duplicates headers and such. */ fd = priv_open_log(); if (fd < 0) return (-1); fp = fdopen(fd, "a+"); if (fp == NULL) { logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); close(fd); return (-1); } if (fstat(fileno(fp), &st) == -1) { logmsg(LOG_ERR, "Error: %s: %s", filename, strerror(errno)); fclose(fp); return (-1); } /* set FILE unbuffered, we do our own buffering */ if (setvbuf(fp, NULL, _IONBF, 0)) { logmsg(LOG_ERR, "Failed to set output buffers"); fclose(fp); return (-1); } #define TCPDUMP_MAGIC 0xa1b2c3d4 if (st.st_size == 0) { if (snaplen != cur_snaplen) { logmsg(LOG_NOTICE, "Using snaplen %d", snaplen); if (set_snaplen(snaplen)) logmsg(LOG_WARNING, "Failed, using old settings"); } hdr.magic = TCPDUMP_MAGIC; hdr.version_major = PCAP_VERSION_MAJOR; hdr.version_minor = PCAP_VERSION_MINOR; hdr.thiszone = 0; hdr.snaplen = hpcap->snapshot; hdr.sigfigs = 0; hdr.linktype = hpcap->linktype; if (fwrite((char *)&hdr, sizeof(hdr), 1, fp) != 1) { fclose(fp); return (-1); } } else if (scan_dump(fp, st.st_size)) { fclose(fp); if (nomove || priv_move_log()) { logmsg(LOG_ERR, "Invalid/incompatible log file, move it away"); return (-1); } return (1); } dpcap = fp; set_suspended(0); flush_buffer(fp); return (0); } int scan_dump(FILE *fp, off_t size) { struct pcap_file_header hdr; #ifdef __FreeBSD__ struct pcap_sf_pkthdr ph; #else struct pcap_pkthdr ph; #endif off_t pos; /* * Must read the file, compare the header against our new * options (in particular, snaplen) and adjust our options so * that we generate a correct file. Furthermore, check the file * for consistency so that we can append safely. * * XXX this may take a long time for large logs. */ (void) fseek(fp, 0L, SEEK_SET); if (fread((char *)&hdr, sizeof(hdr), 1, fp) != 1) { logmsg(LOG_ERR, "Short file header"); return (1); } if (hdr.magic != TCPDUMP_MAGIC || hdr.version_major != PCAP_VERSION_MAJOR || hdr.version_minor != PCAP_VERSION_MINOR || hdr.linktype != hpcap->linktype || hdr.snaplen > PFLOGD_MAXSNAPLEN) { return (1); } pos = sizeof(hdr); while (!feof(fp)) { off_t len = fread((char *)&ph, 1, sizeof(ph), fp); if (len == 0) break; if (len != sizeof(ph)) goto error; if (ph.caplen > hdr.snaplen || ph.caplen > PFLOGD_MAXSNAPLEN) goto error; pos += sizeof(ph) + ph.caplen; if (pos > size) goto error; fseek(fp, ph.caplen, SEEK_CUR); } if (pos != size) goto error; if (hdr.snaplen != cur_snaplen) { logmsg(LOG_WARNING, "Existing file has different snaplen %u, using it", hdr.snaplen); if (set_snaplen(hdr.snaplen)) { logmsg(LOG_WARNING, "Failed, using old settings, offset %llu", (unsigned long long) size); } } return (0); error: logmsg(LOG_ERR, "Corrupted log file."); return (1); } /* dump a packet directly to the stream, which is unbuffered */ void dump_packet_nobuf(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) { FILE *f = (FILE *)user; #ifdef __FreeBSD__ struct pcap_sf_pkthdr sh; #endif if (suspended) { packets_dropped++; return; } #ifdef __FreeBSD__ sh.ts.tv_sec = (bpf_int32)h->ts.tv_sec; sh.ts.tv_usec = (bpf_int32)h->ts.tv_usec; sh.caplen = h->caplen; sh.len = h->len; if (fwrite((char *)&sh, sizeof(sh), 1, f) != 1) { #else if (fwrite((char *)h, sizeof(*h), 1, f) != 1) { #endif off_t pos = ftello(f); /* try to undo header to prevent corruption */ #ifdef __FreeBSD__ if (pos < sizeof(sh) || ftruncate(fileno(f), pos - sizeof(sh))) { #else if (pos < sizeof(*h) || ftruncate(fileno(f), pos - sizeof(*h))) { #endif logmsg(LOG_ERR, "Write failed, corrupted logfile!"); set_suspended(1); gotsig_close = 1; return; } goto error; } if (fwrite((char *)sp, h->caplen, 1, f) != 1) goto error; return; error: set_suspended(1); packets_dropped ++; logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno)); } int flush_buffer(FILE *f) { off_t offset; int len = bufpos - buffer; if (len <= 0) return (0); offset = ftello(f); if (offset == (off_t)-1) { set_suspended(1); logmsg(LOG_ERR, "Logging suspended: ftello: %s", strerror(errno)); return (1); } if (fwrite(buffer, len, 1, f) != 1) { set_suspended(1); logmsg(LOG_ERR, "Logging suspended: fwrite: %s", strerror(errno)); ftruncate(fileno(f), offset); return (1); } set_suspended(0); bufpos = buffer; bufleft = buflen; bufpkt = 0; return (0); } void purge_buffer(void) { packets_dropped += bufpkt; set_suspended(0); bufpos = buffer; bufleft = buflen; bufpkt = 0; } /* append packet to the buffer, flushing if necessary */ void dump_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) { FILE *f = (FILE *)user; #ifdef __FreeBSD__ struct pcap_sf_pkthdr sh; size_t len = sizeof(sh) + h->caplen; #else size_t len = sizeof(*h) + h->caplen; #endif if (len < sizeof(*h) || h->caplen > (size_t)cur_snaplen) { logmsg(LOG_NOTICE, "invalid size %u (%u/%u), packet dropped", len, cur_snaplen, snaplen); packets_dropped++; return; } if (len <= bufleft) goto append; if (suspended) { packets_dropped++; return; } if (flush_buffer(f)) { packets_dropped++; return; } if (len > bufleft) { dump_packet_nobuf(user, h, sp); return; } append: #ifdef __FreeBSD__ sh.ts.tv_sec = (bpf_int32)h->ts.tv_sec; sh.ts.tv_usec = (bpf_int32)h->ts.tv_usec; sh.caplen = h->caplen; sh.len = h->len; memcpy(bufpos, &sh, sizeof(sh)); memcpy(bufpos + sizeof(sh), sp, h->caplen); #else memcpy(bufpos, h, sizeof(*h)); memcpy(bufpos + sizeof(*h), sp, h->caplen); #endif bufpos += len; bufleft -= len; bufpkt++; return; } void log_pcap_stats(void) { struct pcap_stat pstat; if (pcap_stats(hpcap, &pstat) < 0) logmsg(LOG_WARNING, "Reading stats: %s", pcap_geterr(hpcap)); else logmsg(LOG_NOTICE, "%u packets received, %u/%u dropped (kernel/pflogd)", pstat.ps_recv, pstat.ps_drop, packets_dropped); } int main(int argc, char **argv) { int ch, np, ret, Xflag = 0; pcap_handler phandler = dump_packet; const char *errstr = NULL; char *pidf = NULL; ret = 0; closefrom(STDERR_FILENO + 1); while ((ch = getopt(argc, argv, "Dxd:f:i:p:s:")) != -1) { switch (ch) { case 'D': Debug = 1; break; case 'd': delay = strtonum(optarg, 5, 60*60, &errstr); if (errstr) usage(); break; case 'f': filename = optarg; break; case 'i': interface = optarg; break; case 'p': pidf = optarg; break; case 's': snaplen = strtonum(optarg, 0, PFLOGD_MAXSNAPLEN, &errstr); if (snaplen <= 0) snaplen = DEF_SNAPLEN; if (errstr) snaplen = PFLOGD_MAXSNAPLEN; break; case 'x': Xflag++; break; default: usage(); } } log_debug = Debug; argc -= optind; argv += optind; /* does interface exist */ if (!if_exists(interface)) { warn("Failed to initialize: %s", interface); logmsg(LOG_ERR, "Failed to initialize: %s", interface); logmsg(LOG_ERR, "Exiting, init failure"); exit(1); } if (!Debug) { openlog("pflogd", LOG_PID | LOG_CONS, LOG_DAEMON); if (daemon(0, 0)) { logmsg(LOG_WARNING, "Failed to become daemon: %s", strerror(errno)); } pidfile(pidf); } tzset(); (void)umask(S_IRWXG | S_IRWXO); /* filter will be used by the privileged process */ if (argc) { filter = copy_argv(argv); if (filter == NULL) logmsg(LOG_NOTICE, "Failed to form filter expression"); } /* initialize pcap before dropping privileges */ if (init_pcap()) { logmsg(LOG_ERR, "Exiting, init failure"); exit(1); } /* Privilege separation begins here */ if (priv_init()) { logmsg(LOG_ERR, "unable to privsep"); exit(1); } setproctitle("[initializing]"); /* Process is now unprivileged and inside a chroot */ signal(SIGTERM, sig_close); signal(SIGINT, sig_close); signal(SIGQUIT, sig_close); signal(SIGALRM, sig_alrm); signal(SIGUSR1, sig_usr1); signal(SIGHUP, sig_hup); alarm(delay); buffer = malloc(PFLOGD_BUFSIZE); if (buffer == NULL) { logmsg(LOG_WARNING, "Failed to allocate output buffer"); phandler = dump_packet_nobuf; } else { bufleft = buflen = PFLOGD_BUFSIZE; bufpos = buffer; bufpkt = 0; } if (reset_dump(Xflag) < 0) { if (Xflag) return (1); logmsg(LOG_ERR, "Logging suspended: open error"); set_suspended(1); } else if (Xflag) return (0); while (1) { np = pcap_dispatch(hpcap, PCAP_NUM_PKTS, phandler, (u_char *)dpcap); if (np < 0) { if (!if_exists(interface)) { logmsg(LOG_NOTICE, "interface %s went away", interface); ret = -1; break; } logmsg(LOG_NOTICE, "%s", pcap_geterr(hpcap)); } if (gotsig_close) break; if (gotsig_hup) { if (reset_dump(0)) { logmsg(LOG_ERR, "Logging suspended: open error"); set_suspended(1); } gotsig_hup = 0; } if (gotsig_alrm) { if (dpcap) flush_buffer(dpcap); else gotsig_hup = 1; gotsig_alrm = 0; alarm(delay); } if (gotsig_usr1) { log_pcap_stats(); gotsig_usr1 = 0; } } logmsg(LOG_NOTICE, "Exiting"); if (dpcap) { flush_buffer(dpcap); fclose(dpcap); } purge_buffer(); log_pcap_stats(); pcap_close(hpcap); if (!Debug) closelog(); return (ret); } diff --git a/contrib/pf/pflogd/privsep.c b/contrib/pf/pflogd/privsep.c index 2e3895d2be77..326b4af6fe66 100644 --- a/contrib/pf/pflogd/privsep.c +++ b/contrib/pf/pflogd/privsep.c @@ -1,375 +1,372 @@ /* $OpenBSD: privsep.c,v 1.16 2006/10/25 20:55:04 moritz Exp $ */ /* * Copyright (c) 2003 Can Erkin Acar * Copyright (c) 2003 Anil Madhavapeddy * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -__FBSDID("$FreeBSD$"); - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pflogd.h" enum cmd_types { PRIV_SET_SNAPLEN, /* set the snaplength */ PRIV_MOVE_LOG, /* move logfile away */ PRIV_OPEN_LOG /* open logfile for appending */ }; static int priv_fd = -1; static volatile pid_t child_pid = -1; volatile sig_atomic_t gotsig_chld = 0; static void sig_pass_to_chld(int); static void sig_chld(int); static int may_read(int, void *, size_t); static void must_read(int, void *, size_t); static void must_write(int, void *, size_t); static int set_snaplen(int snap); static int move_log(const char *name); extern char *filename; extern pcap_t *hpcap; /* based on syslogd privsep */ int priv_init(void) { int i, fd, socks[2], cmd; int snaplen, ret, olderrno; struct passwd *pw; #ifdef __FreeBSD__ for (i = 1; i < NSIG; i++) #else for (i = 1; i < _NSIG; i++) #endif signal(i, SIG_DFL); /* Create sockets */ if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) err(1, "socketpair() failed"); pw = getpwnam("_pflogd"); if (pw == NULL) errx(1, "unknown user _pflogd"); endpwent(); child_pid = fork(); if (child_pid < 0) err(1, "fork() failed"); if (!child_pid) { gid_t gidset[1]; /* Child - drop privileges and return */ if (chroot(pw->pw_dir) != 0) err(1, "unable to chroot"); if (chdir("/") != 0) err(1, "unable to chdir"); gidset[0] = pw->pw_gid; if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1) err(1, "setresgid() failed"); if (setgroups(1, gidset) == -1) err(1, "setgroups() failed"); if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1) err(1, "setresuid() failed"); close(socks[0]); priv_fd = socks[1]; return 0; } /* Father */ /* Pass ALRM/TERM/HUP/INT/QUIT through to child, and accept CHLD */ signal(SIGALRM, sig_pass_to_chld); signal(SIGTERM, sig_pass_to_chld); signal(SIGHUP, sig_pass_to_chld); signal(SIGINT, sig_pass_to_chld); signal(SIGQUIT, sig_pass_to_chld); signal(SIGCHLD, sig_chld); setproctitle("[priv]"); close(socks[1]); while (!gotsig_chld) { if (may_read(socks[0], &cmd, sizeof(int))) break; switch (cmd) { case PRIV_SET_SNAPLEN: logmsg(LOG_DEBUG, "[priv]: msg PRIV_SET_SNAPLENGTH received"); must_read(socks[0], &snaplen, sizeof(int)); ret = set_snaplen(snaplen); if (ret) { logmsg(LOG_NOTICE, "[priv]: set_snaplen failed for snaplen %d", snaplen); } must_write(socks[0], &ret, sizeof(int)); break; case PRIV_OPEN_LOG: logmsg(LOG_DEBUG, "[priv]: msg PRIV_OPEN_LOG received"); /* create or append logs but do not follow symlinks */ fd = open(filename, O_RDWR|O_CREAT|O_APPEND|O_NONBLOCK|O_NOFOLLOW, 0600); olderrno = errno; send_fd(socks[0], fd); if (fd < 0) logmsg(LOG_NOTICE, "[priv]: failed to open %s: %s", filename, strerror(olderrno)); else close(fd); break; case PRIV_MOVE_LOG: logmsg(LOG_DEBUG, "[priv]: msg PRIV_MOVE_LOG received"); ret = move_log(filename); must_write(socks[0], &ret, sizeof(int)); break; default: logmsg(LOG_ERR, "[priv]: unknown command %d", cmd); _exit(1); /* NOTREACHED */ } } _exit(1); } /* this is called from parent */ static int set_snaplen(int snap) { if (hpcap == NULL) return (1); hpcap->snapshot = snap; set_pcap_filter(); return 0; } static int move_log(const char *name) { char ren[PATH_MAX]; int len; for (;;) { int fd; len = snprintf(ren, sizeof(ren), "%s.bad.%08x", name, arc4random()); if (len >= sizeof(ren)) { logmsg(LOG_ERR, "[priv] new name too long"); return (1); } /* lock destinanion */ fd = open(ren, O_CREAT|O_EXCL, 0); if (fd >= 0) { close(fd); break; } /* if file exists, try another name */ if (errno != EEXIST && errno != EINTR) { logmsg(LOG_ERR, "[priv] failed to create new name: %s", strerror(errno)); return (1); } } if (rename(name, ren)) { logmsg(LOG_ERR, "[priv] failed to rename %s to %s: %s", name, ren, strerror(errno)); return (1); } logmsg(LOG_NOTICE, "[priv]: log file %s moved to %s", name, ren); return (0); } /* * send the snaplength to privileged process */ int priv_set_snaplen(int snaplen) { int cmd, ret; if (priv_fd < 0) errx(1, "%s: called from privileged portion", __func__); cmd = PRIV_SET_SNAPLEN; must_write(priv_fd, &cmd, sizeof(int)); must_write(priv_fd, &snaplen, sizeof(int)); must_read(priv_fd, &ret, sizeof(int)); /* also set hpcap->snapshot in child */ if (ret == 0) hpcap->snapshot = snaplen; return (ret); } /* Open log-file */ int priv_open_log(void) { int cmd, fd; if (priv_fd < 0) errx(1, "%s: called from privileged portion", __func__); cmd = PRIV_OPEN_LOG; must_write(priv_fd, &cmd, sizeof(int)); fd = receive_fd(priv_fd); return (fd); } /* Move-away and reopen log-file */ int priv_move_log(void) { int cmd, ret; if (priv_fd < 0) errx(1, "%s: called from privileged portion\n", __func__); cmd = PRIV_MOVE_LOG; must_write(priv_fd, &cmd, sizeof(int)); must_read(priv_fd, &ret, sizeof(int)); return (ret); } /* If priv parent gets a TERM or HUP, pass it through to child instead */ static void sig_pass_to_chld(int sig) { int oerrno = errno; if (child_pid != -1) kill(child_pid, sig); errno = oerrno; } /* if parent gets a SIGCHLD, it will exit */ static void sig_chld(int sig) { gotsig_chld = 1; } /* Read all data or return 1 for error. */ static int may_read(int fd, void *buf, size_t n) { char *s = buf; ssize_t res, pos = 0; while (n > pos) { res = read(fd, s + pos, n - pos); switch (res) { case -1: if (errno == EINTR || errno == EAGAIN) continue; case 0: return (1); default: pos += res; } } return (0); } /* Read data with the assertion that it all must come through, or * else abort the process. Based on atomicio() from openssh. */ static void must_read(int fd, void *buf, size_t n) { char *s = buf; ssize_t res, pos = 0; while (n > pos) { res = read(fd, s + pos, n - pos); switch (res) { case -1: if (errno == EINTR || errno == EAGAIN) continue; case 0: _exit(0); default: pos += res; } } } /* Write data with the assertion that it all has to be written, or * else abort the process. Based on atomicio() from openssh. */ static void must_write(int fd, void *buf, size_t n) { char *s = buf; ssize_t res, pos = 0; while (n > pos) { res = write(fd, s + pos, n - pos); switch (res) { case -1: if (errno == EINTR || errno == EAGAIN) continue; case 0: _exit(0); default: pos += res; } } }