chiark / gitweb /
42630e41a00686b66e3a41a6596deea8a16a1087
[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 #include "authbind.h"
35
36 typedef void anyfn_type(void);
37 typedef int bindfn_type(int fd, const struct sockaddr *addr, socklen_t addrlen);
38
39 #define STDERRSTR_CONST(m) write(2,m,sizeof(m)-1)
40 #define STDERRSTR_STRING(m) write(2,m,strlen(m))
41
42 static anyfn_type *find_any(const char *name) {
43   static const char *dlerr;
44   anyfn_type *kv;
45
46   kv= dlsym(RTLD_NEXT,name); if (kv) return kv;
47   dlerr= dlerror(); if (!dlerr) dlerr= "dlsym() failed for no reason";
48   STDERRSTR_CONST("libauthbind: error finding original version of ");
49   STDERRSTR_STRING(name);
50   STDERRSTR_CONST(": ");
51   STDERRSTR_STRING(dlerr);
52   STDERRSTR_STRING("\n");
53   errno= ENOSYS;
54   return 0;
55 }
56
57 static bindfn_type find_bind, *old_bind= find_bind;
58
59 int find_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
60   anyfn_type *anyfn;
61   anyfn= find_any("bind"); if (!anyfn) return -1;
62   old_bind= (bindfn_type*)anyfn;
63   return old_bind(fd,addr,addrlen);
64 }
65
66 static int exiterrno(int e) {
67   _exit(e>0 && e<128 ? e : -1);
68 }
69
70 static void removepreload(void) {
71   const char *myself, *found;
72   char *newval, *preload;
73   int lpreload, lmyself, before, after;
74
75   preload= getenv(PRELOAD_VAR);
76   myself= getenv(AUTHBINDLIB_VAR);
77   if (!myself || !preload) return;
78
79   lpreload= strlen(preload);
80   lmyself= strlen(myself);
81
82   if (lmyself < 1 || lpreload<lmyself) return;
83   if (lpreload==lmyself) {
84     if (!strcmp(preload,myself)) unsetenv(PRELOAD_VAR);
85     return;
86   }
87   if (!memcmp(preload,myself,lmyself) && preload[lmyself]==':') {
88     before= 0; after= lpreload-(lmyself+1);
89   } else if (!memcmp(preload+lpreload-lmyself,myself,lmyself) &&
90              preload[lpreload-(lmyself+1)]==':') {
91     before= lpreload-(lmyself+1); after= 0;
92   } else {
93     if (lpreload<lmyself+2) return;
94     found= preload+1;
95     for (;;) {
96       found= strstr(found,myself); if (!found) return;
97       if (found > preload+lpreload-(lmyself+1)) return;
98       if (found[-1]==':' && found[lmyself]==':') break;
99       found++;
100     }
101     before= found-preload;
102     after= lpreload-(before+lmyself+1);
103   }
104   newval= malloc(before+after+1);
105   if (newval) {
106     memcpy(newval,preload,before);
107     strcpy(newval+before,preload+lpreload-after);
108     if (setenv(PRELOAD_VAR,newval,1)) return;
109     free(newval);
110   }
111   strcpy(preload+before,preload+lpreload-after);
112   return;
113 }
114
115 int _init(void);
116 int _init(void) {
117   char *levels;
118   int levelno;
119
120   /* If AUTHBIND_LEVELS is
121    *  unset => always strip from preload
122    *  set and starts with `y' => never strip from preload, keep AUTHBIND_LEVELS
123    *  set to integer > 1 => do not strip now, subtract one from AUTHBIND_LEVELS
124    *  set to integer 1 => do not strip now, unset AUTHBIND_LEVELS
125    *  set to empty string or 0 => strip now, unset AUTHBIND_LEVELS
126    */
127   levels= getenv(AUTHBIND_LEVELS_VAR);
128   if (levels) {
129     if (levels[0]=='y') return 0;
130     levelno= atoi(levels);
131     if (levelno > 0) {
132       levelno--;
133       if (levelno > 0) sprintf(levels,"%d",levelno);
134       else unsetenv(AUTHBIND_LEVELS_VAR);
135       return 0;
136     }
137     unsetenv(AUTHBIND_LEVELS_VAR);
138   }
139   removepreload();
140   return 0;
141 }
142
143 static const int evilsignals[]= { SIGFPE, SIGILL, SIGSEGV, SIGBUS, 0 };
144
145 int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
146   pid_t child, rchild;
147   char portarg[5], addrarg[33];
148   const char *afarg;
149   int i, r, status, restore_sigchild;
150   const int *evilsignal;
151   sigset_t block, saved;
152   struct sigaction old_sigchild;
153   unsigned int portval;
154
155   switch (addr->sa_family) {
156   case AF_INET:
157     portval = ((struct sockaddr_in*)addr)->sin_port;
158     if (addrlen != sizeof(struct sockaddr_in)) goto bail;
159     break;
160   case AF_INET6:
161     portval = ((struct sockaddr_in6*)addr)->sin6_port;
162     if (addrlen != sizeof(struct sockaddr_in6)) goto bail;
163     break;
164   default:
165     goto bail;
166   }
167
168   if (!geteuid() || portval == 0 || ntohs(portval) >= IPPORT_RESERVED) {
169   bail:
170     return old_bind(fd,addr,addrlen);
171   }
172
173   sigfillset(&block);
174   for (evilsignal=evilsignals;
175        *evilsignal;
176        evilsignal++)
177     sigdelset(&block,*evilsignal);
178   if (sigprocmask(SIG_BLOCK,&block,&saved)) return -1;
179
180   switch (addr->sa_family) {
181   case AF_INET:
182     afarg = 0;
183     sprintf(addrarg,"%08lx",
184             ((unsigned long)(((struct sockaddr_in*)addr)->sin_addr.s_addr))
185             &0x0ffffffffUL);
186     break;
187   case AF_INET6:
188     afarg = "6";
189     for (i=0; i<16; i++)
190       sprintf(addrarg+i*2,"%02x",
191               ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr[i]);
192     break;
193   default:
194     abort();
195   }
196   sprintf(portarg,"%04x",
197           portval&0x0ffff);
198
199   restore_sigchild= 0;
200   if (sigaction(SIGCHLD,NULL,&old_sigchild)) return -1;
201   if (old_sigchild.sa_handler == SIG_IGN) {
202     struct sigaction new_sigchild;
203
204     new_sigchild.sa_handler= SIG_DFL;
205     sigemptyset(&new_sigchild.sa_mask);
206     new_sigchild.sa_flags= 0;
207     if (sigaction(SIGCHLD,&new_sigchild,&old_sigchild)) return -1;
208     restore_sigchild= 1;
209   }
210
211   child= fork(); if (child==-1) goto x_err;
212
213   if (!child) {
214     if (dup2(fd,0)) exiterrno(errno);
215     removepreload();
216     execl(HELPER,HELPER,addrarg,portarg,afarg,(char*)0);
217     status= errno > 0 && errno < 127 ? errno : 127;
218     STDERRSTR_CONST("libauthbind: possible installation problem - "
219                     "could not invoke " HELPER "\n");
220     exiterrno(status);
221   }
222
223   rchild= waitpid(child,&status,0);
224   if (rchild==-1) goto x_err;
225   if (rchild!=child) { errno= ECHILD; goto x_err; }
226
227   if (WIFEXITED(status)) {
228     if (WEXITSTATUS(status)) {
229       errno= WEXITSTATUS(status);
230       if (errno >= 127) errno= ENXIO;
231       goto x_err;
232     }
233     r= 0;
234     goto x;
235   } else {
236     errno= ENOSYS;
237     goto x_err;
238   }
239
240 x_err:
241   r= -1;
242 x:
243   if (sigprocmask(SIG_SETMASK,&saved,0)) abort();
244   if (restore_sigchild) {
245     if (sigaction(SIGCHLD,&old_sigchild,NULL)) return -1;
246     if (old_sigchild.sa_handler == SIG_IGN) {
247       int discard;
248       while (waitpid(-1, &discard, WNOHANG) > 0)
249         ;
250     }
251   }
252   return r;
253 }