chiark / gitweb /
ipv6 etc.
[authbind.git] / libauthbind.c
1 /*
2  *  libauthbind.c - bind(2)-redirector library for authbind
3  *
4  *  authbind is Copyright (C) 1998 Ian Jackson
5  * 
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  * 
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  * 
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software Foundation,
18  *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
19  * 
20  */
21
22 #include <dlfcn.h>
23 #include <stdio.h>
24 #include <sys/types.h>
25 #include <unistd.h>
26 #include <errno.h>
27 #include <stdlib.h>
28 #include <signal.h>
29 #include <string.h>
30 #include <sys/socket.h>
31 #include <sys/wait.h>
32 #include <netinet/in.h>
33
34 static const char *rcsid="$Id$";
35
36 #include "authbind.h"
37
38 typedef void anyfn_type(void);
39 typedef int bindfn_type(int fd, const struct sockaddr *addr, socklen_t addrlen);
40
41 #define STDERRSTR_CONST(m) write(2,m,sizeof(m)-1)
42 #define STDERRSTR_STRING(m) write(2,m,strlen(m))
43
44 static anyfn_type *find_any(const char *name) {
45   static const char *dlerr;
46   anyfn_type *kv;
47
48   kv= dlsym(RTLD_NEXT,name); if (kv) return kv;
49   dlerr= dlerror(); if (!dlerr) dlerr= "dlsym() failed for no reason";
50   STDERRSTR_CONST("libauthbind: error finding original version of ");
51   STDERRSTR_STRING(name);
52   STDERRSTR_CONST(": ");
53   STDERRSTR_STRING(dlerr);
54   STDERRSTR_STRING("\n");
55   errno= ENOSYS;
56   return 0;
57 }
58
59 static bindfn_type find_bind, *old_bind= find_bind;
60
61 int find_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
62   anyfn_type *anyfn;
63   anyfn= find_any("bind"); if (!anyfn) return -1;
64   old_bind= (bindfn_type*)anyfn;
65   return old_bind(fd,addr,addrlen);
66 }
67
68 static int exiterrno(int e) {
69   _exit(e>0 && e<128 ? e : -1);
70 }
71
72 static void removepreload(void) {
73   const char *myself, *found;
74   char *newval, *preload;
75   int lpreload, lmyself, before, after;
76
77   preload= getenv(PRELOAD_VAR);
78   myself= getenv(AUTHBINDLIB_VAR);
79   if (!myself || !preload) return;
80
81   lpreload= strlen(preload);
82   lmyself= strlen(myself);
83
84   if (lmyself < 1 || lpreload<lmyself) return;
85   if (lpreload==lmyself) {
86     if (!strcmp(preload,myself)) unsetenv(PRELOAD_VAR);
87     return;
88   }
89   if (!memcmp(preload,myself,lmyself) && preload[lmyself]==':') {
90     before= 0; after= lpreload-(lmyself+1);
91   } else if (!memcmp(preload+lpreload-lmyself,myself,lmyself) &&
92              preload[lpreload-(lmyself+1)]==':') {
93     before= lpreload-(lmyself+1); after= 0;
94   } else {
95     if (lpreload<lmyself+2) return;
96     found= preload+1;
97     for (;;) {
98       found= strstr(found,myself); if (!found) return;
99       if (found > preload+lpreload-(lmyself+1)) return;
100       if (found[-1]==':' && found[lmyself]==':') break;
101       found++;
102     }
103     before= found-preload;
104     after= lpreload-(before+lmyself+1);
105   }
106   newval= malloc(before+after+1);
107   if (newval) {
108     memcpy(newval,preload,before);
109     strcpy(newval+before,preload+lpreload-after);
110     if (setenv(PRELOAD_VAR,newval,1)) return;
111     free(newval);
112   }
113   strcpy(preload+before,preload+lpreload-after);
114   return;
115 }
116
117 int _init(void);
118 int _init(void) {
119   char *levels;
120   int levelno;
121
122   /* If AUTHBIND_LEVELS is
123    *  unset => always strip from preload
124    *  set and starts with `y' => never strip from preload, keep AUTHBIND_LEVELS
125    *  set to integer > 1 => do not strip now, subtract one from AUTHBIND_LEVELS
126    *  set to integer 1 => do not strip now, unset AUTHBIND_LEVELS
127    *  set to empty string or 0 => strip now, unset AUTHBIND_LEVELS
128    */
129   levels= getenv(AUTHBIND_LEVELS_VAR);
130   if (levels) {
131     if (levels[0]=='y') return 0;
132     levelno= atoi(levels);
133     if (levelno > 0) {
134       levelno--;
135       if (levelno > 0) sprintf(levels,"%d",levelno);
136       else unsetenv(AUTHBIND_LEVELS_VAR);
137       return 0;
138     }
139     unsetenv(AUTHBIND_LEVELS_VAR);
140   }
141   removepreload();
142   return 0;
143 }
144
145 static const int evilsignals[]= { SIGFPE, SIGILL, SIGSEGV, SIGBUS, 0 };
146
147 int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
148   pid_t child, rchild;
149   char portarg[5], addrarg[33];
150   const char *afarg;
151   int i, r, status;
152   const int *evilsignal;
153   sigset_t block, saved;
154   unsigned int portval;
155
156   switch (addr->sa_family) {
157   case AF_INET:
158     portval = ((struct sockaddr_in*)addr)->sin_port;
159     if (addrlen != sizeof(struct sockaddr_in)) goto bail;
160     break;
161   case AF_INET6:
162     portval = ((struct sockaddr_in6*)addr)->sin6_port;
163     if (addrlen != sizeof(struct sockaddr_in6)) goto bail;
164     break;
165   default:
166     goto bail;
167   }
168
169   if (!geteuid() || portval == 0 || portval >= IPPORT_RESERVED) {
170   bail:
171     return old_bind(fd,addr,addrlen);
172   }
173
174   sigfillset(&block);
175   for (evilsignal=evilsignals;
176        *evilsignal;
177        evilsignal++)
178     sigdelset(&block,*evilsignal);
179   if (sigprocmask(SIG_BLOCK,&block,&saved)) return -1;
180
181   switch (addr->sa_family) {
182   case AF_INET:
183     afarg = 0;
184     sprintf(addrarg,"%08lx",
185             ((unsigned long)(((struct sockaddr_in*)addr)->sin_addr.s_addr))
186             &0x0ffffffffUL);
187     break;
188   case AF_INET6:
189     afarg = "6";
190     for (i=0; i<16; i++)
191       sprintf(addrarg+i*2,"%02x",
192               ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr[i]);
193     break;
194   default:
195     abort();
196   }
197   sprintf(portarg,"%04x",
198           portval&0x0ffff);
199
200   child= fork(); if (child==-1) goto x_err;
201
202   if (!child) {
203     if (dup2(fd,0)) exiterrno(errno);
204     removepreload();
205     execl(HELPER,HELPER,addrarg,portarg,afarg,(char*)0);
206     status= errno > 0 && errno < 127 ? errno : 127;
207     STDERRSTR_CONST("libauthbind: possible installation problem - "
208                     "could not invoke " HELPER " (");
209     STDERRSTR_STRING(rcsid);
210     STDERRSTR_CONST(")\n");
211     exiterrno(status);
212   }
213
214   rchild= waitpid(child,&status,0);
215   if (rchild==-1) goto x_err;
216   if (rchild!=child) { errno= ECHILD; goto x_err; }
217
218   if (WIFEXITED(status)) {
219     if (WEXITSTATUS(status)) {
220       errno= WEXITSTATUS(status);
221       if (errno >= 127) errno= ENXIO;
222       goto x_err;
223     }
224     r= 0;
225     goto x;
226   } else {
227     errno= ENOSYS;
228     goto x_err;
229   }
230
231 x_err:
232   r= -1;
233 x:
234   if (sigprocmask(SIG_SETMASK,&saved,0)) abort();
235   return r;
236 }