diff options
author | Bob Beck <beck@cvs.openbsd.org> | 2002-04-01 17:43:43 +0000 |
---|---|---|
committer | Bob Beck <beck@cvs.openbsd.org> | 2002-04-01 17:43:43 +0000 |
commit | aa2ec0d4377ea46798a91e0652e00dc623479dbb (patch) | |
tree | 487bc7ec557790af0bc6c7d437585a2a508d5a76 /usr.sbin/authpf | |
parent | 876a6ac37a64aece433f7a7271db46862b83a39a (diff) |
authpf - authenticating gateway shell for use with ssh(1) to make
authenticating gateway type firewalls.
caveats - needs to be setuid to opertate (but does not install that way)
consult the man page for configuration issues.
Diffstat (limited to 'usr.sbin/authpf')
-rw-r--r-- | usr.sbin/authpf/Makefile | 9 | ||||
-rw-r--r-- | usr.sbin/authpf/authpf.8 | 424 | ||||
-rw-r--r-- | usr.sbin/authpf/authpf.c | 924 | ||||
-rw-r--r-- | usr.sbin/authpf/pathnames.h | 38 |
4 files changed, 1395 insertions, 0 deletions
diff --git a/usr.sbin/authpf/Makefile b/usr.sbin/authpf/Makefile new file mode 100644 index 00000000000..d99167a52d1 --- /dev/null +++ b/usr.sbin/authpf/Makefile @@ -0,0 +1,9 @@ +# $OpenBSD: Makefile,v 1.1 2002/04/01 17:43:42 beck Exp $ + +PROG= authpf +MAN= authpf.8 +SRCS= authpf.c parse.y pfctl_parser.c +CFLAGS+= -I${.CURDIR}/../../sbin/pfctl -Wall -Werror +.PATH: ${.CURDIR}/../../sbin/pfctl + +.include <bsd.prog.mk> diff --git a/usr.sbin/authpf/authpf.8 b/usr.sbin/authpf/authpf.8 new file mode 100644 index 00000000000..f3e49281665 --- /dev/null +++ b/usr.sbin/authpf/authpf.8 @@ -0,0 +1,424 @@ +\" $OpenBSD: authpf.8,v 1.1 2002/04/01 17:43:42 beck Exp $ +.\" +.\" Copyright (c) 2002 Bob Beck (beck@openbsd.org>. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. 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. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. +.\" +.Dd Jan 10, 2002 +.Dt AUTHPF 8 +.Os +.Sh NAME +.Nm authpf +.Nd authenticating gateway user shell +.Sh SYNOPSIS +.Nm authpf +.Sh DESCRIPTION +.Nm +is used as a user shell for authenticating gateways. It is used to +change +.Xr pf 4 +rules when a user authenticates and starts a session with +.Xr sshd 8 +and to then undo the changes when the user's session exits. It is designed +for changing filter and translation rules for an individual source IP address +as long as a user maintains an active +.Xr ssh 1 +session. Typical use would be for a gateway that authenticates users before +allowing them Internet use, or a gateway that allows different users into +different places. +.Nm +logs the successful start and end of a session to +.Xr syslog 8 . +This, combined with properly set up filter rules and secure switches +can be used to ensure users are held accountable for their network traffic. +.Pp +.Nm +can add and filter rules using the syntax of +.Xr pf.conf 5 +and translation rules using the syntax of +.Xr nat.conf 5 . +.Nm +requires that the +.Xr pf 4 +system be enabled before use. +.Pp +.Nm +is meant to be used with users who can connect via +.Xr ssh 1 +only. On startup, +.Nm +retrieves the client's connecting IP address via the +.Ev SSH_CLIENT +environment variable, and after performing additional access checks, reads +a filter rule template file is read to determine what filter rules to add. +Optionally, a translation rule template file is read to determine translation +rules to add. On session exit the same rules that were added at startup are +removed. By default, filter rules are added at the end of the active +.Xr pf 4 +filter list, and translation rules are added at the start of the active +.Xr pf 4 +nat and rdr lists. +.Sh FILTER AND TRANSLATION RULES +Filter and Translation rules for +.Nm +use the same format described in +.Xr pf.conf 5 +and +.Xr nat.conf 5 . +The only difference is that these rules may (and probably should) use +the macro +.Em user_ip +which is defined to the connecting ip address whenever +.Nm +is run. +.Pp +Filter rules are loaded from the file +.Pa $HOME/.authpf/authpf.rules . +If this file does not exist the file +.Pa /etc/authpf/authpf.rules +is used. The +.Pa authpf.rules +file must exist in either the user's +.Pa $HOME/.authpf/ +directory, or in +.Pa /etc/authpf , +for +.Nm +to run. +.Pp +Translation rules are loaded from the file +.Pa $HOME/.authpf/authpf.nat . +If this file does not exist the file +.Pa /etc/authpf/authpf.nat +is used. The use of translation rules in an +.Pa authpf.nat +file is optional. +.Sh OPTIONS +Options are controlled by the +.Pa /etc/authpf/authpf.conf +file. This file is optional, is not needed unless the default behavior +needs to be changed. The file consists of pairs of the form +.Li name=value +one per line. Currently, the allowed values are as follows: +.Bl -tag -width Ds +.It rule_action=[head|tail] +controls where filter rules are added, the default behavior is "tail" +meaning filter rules are added to the end of the active filter list. +.It Dv nat_action=[head|tail] +controls where nat rules are added, the default behavior is "head" +meaning filter rules are added to the start of the active nat list. +.It Dv rdr_action=[head|tail] +controls where rdr rules are added, the default behavior is "head" +meaning filter rules are added to the start of the active rdr list. +.El +.Sh USER MESSAGES +On successful invocation, +.Nm +displays a message telling the user they have been authenticated. It will +additionally display the contents of the file +.Pa /etc/authpf/authpf.message +if the file exists and is readable. +.Pp +There exist two methods for providing additional granularity to the control +offered by +.Nm +- it is possible to set the gateway to explicitly allow users who have +authenticated to +.Xr ssh 1 +and deny access to only a few troublesome individuals. This is done by +creating a file with the banned user's login name in +.Pa /var/authpf/banned . +The contents of this file will be displayed to a banned user, thus providing +a method for informing the user that they have been banned, and where they can +go and how to get there if they want to have their service restored. This is +the default behaviour. +.Pp +It is also possible to configure +.Nm +to only allow specific users access. This is done by listing their login +names, one per line, in +.Pa /etc/authpf/authpf.allow . +If "*" is found on a line, then all usernames match. If +.Nm +is unable to verify the user's permission to use the gateway, it will +print a brief message and die. It should be noted that a ban takes precedence +over an allow. +.Pp +On failure, messages will be logged to +.Xr syslog 8 +for the system administrator. The user does not see these, but +will be told the system is unavailable due to technical difficulties. +The contents of the file +.Pa /etc/authpf/authpf.problem +will also be displayed if the file exists and is readable. +.Sh CONFIGURATION ISSUES +.Nm +maintains the changed filter rules as long as the user maintains an +active session. It is important to remember however, that the existence +of this session means the user is authenticated. Because of this, it +is important to both configure +.Xr sshd 8 +to ensure the security of the session, and to ensure that the network +by which users connect to use. +.Xr sshd 8 +should be configured to use the +.Dv ClientAliveInterval +and +.Dv ClientAliveCountMax +parameters to ensure than an ssh session is terminated quickly if +it becomes unresponsive, or if arp or address spoofing is used to +hijack the session. Note that TCP keepalives are not sufficient for +this, since they are not secure. +.Pp +.Nm +will remove state table entries that were created during a user's +session. This ensures that there will be no unauthenticated traffic +allowed to pass after the controlling +.Xr ssh 1 +session has been closed. +.Pp +.Nm +is designed for gateway machines with don't typically have regular +(non-administrative) users using the machine. An administrator +must remember that +.Nm +can be used to modify the filter rules through the environment in +which it is run, and as such could be used to modify the filter rules +(based on the contents of the configuration files) by regular +users. In the case where a machine has regular users using it, as well +as users with +.Nm +as their shell, the regular users should be prevented from running +.Nm +by using the +.Pa /etc/authpf/authpf.allow +or +.Pa /var/authpf/banned/ +facilities. +.Pp +.Nm +must be setuid-root in order to modify the packet filter and translation +rules, though it is not installed with the setuid bit turned on. After +considering the effect +.Nm +may have on the main packet filter rules, the system administrator may run +the following command to enable +.Nm +: "chmod +s /usr/sbin/authpf" . +.Sh EXAMPLES +\fBControl Files\fP - To illustrate the user-specific access control +mechanisms, let us consider a typical user named bob. Normally, as long as +bob can authenticate himself, the +.Nm +program will load the appropriate rules. Enter the +.Pa /var/authpf/banned/ +directory. If bob has somehow fallen from grace in the eyes of the +powers-that-be, they can prohibit him from using the gateway by creating +the file +.Pa /var/authpf/banned/bob +containing a message about why he has been banned from using the network. +Once bob has done suitable pennance, his access may be restored by moving or +removing the file +.Pa /var/authpf/banned/bob. +.Pp +Now consider a workgroup containing alice, bob, carol and dave. They have a +wireless network which they would like to protect from unauthorized use. To +accomplish this, they create the file +.Pa /etc/authpf/authpf.allow +which lists their login ids, one per line. At this point, even if eve could +authenticate to +.Xr sshd 8 , +she would not be allowed to use the gateway. Adding and removing users from +the work group is a simple matter of maintaining a list of allowed userids. +If bob once again manages to annoy the powers-that-be, they can ban him from +using the gateway by creating the familiar +.Pa /var/authpf/banned/bob +file. Though bob is listed in the allow file, he is prevented from using +this gateway due to the existence of a ban file. +.Pp +\fBDistributed Authentication\fP - It is often desirable to interface with a +distributed password system rather than forcing the sysadmins to keep a large +number of local password files in sync. The +.Xr login.conf 5 +mechanism in +.Ox +can be used to fork the right shell. To make that happen, +.Xr login.conf 5 +should have entries that look something like this: +.Bd -literal +shell-default:shell=/bin/csh + +default:\\ + ... + :shell=/usr/sbin/authpf + +daemon:\\ + ... + :shell=/bin/csh:\\ + :tc=default: + +staff:\\ + ... + :shell=/bin/csh:\\ + :tc=default: +.Ed +.Pp +Using a default password file, all users will get +.Nm +as their shell except for root who will get +.Pa /bin/csh. +.Pp +\fBSSH Configuration\fP - As stated earlier, +.Xr sshd 8 +must be properly configured to detect and defeat network attacks. To that end, +the following options should be added to +.Pa sshd_config : +.Bd -literal +ClientAliveInterval 15 +ClientAliveCountMax 3 + +.Ed +This ensures that unresponsive or spoofed session are terminated in under a +minute, since a hijacker should not be able to spoof ssh keepalive messages. +.Pp +.Pp +\fBBanners\fP - Once authenticated, the user is shown the contents of +.Pa /etc/authpf/authpf.message. +This message may be a screen-full of the appropriate use policy, the contents +of +.Pa /etc/motd +or something as simple as the following: +.Bd -literal + + This means you will be held accountable by the powers that be + for traffic originating from your machine, so please play nice. +.Ed +.Pp +To tell the user where to go when the system is broken, +.Pa /etc/authpf/authpf.problem +could contain something like this: +.Bd -literal + + Sorry, there appears to be some system problem. To report this + problem so we can fix it, please phone 1-900-314-1597 or send + an email to remove@bulkmailerz.net. +.Ed +.Pp +\fBPacket Filter Rules\fP - In areas where this gateway is used to protect a +wireless network (a hub with several hundred ports) the default rule set as +well as the per-user rules should probably allow very few things beyond +encrypted protocols like +.Xr ssh 1 , +.Xr ssl 8 , +or +.Xr ipsec 4 . +On a securely switched network, with plug-in jacks for visitors who are +given authentication accounts, you might want to allow out everything. In +this context, a secure switch is one that tries to prevent address table +overflow attacks. The examples below assume a switched wired net. +.Pp +Example +.Pa /etc/pf.conf : +.Bd -literal +# by default we allow internal clients to talk to us using +# ssh and use us as a dns server. +internal_if="fxp1" +gateway_addr="10.0.1.1" +block in on $internal_if from any to any +pass in quick on $internal_if proto tcp from any to $gateway_addr/32 \\ + port = ssh +pass in quick on $internal_if proto udp from any to $gateway_addr/32 \\ + port = domain +.Ed +.Pp +Example +.Pa /etc/authpf/authpf.rules : +.Bd -literal +# no real restrictions here, basically turn the network jack off or on. + +external_if = "xl0" +internal_if = "fxp0" + +pass in quick log on $internal_if proto tcp from $user_ip/32 to any \\ + keep state +pass in quick on $internal_if from $user_ip/32 to any +.Ed +.Pp +Example +.Pa /etc/authpf/authpf.nat : +.Bd -literal +# When the user authenticates, rdr ftp for proxying by ftp-proxy(8) +internal_if="fxp1" +rdr on $internal_if proto tcp from $user_ip/32 to any port 21 \\ + -> 127.0.0.1 port 8081 +.Ed +.Pp +Another example +.Pa /etc/authpf/authpf.rules +for an insecure network (such as a public wireless network) where +we might need to be a bit more restrictive. +.Bd -literal +internal_if="fxp1" +ipsec_gw="10.2.3.4" +# allow out ftp, ssh, www and https only, and allow user to negotiate +# ipsec with the ipsec server. +pass in quick log on $internal_if proto tcp from $user_ip/32 to any \\ + { port 21, 22, 80, 443 } flags S/SA +pass in quick on $internal_if proto tcp from $user_ip/32 to any \\ + { port 21, 22, 80, 443 } +pass in quick proto udp from $user_ip/32 to $ipsec_gw/32 port = isakmp \\ + keep-state +pass in quick proto esp from $user_ip/32 to $ipsec_gw/32 +.Ed +.Sh FILES +.Bl -tag -width "/etc/authpf/authpf.conf" -compact +.It Pa /etc/authpf/authpf.conf +.It Pa /etc/authpf/authpf.allow +.It Pa /etc/authpf/authpf.rules +.It Pa /etc/authpf/authpf.nat +.It Pa /etc/authpf/authpf.message +.It Pa /etc/authpf/authpf.problem +.El +.Sh SEE ALSO +.Xr pf 4 , +.Xr nat.conf 5 , +.Xr pf.conf 5 , +.Xr ftp-proxy 8 +.Sh BUGS +.Pp +.Nm +does not support binat translation rules. +.Pp +Configuration issues are tricky. The authenticating +.Xr ssh 1 +connection may be secured, but if the network is not secured the user may +expose insecure protocols to attackers on the same network, or enable other +attackers on network to pretend to be the user by spoofing their IP address. +.Pp +.Nm +is not designed to prevent users from denying service to other users. +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 3.1 . diff --git a/usr.sbin/authpf/authpf.c b/usr.sbin/authpf/authpf.c new file mode 100644 index 00000000000..f065343f1a2 --- /dev/null +++ b/usr.sbin/authpf/authpf.c @@ -0,0 +1,924 @@ +/* + * Copyright (C) 1998 - 2002 Bob Beck (beck@openbsd.org). + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the author nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <sys/types.h> +#include <sys/file.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <net/pfvar.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +#include <arpa/nameser.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <netdb.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <string.h> +#include <strings.h> +#include <sysexits.h> +#include <syslog.h> +#include <unistd.h> +#include <resolv.h> + +#include <pfctl_parser.h> + +#include "pathnames.h" + +int Rule_Action = PF_CHANGE_ADD_TAIL; +int Nat_Action = PF_CHANGE_ADD_HEAD; +int Rdr_Action = PF_CHANGE_ADD_HEAD; +int dev; /* pf device */ +int Delete_Rules; /* for parse_rules callbacks */ + +char *infile; /* infile name needed by parse_[rules|nat] */ +char luser[MAXLOGNAME] = ""; /* username */ +char ipsrc[256] = ""; /* ip as a string */ +char pidfile[MAXPATHLEN]; /* we save pid in this file. */ +char userfile[MAXPATHLEN]; /* we save username in this file */ +char configfile[] = PATH_CONFFILE; +char allowfile[] = PATH_ALLOWFILE; + +struct timeval Tstart, Tend; /* start and end times of session */ +static volatile sig_atomic_t hasta_la_vista; + +int pfctl_add_rule(struct pfctl *, struct pf_rule *); +int pfctl_add_nat(struct pfctl *, struct pf_nat *); +int pfctl_add_rdr(struct pfctl *, struct pf_rdr *); +int pfctl_add_binat(struct pfctl *, struct pf_binat *); + +static void read_config(void); +static void print_message(char *); +static int allowed_luser(char *); +static int check_luser(char *, char *); +static int changefilter(int, char *, char *); +static void authpf_kill_states(void); +static void terminator(int s); +static __dead void go_away(void); + +/* + * authpf: + * 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(int argc, char *argv[]) +{ + int pidfd, ufd, namelen; + int lockcnt = 0; + char *foo, *cp; + FILE *fp = NULL; + struct sockaddr *namep; + struct sockaddr_in peer; + char bannedir[] = PATH_BAN_DIR; + + namep = (struct sockaddr *)&peer; + namelen = sizeof(peer); + memset(namep, 0, namelen); + + openlog("authpf", LOG_PID | LOG_NDELAY, LOG_DAEMON); + + read_config(); + + if ((foo = getenv("LOGNAME")) != NULL) + strlcpy(luser, foo, sizeof(luser)); + else if ((foo = getenv("USER")) != NULL) + strlcpy(luser, foo, sizeof(luser)); + else { + syslog(LOG_ERR, "No user given!"); + exit(EX_CONFIG); + } + + if ((foo = getenv("SSH_CLIENT")) != NULL) { + strlcpy(ipsrc, foo, sizeof(ipsrc)); + cp = ipsrc; + while (*cp != '\0') { + if (*cp == ' ') + *cp = '\0'; + else + cp++; + } + } else { + syslog(LOG_ERR, "Can't determine connection source"); + exit(EX_CONFIG); + } + if (!check_luser(bannedir, luser) || !allowed_luser(luser)) { + /* give the luser time to read our nastygram on the screen */ + sleep(180); + exit(EX_NOPERM); + } + + /* + * make ourselves an entry in /var/run as /var/run/authpf-ipaddr, + * so that things may find us easily to kill us if necessary. + */ + + if (snprintf(pidfile, sizeof pidfile, "%s-%s", PATH_PIDFILE, ipsrc) >= + sizeof pidfile) { + fprintf(stderr, "Sorry, host too long for me to handle..\n"); + syslog(LOG_ERR, "snprintf pidfile bogosity - exiting"); + goto dogdeath; + } + + if ((pidfd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1 || + (fp = fdopen(pidfd, "r+")) == NULL) { + syslog(LOG_ERR, "can't open or create %s: %s", + pidfile, strerror(errno)); + goto dogdeath; + } + + /* + * If someone else is already using this ip, then this person + * wants to switch users - so kill the old process and we + * exit as well. Of course, this will only work if we are + * running with priviledge. + * + * 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. + */ + + while (flock(pidfd, LOCK_EX|LOCK_NB) == -1 && errno != EBADF) { + int otherpid = -1; + int save_errno = errno; + + lockcnt++; + fscanf(fp, "%d", &otherpid); + syslog (LOG_DEBUG, "Tried to lock %s, in use by pid %d: %s", + pidfile, otherpid, strerror(save_errno)); + fclose(fp); + + close(pidfd); + if (otherpid > 0) { + syslog(LOG_INFO, + "killing prior auth (pid %d) of %s by user %s", + otherpid, ipsrc, luser); + if (kill((pid_t) otherpid, SIGTERM) == -1) { + syslog (LOG_INFO, + "Couldn't kill process %d: (%m)", + otherpid); + } + } + + /* we try to kill the previous process and aquire the lock + * for 10 seconds, trying once a second. if we can't after + * 10 attempts we log an error and give up + */ + + if (lockcnt > 10) { + syslog(LOG_ERR, "Can't kill previous authpf (pid %d)", + otherpid); + goto dogdeath; + } + sleep(1); + } + + fp = fopen(pidfile, "w+"); + rewind(fp); + fprintf(fp, "%d\n", getpid()); + fflush(fp); + (void) ftruncate(fileno(fp), ftell(fp)); + + /* open the pf device */ + + dev = open (PATH_DEVFILE, O_RDWR); + if (dev == -1) { + syslog(LOG_ERR, "Can't open filter device (%m)"); + goto dogdeath; + } + + /* + * make an entry in file /var/authpf/ipaddr, containing the username. + * this lets external applications check for authentication by looking + * for the ipaddress in that directory, and retrieving the username + * from it. + */ + + snprintf(userfile, sizeof(userfile), "%s/%s", PATH_USERFILE, ipsrc); + if ((ufd = open(userfile, O_CREAT|O_WRONLY, 0640)) == -1) { + syslog(LOG_ERR, "Can't open \"%s\" ! (%m)", userfile); + goto dogdeath; + } + + write(ufd, luser, strlen(luser)); + write(ufd, "\n", 1); + close(ufd); + + if (changefilter(1, luser, ipsrc) == -1) { + /* XXX */ + } + + signal(SIGTERM, terminator); + signal(SIGINT, terminator); + signal(SIGALRM, terminator); + signal(SIGPIPE, terminator); + signal(SIGHUP, terminator); + signal(SIGSTOP, terminator); + signal(SIGTSTP, terminator); + while(1) { + printf("\r\nHello %s, ", luser); + printf("You are authenticated from host \"%s\"\r\n", ipsrc); + print_message(PATH_MESSAGE); + while (1) { + sleep(10); + if (hasta_la_vista) + go_away(); + } + } + /* 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 %d) was unable to run\n", + getpid()); + sleep(180); /* them lusers read reaaaaal slow */ + if (pidfile[0] != '\0') + unlink(pidfile); /* fail silently */ + if (userfile[0] != '\0') + unlink(userfile); /* fail silently */ + exit(EX_CONFIG); +} + +/* read_config: + * reads config file in PATH_CONFILE to set optional behaviours up + */ + +static void +read_config(void) +{ + char buf[1024]; + int i = 0; + FILE *f; + + f = fopen(configfile, "r"); + if (f == NULL) + return; /* fail silently, run with defaults */ + + do { + char **ap, *pair[4], *cp, *tp; + int len; + + if (fgets(buf, sizeof(buf), f) == NULL) { + fclose(f); + return; + } + i++; + len = strlen(buf); + if (buf[len - 1] != '\n' && !feof(f)) { + syslog(LOG_ERR, "line %d too long in %s", i, + configfile); + exit(EX_CONFIG); + } + buf[len - 1] = '\0'; + + for (cp = buf; *cp == ' ' || *cp == '\t'; cp++) + ; + + 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], "rule_action") == 0) { + if (strcasecmp(pair[1], "head") == 0) + Rule_Action = PF_CHANGE_ADD_HEAD; + else if (strcasecmp(pair[1], "tail") == 0) + Rule_Action = PF_CHANGE_ADD_TAIL; + else + goto parse_error; + } else if (strcasecmp(pair[0], "nat_action") == 0) { + if (strcasecmp(pair[1], "head") == 0) + Nat_Action = PF_CHANGE_ADD_HEAD; + else if (strcasecmp(pair[1], "tail") == 0) + Nat_Action = PF_CHANGE_ADD_TAIL; + else + goto parse_error; + + } else if (strcasecmp(pair[0], "rdr_action") == 0) { + if (strcasecmp(pair[1], "head") == 0) + Rdr_Action = PF_CHANGE_ADD_HEAD; + else if (strcasecmp(pair[1], "tail") == 0) + Rdr_Action = PF_CHANGE_ADD_TAIL; + else + goto parse_error; + } + } while (!feof(f) && !ferror(f)); + fclose(f); + return; + parse_error: + fclose(f); + syslog(LOG_ERR, "parse error, line %d of %s", i, configfile); + exit(EX_CONFIG); +} + + +/* + * print_message: + * 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(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)); + fclose(f); +} + +/* + * allowed_luser: + * 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.allow . + * + * If /etc/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.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(char *luser) +{ + char *buf, *lbuf; + size_t len; + FILE *f; + + if ((f = fopen(allowfile, "r")) == NULL) { + if (errno == ENOENT) { + /* + * allowfile doesn't exist, this 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, "Can't open allowed users file %s (%s)", + allowfile, strerror(errno)); + return(0); + } else { + /* + * /etc/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. + */ + + lbuf = NULL; + 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 (strcmp(luser, buf) == 0 || strcmp("*", buf) == 0) + return(1); /* matched an allowed username */ + + if (lbuf != NULL) { + free(lbuf); + lbuf = NULL; + } + } + syslog(LOG_INFO, "Denied access to %s: not listed in %s", + luser, allowfile); + + /* reuse buf */ + buf = "\n\nSorry, you aren't allowed to use this facility!\n"; + fputs(buf, stdout); + } + fflush(stdout); + return(0); +} + +/* + * check_luser: + * 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(char *luserdir, char *luser) +{ + char tmp[1024]; + FILE *f; + + if (snprintf(tmp, sizeof(tmp), "%s/%s", luserdir, luser) >= + 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, "Can't 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", + luser, 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); + return(0); + } + } + } + fflush(stdout); + return(0); +} + + +/* + * changefilter: + * Add/remove filter entries for user "luser" from ip "ipsrc" + */ +static int +changefilter(int add, char *luser, char *ipsrc) +{ + char rulesfile[MAXPATHLEN], natfile[MAXPATHLEN], buf[1024]; + char template[] = "/tmp/authpfrules.XXXXXXX"; + char template2[] = "/tmp/authpfnat.XXXXXXX"; + int tmpfile = -1, from_fd = -1, ret = -1; + struct pfioc_nat pn; + struct pfioc_binat pb; + struct pfioc_rdr pd; + struct pfioc_rule pr; + struct pfctl pf; + int rcount, wcount; + FILE *fin = NULL; + char *cp; + + memset (&pf, 0, sizeof(pf)); + memset (&pr, 0, sizeof(pr)); + + syslog (LOG_DEBUG, "%s filter for ip=%s, user %s", + add ? "Adding" : "Removing", ipsrc, luser); + + /* add filter rules */ + + if (add) + Delete_Rules = 0; + else + Delete_Rules = 1; + + tmpfile = mkstemp(template); + if (tmpfile == -1) { + syslog(LOG_ERR, "Can't open temp file %s (%m)", + template); + goto error; + } + + fin = fdopen(tmpfile, "r+"); + if (fin == NULL) { + syslog(LOG_ERR, "can't open %s (%m)", template); + goto error; + } + + /* write the variable to the start of the file */ + + fprintf(fin, "user_ip = \"%s\"\n", ipsrc); + + fflush(fin); + + if ((cp = getenv("HOME")) == NULL) { + syslog(LOG_ERR, "No Home Directory!"); + goto error; + } + if (snprintf(rulesfile, sizeof rulesfile, "%s/.authpf/authpf.rules", + cp) >= sizeof rulesfile) { + syslog(LOG_ERR, "homedir path too long, exiting"); + goto error; + } + if ((from_fd = open(rulesfile, O_RDONLY, 0)) == -1) { + /* if home dir rules do not exist, we try PATH_PFRULES */ + if (errno != ENOENT) { + syslog(LOG_ERR, "can't open %s (%m)", rulesfile); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + } + snprintf(rulesfile, sizeof rulesfile, PATH_PFRULES); + if (from_fd == -1 && + (from_fd = open(rulesfile, O_RDONLY, 0)) == -1) { + syslog(LOG_ERR, "can't open %s (%m)", rulesfile); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + + while ((rcount = read(from_fd, buf, sizeof(buf))) > 0) { + wcount = write(tmpfile, buf, rcount); + if (rcount != wcount || wcount == -1) { + syslog(LOG_ERR, "rules template copy failed"); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + } + if (rcount == -1) { + syslog(LOG_ERR, "read of rules template failed"); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + + fclose(fin); + fin = NULL; + close(tmpfile); + tmpfile = -1; + close(from_fd); + from_fd = -1; + + fin = fopen(template, "r"); + if (fin == NULL) { + syslog(LOG_ERR, "can't open %s (%m)", template); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + + infile = template; + + if (unlink(template) == -1) { + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + + /* add/delete rules, using parse_rule */ + memset(&pf, 0, sizeof(pf)); + pf.dev = dev; + pf.prule = ≺ + if (parse_rules(fin, &pf) < 0) { + syslog(LOG_ERR, + "syntax error in rule file: authpf rules not loaded"); + goto error; + } + + /* now, for NAT, if we have some */ + + if ((cp = getenv("HOME")) == NULL) { + syslog(LOG_ERR, "No Home Directory!"); + goto error; + } + if (snprintf(natfile, sizeof natfile, "%s/.authpf/authpf.nat", cp) >= + sizeof natfile) { + syslog(LOG_ERR, "homedir path too long, exiting"); + goto error; + } + if ((from_fd = open(natfile, O_RDONLY, 0)) == -1) { + /* if it doesn't exist, we try /etc */ + if (errno != ENOENT) { + syslog(LOG_ERR, "can't open %s (%m)", natfile); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + } + snprintf(natfile, sizeof natfile, PATH_NATRULES); + if (from_fd == -1 && + (from_fd = open(natfile, O_RDONLY, 0)) == -1) { + if (errno == ENOENT) + goto out; /* NAT is optional */ + else { + syslog(LOG_ERR, "can't open %s (%m)", natfile); + if (unlink(template) == -1) + syslog(LOG_ERR, "can't unlink %s", template); + goto error; + } + } + tmpfile = mkstemp(template2); + if (tmpfile == -1) { + syslog(LOG_ERR, "Can't open temp file %s (%m)", + template2); + goto error; + } + + fin = fdopen(tmpfile, "r+"); + if (fin == NULL) { + syslog(LOG_ERR, "Can't open %s (%m)", template2); + goto error; + } + + /* write the variable to the start of the file */ + fprintf(fin, "user_ip = \"%s\"\n", ipsrc); + fflush(fin); + + while ((rcount = read(from_fd, buf, sizeof(buf))) > 0) { + wcount = write(tmpfile, buf, rcount); + if (rcount != wcount || wcount == -1) { + syslog(LOG_INFO, "nat copy failed"); + goto error; + } + } + + if (rcount == -1) { + syslog(LOG_INFO, "read for nat copy failed"); + goto error; + } + + fclose(fin); + fin = NULL; + close(tmpfile); + tmpfile = -1; + close(from_fd); + from_fd = -1; + + fin = fopen(template2, "r"); + + if (fin == NULL) { + syslog(LOG_INFO, "can't open %s (%m)", template2); + goto error; + } + + infile = template; + + if (unlink(template2) == -1) { + syslog(LOG_INFO, "can't unlink %s (%m)", template2); + goto error; + } + /* add/delete rules, using parse_nat */ + + memset(&pf, 0, sizeof(pf)); + pf.dev = dev; + pf.pnat = &pn; + pf.pbinat = &pb; + pf.prdr = &pd; + if (parse_nat(fin, &pf) < 0) { + syslog(LOG_INFO, + "syntax error in nat file: nat rules not loaded"); + goto error; + } + ret = 0; + goto out; + error: + ret = -1; + out: + if (fin != NULL) + fclose(fin); + if (tmpfile != -1) + close(tmpfile); + if (from_fd != -1) + close(from_fd); + if (add) { + (void)gettimeofday(&Tstart, NULL); + syslog (LOG_INFO, "Allowing %s, user %s", ipsrc, luser); + } else { + (void)gettimeofday(&Tend, NULL); + syslog (LOG_INFO, "Removed %s, user %s - duration %ld seconds", + ipsrc, luser, Tend.tv_sec - Tstart.tv_sec); + } + return(ret); +} + +/* + * authpf_kill_states: + * This is to kill off states that would otherwide 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() +{ + struct pfioc_state_kill psk; + struct in_addr target, temp; + + memset(&psk, 0, sizeof(psk)); + memset(&psk.psk_src.mask, 0xff, sizeof(psk.psk_src.mask)); + memset(&target, 0xff, sizeof(target)); + memset(&temp, 0xff, sizeof(temp)); + + inet_pton(AF_INET, "255.255.255.255", &temp); + inet_pton(AF_INET, ipsrc, &target); + + psk.psk_src.addr.v4 = target; + psk.psk_dst.addr.v4 = temp; + if (ioctl(dev, DIOCKILLSTATES, &psk)) + syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)"); + + psk.psk_dst.addr.v4 = target; + psk.psk_src.addr.v4 = temp; + if (ioctl(dev, DIOCKILLSTATES, &psk)) + syslog(LOG_ERR, "DIOCKILLSTATES failed (%m)"); +} + +/* signal handler that makes us go away properly */ +static void +terminator(int s) +{ + hasta_la_vista = 1; +} + +/* + * go_away: + * function that removes our stuff when we go away. + */ +static __dead void +go_away(void) +{ + int ret = EX_OK; + + changefilter(0, luser, ipsrc); + authpf_kill_states(); + if (unlink(pidfile) != 0) { + syslog(LOG_ERR, "Couldn't unlink %s! (%m)", pidfile); + ret = EX_OSERR; + } + if (unlink(userfile) != 0) { + syslog(LOG_ERR, "Couldn't unlink %s! (%m)", userfile); + ret = EX_OSERR; + } + exit(ret); +} + +/* + * pfctl_add_rules: + * callback for rule add, used by parser in parse_rules + */ +int +pfctl_add_rule(struct pfctl *pf, struct pf_rule *r) +{ + struct pfioc_changerule pcr; + + memset(&pcr, 0, sizeof(pcr)); + if (Delete_Rules) { + pcr.action = PF_CHANGE_REMOVE; + memcpy(&pcr.oldrule, r, sizeof(pcr.oldrule)); + } else { + pcr.action = Rule_Action; + memcpy(&pcr.newrule, r, sizeof(pcr.newrule)); + } + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCCHANGERULE, &pcr)) + syslog(LOG_INFO, "DIOCCHANGERULE %m"); + } + + return 0; +} + +/* + * pfctl_add_nat: + * callback for nat add, used by parser in parse_nat + */ +int +pfctl_add_nat(struct pfctl *pf, struct pf_nat *n) +{ + struct pfioc_changenat pcr; + + memset(&pcr, 0, sizeof(pcr)); + if (Delete_Rules) { + pcr.action = PF_CHANGE_REMOVE; + memcpy(&pcr.oldnat, n, sizeof(pcr.oldnat)); + } else { + pcr.action = Nat_Action; + memcpy(&pcr.newnat, n, sizeof(pcr.newnat)); + } + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCCHANGENAT, &pcr)) + syslog(LOG_INFO, "DIOCCHANGENAT %m"); + } + return 0; +} + +/* + * pfctl_add_rdr: + * callback for rdr add, used by parser in parse_nat + */ +int +pfctl_add_rdr(struct pfctl *pf, struct pf_rdr *r) +{ + struct pfioc_changerdr pcr; + + memset(&pcr, 0, sizeof(pcr)); + if (Delete_Rules) { + pcr.action = PF_CHANGE_REMOVE; + memcpy(&pcr.oldrdr, r, sizeof(pcr.oldrdr)); + } else { + pcr.action = Rdr_Action; + memcpy(&pcr.newrdr, r, sizeof(pcr.newrdr)); + } + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (ioctl(pf->dev, DIOCCHANGERDR, &pcr)) + syslog(LOG_INFO, "DIOCCHANGERDR %m"); + } + return 0; +} + +/* + * pfctl_add_binat: + * We don't support adding binat's, since pf doesn't, + * and I can't for the life of me think of a sane situation where it + * might be useful. This is here only because the pfctl parse + * routines need this defined. + */ +int +pfctl_add_binat(struct pfctl *pf, struct pf_binat *b) +{ + return 0; +} diff --git a/usr.sbin/authpf/pathnames.h b/usr.sbin/authpf/pathnames.h new file mode 100644 index 00000000000..719c0850f86 --- /dev/null +++ b/usr.sbin/authpf/pathnames.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2002 Chris Kuethe (ckuethe@ualberta.ca) + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the author nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. + */ + +#define PATH_CONFFILE "/etc/authpf/authpf.conf" +#define PATH_ALLOWFILE "/etc/authpf/authpf.allow" +#define PATH_PFRULES "/etc/authpf/authpf.rules" +#define PATH_NATRULES "/etc/authpf/authpf.nat" +#define PATH_PROBLEM "/etc/authpf/authpf.problem" +#define PATH_MESSAGE "/etc/authpf/authpf.message" +#define PATH_BAN_DIR "/var/authpf/banned" +#define PATH_DEVFILE "/dev/pf" +#define PATH_PIDFILE "/var/run/authpf" +#define PATH_USERFILE "/var/authpf" |