home *** CD-ROM | disk | FTP | other *** search
- /*
- authtcp.c: Create a locally authenticated TCP connection.
- */
-
- /* WARNING! WARNING! WARNING! */
- /* For the authentication to work, authtcp must run setuid auth! */
- /* All setuid programs are dangerous! Check them carefully! */
-
- static char authtcpauthor[] =
- "authtcp was written by Daniel J. Bernstein.\n\
- Internet address: brnstnd@acf10.nyu.edu.\n";
-
- static char authtcpversion[] =
- "authtcp version 2.1, April 18, 1990.\n\
- Copyright (c) 1990, Daniel J. Bernstein.\n\
- All rights reserved.\n";
-
- static char authtcpcopyright[] =
- "authtcp version 2.1, April 18, 1990.\n\
- Copyright (c) 1990, Daniel J. Bernstein.\n\
- All rights reserved.\n\
- \n\
- Until January 1, 1995, you are granted the following rights: A. To make\n\
- copies of this work in original form, so long as (1) the copies are exact\n\
- and complete; (2) the copies include the copyright notice, this paragraph,\n\
- and the disclaimer of warranty in their entirety. B. To distribute this\n\
- work, or copies made under the provisions above, so long as (1) this is\n\
- the original work and not a derivative form; (2) you do not charge a fee\n\
- for copying or for distribution; (3) you ensure that the distributed form\n\
- includes the copyright notice, this paragraph, and the disclaimer of\n\
- warranty in their entirety. These rights are temporary and revocable upon\n\
- written, oral, or other notice by Daniel J. Bernstein. These rights are\n\
- automatically revoked on January 1, 1995. This copyright notice shall be\n\
- governed by the laws of the state of New York.\n\
- \n\
- If you have questions about authtcp or about this copyright notice,\n\
- or if you would like additional rights beyond those granted above,\n\
- please feel free to contact the author at brnstnd@acf10.nyu.edu\n\
- on the Internet.\n";
-
- static char authtcpwarranty[] =
- "To the extent permitted by applicable law, Daniel J. Bernstein disclaims\n\
- all warranties, explicit or implied, including but not limited to the\n\
- implied warranties of merchantability and fitness for a particular purpose.\n\
- Daniel J. Bernstein is not and shall not be liable for any damages,\n\
- incidental or consequential, arising from the use of this program, even\n\
- if you inform him of the possibility of such damages. This disclaimer\n\
- shall be governed by the laws of the state of New York.\n\
- \n\
- In other words, use this program at your own risk.\n\
- \n\
- If you have questions about authtcp or about this disclaimer of warranty,\n\
- please feel free to contact the author at brnstnd@acf10.nyu.edu\n\
- on the Internet.\n";
-
- static char authtcpusage[] =
- "Usage: authtcp [ -dn ] [ -pport ] [ -xXvACHUVW ] inetaddr tcpport program \n\
- Help: authtcp -H\n";
-
- static char authtcphelp[] =
- "authtcp creates a locally authenticated TCP connection to an Internet host.\n\
- \n\
- authtcp -A: print authorship notice\n\
- authtcp -C: print copyright notice\n\
- authtcp -H: print this notice\n\
- authtcp -U: print short usage summary\n\
- authtcp -V: print version number\n\
- authtcp -W: print disclaimer of warranty\n\
- \n\
- authtcp [ -dn ] [ -pport ] [ -vxXrR ] inetaddr tcpport program:\n\
- connect to tcpport at inetaddr and run program\n\
- -dn: pass the connection to the program in file descriptor n (default 6)\n\
- -pport: attempt to use a particular local port\n\
- -v: verbose: proclaim success; report unusual program termination\n\
- -x: authenticate by telling authd(8) about the connection (default)\n\
- -X: do not locally authenticate\n\
- -r: attempt to authenticate the remote side as well (default), placing\n\
- user@host and TCP into environment variables REMOTE and PROTO\n\
- -R: do not remotely authenticate\n\
- \n\
- If you have questions about or suggestions for authtcp, please feel free\n\
- to contact the author, Daniel J. Bernstein, at brnstnd@acf10.nyu.edu\n\
- on the Internet.\n";
-
- #include <stdio.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <netdb.h>
- #include <errno.h>
- extern int errno;
- extern char *malloc(); /* many systems don't have malloc.h */
- #include <pwd.h>
- #include <sys/file.h>
- #ifdef BSD
- #include <limits.h>
- #endif
- #include <sys/wait.h>
- #include <sys/time.h>
- #include <sys/resource.h>
- #include <signal.h>
- #include <ctype.h>
- extern int getopt();
- extern char *optarg; /* these should be in getopt.h! */
- extern int optind;
- #include "authuser.h"
- #include "djberr.h"
- #include "djbatoi.h"
-
- #ifndef AUTHDIR
- #define AUTHDIR "/usr/etc/auth"
- #endif
-
- #ifndef MAXHOSTNAMELEN
- #define MAXHOSTNAMELEN 128 /* stupid Suns don't define this in sys/param.h */
- #endif
-
- int flagcont = 0;
-
- sigcont()
- {
- flagcont = 1;
- }
-
- int flagauth = 1;
- int flagremote = 1;
- int filedesc = 6;
- unsigned short localport = 0;
-
- char *strinetaddr = NULL;
- char *strtcpport = NULL;
- char **program = NULL;
-
- main(argc,argv,envp)
- int argc;
- char *argv[];
- char *envp[];
- {
- extern char **environ;
- int opt;
- struct sockaddr_in sa;
- unsigned long in;
- unsigned short remoteport;
- int s;
- int t;
- int uid;
- int euid;
- int dummy;
- char authfn[sizeof(AUTHDIR) + 30];
- int authfd;
- char lockfn[sizeof(AUTHDIR) + 30];
- int lockfd;
- char lockbuf[32]; /* 5 pid, 1 :, 10 I, 1 ., 5 R, 1 \n, 8 U, 1\0 */
- struct passwd *pw;
- char strfd[20];
- int flagverbose = 0;
- struct hostent *he;
- struct servent *se;
- union wait status;
- int f;
- struct in_addr inet; /* dummy for inet_ntoa() */
- int flagsigintign;
- int flagsigquitign;
- int flagsigtstpign;
- int flagsighupign;
- int flagsigalrmign;
- int flagsigxcpuign;
- int flagsigxfszign;
- int flagsigvtalrmign;
- int flagsigprofign;
-
- /* ALERT! ALERT! ALERT! We're probably running setuid auth! */
- /* Note that accounting is by real uid, not effective uid. */
- /* The system should be careful about setuid core dumps 'n' such. */
-
- uid = getuid();
- euid = geteuid();
-
- /* The following are necessary to be absolutely sure of removing the
- authentication entry. It's a flaw of the signal handling system that
- every new extension could turn a secure program like this into an
- (ever so slightly) insecure one. */
- flagsigintign = (signal(SIGINT,SIG_IGN) == SIG_IGN);
- flagsigquitign = (signal(SIGQUIT,SIG_IGN) == SIG_IGN);
- flagsigtstpign = (signal(SIGTSTP,SIG_IGN) == SIG_IGN);
- flagsighupign = (signal(SIGHUP,SIG_IGN) == SIG_IGN);
- flagsigalrmign = (signal(SIGALRM,SIG_IGN) == SIG_IGN);
- flagsigxcpuign = (signal(SIGXCPU,SIG_IGN) == SIG_IGN);
- flagsigxfszign = (signal(SIGXFSZ,SIG_IGN) == SIG_IGN);
- flagsigvtalrmign = (signal(SIGVTALRM,SIG_IGN) == SIG_IGN);
- flagsigprofign = (signal(SIGPROF,SIG_IGN) == SIG_IGN);
- /* At least we can depend on SIG_IGN and SIG_DFL being the only
- possible handlers passed through an exec. Programmers should note
- the above trick to avoid having to worry about the signal() type. */
-
- while ((opt = getopt(argc,argv,"rRxXd:p:vACHUVW")) != EOF)
- switch(opt)
- {
- case 'r': flagremote = 1; break;
- case 'R': flagremote = 0; break;
- case 'x': flagauth = 1; break;
- case 'X': flagauth = 0; break;
- case 'd': filedesc = atoi(optarg); break;
- case 'p': localport = atoi(optarg); break;
- case 'v': flagverbose = 1; break;
- case 'A': (void) err(authtcpauthor); (void) setreuid(uid,uid); exit(1);
- case 'C': (void) err(authtcpcopyright); (void) setreuid(uid,uid); exit(1);
- case 'H': (void) err(authtcphelp); (void) setreuid(uid,uid); exit(1);
- case 'U': (void) err(authtcpusage); (void) setreuid(uid,uid); exit(1);
- case 'V': (void) err(authtcpversion); (void) setreuid(uid,uid); exit(1);
- case 'W': (void) err(authtcpwarranty); (void) setreuid(uid,uid); exit(1);
- case '?': (void) err(authtcpusage); (void) setreuid(uid,uid); exit(1);
- }
- argv += optind, argc -= optind;
- while (*argv)
- {
- if (strinetaddr == NULL)
- strinetaddr = *argv;
- else if (strtcpport == NULL)
- strtcpport = *argv;
- else
- {
- program = argv;
- break;
- }
- argv++;
- }
-
- if ((program == NULL) || (*program == NULL))
- {
- err(authtcpusage); (void) setreuid(uid,uid); exit(1);
- /* what else can you say? */
- }
-
- t = strlen(strinetaddr) - 1;
- if (isascii(strinetaddr[t]) && isdigit(strinetaddr[t]))
- in = inet_addr(strinetaddr);
- else
- if ((he = gethostbyname(strinetaddr)) == NULL)
- in = (unsigned long) -1;
- else
- in = *((unsigned long *) (he->h_addr));
-
- if (in == (unsigned long) -1)
- {
- errn2("authtcp: fatal: do not understand inetaddr %s",strinetaddr);
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- t = strlen(strtcpport) - 1;
- if (isascii(strtcpport[t]) && isdigit(strtcpport[t]))
- remoteport = atoi(strtcpport); /* so who cares if it's zero? */
- else
- if ((se = getservbyname(strtcpport,"tcp")) == NULL)
- remoteport = 0;
- else
- remoteport = ntohs(se->s_port); /* inconsistency alert! */
- /* se->s_port is int! */
-
- /* We will depend on the type of localport and remoteport, namely */
- /* unsigned short, to keep them between 0 and 65535. */
-
- /* The parent program must remain active and setuid auth, to wait for */
- /* the child to finish and to remove its authentication entry. But the */
- /* TCP connection must be made as the user---don't want fake rlogins! */
-
- /* We now switch to the real user ID. */
-
- if (setreuid(euid,uid))
- {
- perrn2("%s","authtcp: fatal: cannot setreuid");
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- if ((s = socket(AF_INET,SOCK_STREAM,0)) == -1) /* no security problem */
- {
- perrn2("%s","authtcp: fatal: cannot create socket");
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- /* The bind() below used to be if (localport) only. However, connect() */
- /* can take arbitrarily long, there's no solution like select-accept, */
- /* and authd should not have to wait to get its information. So we have */
- /* to find our local port now. This is documented to work in PS1:8-31. */
-
- sa.sin_family = AF_INET;
- sa.sin_port = htons(localport); /* ever seen a client do this before? */
- sa.sin_addr.s_addr = INADDR_ANY; /* or this? */
-
- if (bind(s,&sa,sizeof(sa)) == -1)
- if (localport)
- {
- perrn2("authtcp: fatal: cannot bind local port %u",
- (unsigned int) localport);
- (void) setreuid(uid,uid);
- exit(1);
- }
- else
- {
- perrn2("%s","authtcp: fatal: cannot bind local port");
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- /* We now switch back to auth... */
- if (setreuid(uid,euid))
- {
- perrn2("%s","authtcp: fatal: cannot setreuid");
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- if (flagauth)
- {
- dummy = sizeof(sa);
- if ((pw = getpwuid(uid)) == NULL)
- {
- errn("authtcp: fatal: cannot authenticate: who are you?");
- (void) setreuid(uid,uid);
- exit(1);
- }
- if (getsockname(s,&sa,&dummy) == -1)
- {
- perrn2("%s","authtcp: fatal: cannot get socket name");
- (void) setreuid(uid,uid);
- exit(1);
- }
- (void) sprintf(authfn,"%s/tcp/%D.%u.%u",AUTHDIR,in,
- (unsigned int) ntohs(sa.sin_port),(unsigned int) remoteport);
- (void) sprintf(lockfn,"%s/tcp/lock.%u",AUTHDIR,
- (unsigned int) ntohs(sa.sin_port));
- if (((lockfd = open(lockfn,O_WRONLY | O_CREAT | O_EXCL,0600)) == -1)
- &&(((lockfd = open(lockfn,O_RDONLY)) == -1)
- ||(flock(lockfd,LOCK_EX) == -1)
- ||(read(lockfd,lockbuf,31) <= 0)
- ||((lockbuf[0] != '!')
- &&((atoi(lockbuf) <= 0)
- ||(kill(atoi(lockbuf),0) == 0))) /* okay, screw the last process */
- ||(close(lockfd) == -1) /* impossible */
- ||((lockfd = open(lockfn,O_WRONLY | O_CREAT | O_TRUNC,0600)) == -1)))
- { /* yikes, that was incomprehensible */
- errn2("authtcp: fatal: local port %u locked",
- (unsigned int) ntohs(sa.sin_port));
- (void) setreuid(uid,uid);
- exit(1);
- }
- (void) flock(lockfd,LOCK_EX);
- (void) sprintf(lockbuf,"%d:%D.%u\n%s",getpid(),in,(unsigned int) remoteport,
- pw->pw_name);
- (void) write(lockfd,lockbuf,strlen(lockbuf));
- (void) flock(lockfd,LOCK_UN);
- }
- /* It isn't a disaster if the user kills us now; it's just a possible */
- /* denial of service for later auths on this port. The amount of effort */
- /* necessary to guarantee that denial is huge. */
-
- /* We now switch back to real... */
- if (setreuid(euid,uid))
- {
- perrn2("%s","authtcp: fatal: cannot setreuid");
- (void) setreuid(uid,euid);
- if (flagauth)
- (void) unlink(lockfn);
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- sa.sin_family = AF_INET;
- sa.sin_port = htons(remoteport);
- sa.sin_addr.s_addr = in;
-
- if (connect(s,&sa,sizeof(sa)) == -1)
- {
- perrn2("authtcp: fatal: cannot connect to %s",strinetaddr);
- (void) setreuid(uid,euid);
- if (flagauth)
- (void) unlink(lockfn);
- (void) setreuid(uid,uid);
- exit(1);
- }
-
- if (flagverbose)
- {
- inet.s_addr = in;
- fprintf(stderr,"authtcp: connected to %s port %u\n",
- inet_ntoa(inet),(unsigned int) remoteport);
- }
-
- /* We now have the uids switched, the connection open in socket s. */
-
- /* We're about to write the authentication entry. We must remove it */
- /* before coming back to the real uid and/or exiting. On the other */
- /* hand, we must fork as the real uid: one system stupidity is that */
- /* fork() uses the effective uid for MAXUPRC checks. Hence we fork */
- /* as the real (i.e. switched) uid, before authentication. In fact, */
- /* we delay authentication until it's convenient in the parent; the */
- /* child program could run before the authentication file exists. */
- /* This is not a race condition, for reasons explained in dir.doc. */
-
- if ((f = fork()) == 0) /* child */
- {
- if (setreuid(uid,uid))
- {
- perrn2("%s","authtcp: fatal: cannot setreuid");
- exit(1);
- }
- if (filedesc)
- {
- if (dup2(s,filedesc) == -1)
- {
- perrn2("%s","authtcp: fatal: cannot use file descriptor");
- exit(1);
- }
- }
- else
- {
- (void) sprintf(strfd,"%d",s);
- while (*(++argv))
- if ((**argv == '=') && (*(*argv + 1) == '\0'))
- {
- *argv = strfd;
- break;
- }
- /* don't need to complain about failure here */
- }
-
- if (flagremote)
- {
- unsigned long in; /* keep confirming variables separate */
- unsigned short local;
- unsigned short remote;
- char *ruser;
- char *srem;
- char **t;
- char **trem;
- char **tproto;
- char **envbak;
-
- if (auth_fd(s,&in,&local,&remote) == -1)
- {
- perrn2("%s","authtcp: fatal: cannot confirm connection");
- exit(1);
- }
- if ((ruser = auth_tcpuser(in,local,remote)) == NULL)
- ruser = ""; /* bummer */
- if ((srem = malloc(strlen(ruser) + 30)) == NULL)
- {
- perrn2("%s","authtcp: fatal: cannot allocate environment");
- exit(1);
- }
- inet.s_addr = in;
- sprintf(srem,"REMOTE=%s@%s",ruser,inet_ntoa(inet));
- for (trem = envp;*trem;trem++)
- if (strncmp(*trem,"REMOTE=",7) == 0)
- break;
- for (tproto = envp;*tproto;tproto++)
- if (strncmp(*tproto,"PROTO=",6) == 0)
- break;
- if (!(*trem && *tproto))
- {
- envbak = envp;
- if ((environ = (char **) malloc((trem - envp + 3) * sizeof(char *)))
- == NULL)
- {
- perrn2("%s","authtcp: fatal: cannot allocate environment");
- exit(1);
- }
- for (t = envbak;*t;t++)
- environ[t - envbak] = *t; /* not worth a bcopy */
- trem = environ + ((*trem ? trem : t++) - envbak);
- tproto = environ + ((*tproto ? tproto : t++) - envbak);
- environ[t - envbak] = NULL;
- }
- *trem = srem;
- *tproto = "PROTO=TCP";
- /* XXXXXX: Should we do confirming sanity checks here? */
- }
-
- (void) signal(SIGINT,flagsigintign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGQUIT,flagsigquitign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGTSTP,flagsigtstpign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGHUP,flagsighupign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGALRM,flagsigalrmign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGXCPU,flagsigxcpuign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGXFSZ,flagsigxfszign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGVTALRM,flagsigvtalrmign ? SIG_IGN : SIG_DFL);
- (void) signal(SIGPROF,flagsigprofign ? SIG_IGN : SIG_DFL);
-
- /* Yes, Virginia, this is portable. Read execvp(3). */
- /* Annoying that there isn't a better interface, though. */
- (void) execvp(*program,program); /* must use environ! */
- perrn2("authtcp: fatal: cannot execute %s",*program);
- exit(1);
- }
- /* no need to test for failing fork */
-
- if (setreuid(uid,euid))
- {
- perrn2("%s","authtcp: fatal: cannot setreuid");
- (void) setreuid(uid,euid);
- if (flagauth)
- (void) unlink(lockfn);
- (void) setreuid(uid,uid);
- exit(1);
- }
- /* Now we're back to setuid auth in the parent, as we will remain. */
-
- if (flagauth)
- {
- /* authfn and pw are put together up above */
- if ((authfd = open(authfn,O_WRONLY | O_CREAT | O_EXCL,0600)) == -1)
- {
- perrn2("%s","authtcp: fatal: cannot authenticate");
- (void) setreuid(uid,euid);
- if (flagauth)
- (void) unlink(lockfn);
- (void) setreuid(uid,uid);
- exit(1);
- }
- (void) write(authfd,pw->pw_name,strlen(pw->pw_name));
- (void) close(authfd); /* if it fails, tough luck. */
- }
-
- (void) signal(SIGCONT,sigcont);
-
- /* Unless the user is auth we won't receive any signals. So the */
- /* following handlers are mainly a convenience on machines where */
- /* authtcp is not installed by the sysadmin. */
- (void) signal(SIGTERM,SIG_IGN); /* kill child, not us! */
- (void) signal(SIGTTOU,SIG_DFL); /* for stopping */
- (void) signal(SIGTTIN,SIG_DFL); /* for stopping */
-
- /* We're going to leave the socket open, to completely close a possible */
- /* security hole. The application must not depend upon the connection */
- /* disappearing when it's closed. */
-
- for (t = getdtablesize();t >= 0;t--)
- if ((s != t) && (t != 2)) /* have to leave stderr open! */
- (void) close(t); /* might as well do a bit of dissociation */
-
- while (wait3(&status,WUNTRACED,(struct rusage *) NULL) >= 0)
- {
- if (status.w_stopval == WSTOPPED) /* if child stops, we stop */
- {
- flagcont = 0;
- (void) signal(SIGTSTP,SIG_DFL); /* for stopping */
- switch(status.w_stopsig)
- { /* this just looks good */
- case SIGSTOP: (void) kill(getpid(),SIGSTOP); break;
- case SIGTSTP: (void) kill(getpid(),SIGTSTP); break;
- case SIGTTOU: (void) kill(getpid(),SIGTTOU); break;
- case SIGTTIN: (void) kill(getpid(),SIGTTIN); break;
- default: (void) kill(getpid(),SIGSTOP); break;
- }
- while (flagcont == 0)
- ; /* rack up CPU time waiting for the CONT---is there a better way? */
- (void) signal(SIGTSTP,SIG_IGN);
- (void) kill(f,SIGCONT); /* when we start, child starts */
- continue;
- }
- if (flagverbose)
- if (WIFSIGNALED(status))
- if (status.w_coredump)
- {
- errn2("authtcp: fatal: killed by signal %d (core dumped)",
- status.w_termsig);
- }
- else
- {
- errn2("authtcp: fatal: killed by signal %d",status.w_termsig);
- }
- break;
- }
-
- if (flagauth)
- {
- if (unlink(authfn) == -1) /* had better succeed! */
- {
- perrn2("authtcp: fatal: cannot unlink authentication entry %s",authfn);
- exit(1);
- }
- (void) unlink(lockfn);
- }
-
- exit(WIFEXITED(status) ? ((int) status.w_retcode) : 1);
- }
-