chiark / gitweb /
extend the example
[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;
150   const int *evilsignal;
151   sigset_t block, saved;
152   unsigned int portval;
153
154   switch (addr->sa_family) {
155   case AF_INET:
156     portval = ((struct sockaddr_in*)addr)->sin_port;
157     if (addrlen != sizeof(struct sockaddr_in)) goto bail;
158     break;
159   case AF_INET6:
160     portval = ((struct sockaddr_in6*)addr)->sin6_port;
161     if (addrlen != sizeof(struct sockaddr_in6)) goto bail;
162     break;
163   default:
164     goto bail;
165   }
166
167   if (!geteuid() || portval == 0 || ntohs(portval) >= IPPORT_RESERVED) {
168   bail:
169     return old_bind(fd,addr,addrlen);
170   }
171
172   sigfillset(&block);
173   for (evilsignal=evilsignals;
174        *evilsignal;
175        evilsignal++)
176     sigdelset(&block,*evilsignal);
177   if (sigprocmask(SIG_BLOCK,&block,&saved)) return -1;
178
179   switch (addr->sa_family) {
180   case AF_INET:
181     afarg = 0;
182     sprintf(addrarg,"%08lx",
183             ((unsigned long)(((struct sockaddr_in*)addr)->sin_addr.s_addr))
184             &0x0ffffffffUL);
185     break;
186   case AF_INET6:
187     afarg = "6";
188     for (i=0; i<16; i++)
189       sprintf(addrarg+i*2,"%02x",
190               ((struct sockaddr_in6*)addr)->sin6_addr.s6_addr[i]);
191     break;
192   default:
193     abort();
194   }
195   sprintf(portarg,"%04x",
196           portval&0x0ffff);
197
198   child= fork(); if (child==-1) goto x_err;
199
200   if (!child) {
201     if (dup2(fd,0)) exiterrno(errno);
202     removepreload();
203     execl(HELPER,HELPER,addrarg,portarg,afarg,(char*)0);
204     status= errno > 0 && errno < 127 ? errno : 127;
205     STDERRSTR_CONST("libauthbind: possible installation problem - "
206                     "could not invoke " HELPER "\n");
207     exiterrno(status);
208   }
209
210   rchild= waitpid(child,&status,0);
211   if (rchild==-1) goto x_err;
212   if (rchild!=child) { errno= ECHILD; goto x_err; }
213
214   if (WIFEXITED(status)) {
215     if (WEXITSTATUS(status)) {
216       errno= WEXITSTATUS(status);
217       if (errno >= 127) errno= ENXIO;
218       goto x_err;
219     }
220     r= 0;
221     goto x;
222   } else {
223     errno= ENOSYS;
224     goto x_err;
225   }
226
227 x_err:
228   r= -1;
229 x:
230   if (sigprocmask(SIG_SETMASK,&saved,0)) abort();
231   return r;
232 }