Convert setuid/setgid users to xsetuid/xsetgid.
[platform/upstream/busybox.git] / networking / fakeidentd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * A fake identd server
4  *
5  * Adapted to busybox by Thomas Lundquist <thomasez@zelow.no>
6  * Original Author: Tomi Ollila <too@iki.fi>
7  *                  http://www.guru-group.fi/~too/sw/
8  *
9  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
10  */
11
12 #include "busybox.h"
13
14 #include <unistd.h>
15 #include <string.h>
16 #include <fcntl.h>
17 #include <signal.h>
18 #include <sys/syslog.h>
19
20 #include <pwd.h>
21
22 #include <sys/syslog.h>
23 #include <time.h>
24 #include <sys/socket.h>
25 #include <errno.h>
26 #include <sys/uio.h>
27
28
29 #define IDENT_PORT  113
30 #define MAXCONNS    20
31 #define MAXIDLETIME 45
32
33 static const char ident_substr[] = " : USERID : UNIX : ";
34 enum { ident_substr_len = sizeof(ident_substr) - 1 };
35 #define PIDFILE "/var/run/identd.pid"
36
37 /*
38  * We have to track the 'first connection socket' so that we
39  * don't go around closing file descriptors for non-clients.
40  *
41  * descriptor setup normally
42  *  0 = server socket
43  *  1 = syslog fd (hopefully -- otherwise this won't work)
44  *  2 = connection socket after detached from tty. standard error before that
45  *  3 - 2 + MAXCONNS = rest connection sockets
46  *
47  * To try to make sure that syslog fd is what is "requested", the that fd
48  * is closed before openlog() call.  It can only severely fail if fd 0
49  * is initially closed.
50  */
51 #define FCS 2
52
53 /*
54  * FD of the connection is always the index of the connection structure
55  * in `conns' array + FCS
56  */
57 static struct {
58         char buf[20];
59         int len;
60         time_t lasttime;
61 } conns[MAXCONNS];
62
63 /* When using global variables, bind those at least to a structure. */
64 static struct {
65         const char *identuser;
66         fd_set readfds;
67         int conncnt;
68 } G;
69
70 /*
71  * Prototypes
72  */
73 static void reply(int s, char *buf);
74 static void replyError(int s, char *buf);
75
76 static const char *nobodystr = "nobody"; /* this needs to be declared like this */
77 static char *bind_ip_address = "0.0.0.0";
78
79 static inline void movefd(int from, int to)
80 {
81         if (from != to) {
82                 dup2(from, to);
83                 close(from);
84         }
85 }
86
87 static void inetbind(void)
88 {
89         int s, port;
90         struct sockaddr_in addr;
91         int len = sizeof(addr);
92         int one = 1;
93         struct servent *se;
94
95         if ((se = getservbyname("identd", "tcp")) == NULL)
96                 port = IDENT_PORT;
97         else
98                 port = se->s_port;
99
100         s = bb_xsocket(AF_INET, SOCK_STREAM, 0);
101
102         setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
103
104         memset(&addr, 0, sizeof(addr));
105         addr.sin_addr.s_addr = inet_addr(bind_ip_address);
106         addr.sin_family = AF_INET;
107         addr.sin_port = htons(port);
108
109         bb_xbind(s, (struct sockaddr *)&addr, len);
110         bb_xlisten(s, 5);
111
112         movefd(s, 0);
113 }
114
115 static void handlexitsigs(int signum)
116 {
117         if (unlink(PIDFILE) < 0)
118                 close(open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0644));
119         exit(0);
120 }
121
122 /* May succeed. If not, won't care. */
123 static inline void writepid(uid_t nobody, uid_t nogrp)
124 {
125         char buf[24];
126         int fd = open(PIDFILE, O_WRONLY|O_CREAT|O_TRUNC, 0664);
127
128         if (fd < 0)
129                 return;
130
131         snprintf(buf, 23, "%d\n", getpid());
132         write(fd, buf, strlen(buf));
133         fchown(fd, nobody, nogrp);
134         close(fd);
135
136         /* should this handle ILL, ... (see signal(7)) */
137         signal(SIGTERM, handlexitsigs);
138         signal(SIGINT,  handlexitsigs);
139         signal(SIGQUIT, handlexitsigs);
140 }
141
142 /* return 0 as parent, 1 as child */
143 static int godaemon(void)
144 {
145         uid_t nobody, nogrp;
146         struct passwd *pw;
147
148         switch (fork()) {
149         case -1:
150                 bb_perror_msg_and_die("Could not fork");
151
152         case 0:
153                 pw = getpwnam(nobodystr);
154                 if (pw == NULL)
155                         bb_error_msg_and_die("Cannot find uid/gid of user '%s'", nobodystr);
156                 nobody = pw->pw_uid;
157                 nogrp = pw->pw_gid;
158                 writepid(nobody, nogrp);
159
160                 close(0);
161                 inetbind();
162                 xsetgid(nogrp);
163                 xsetuid(nobody);
164                 close(1);
165                 close(2);
166
167                 signal(SIGHUP, SIG_IGN);
168                 signal(SIGPIPE, SIG_IGN); /* connection closed when writing (raises ???) */
169
170                 setsid();
171
172                 openlog(bb_applet_name, 0, LOG_DAEMON);
173                 return 1;
174         }
175
176         return 0;
177 }
178
179 static void deleteConn(int s)
180 {
181         int i = s - FCS;
182
183         close(s);
184
185         G.conncnt--;
186
187         /*
188          * Most of the time there is 0 connections. Most often that there
189          * is connections, there is just one connection. When this one connection
190          * closes, i == G.conncnt = 0 -> no copying.
191          * When there is more than one connection, the oldest connections closes
192          * earlier on average. When this happens, the code below starts copying
193          * the connection structure w/ highest index to the place which which is
194          * just deleted. This means that the connection structures are no longer
195          * in chronological order. I'd quess this means that when there is more
196          * than 1 connection, on average every other connection structure needs
197          * to be copied over the time all these connections are deleted.
198          */
199         if (i != G.conncnt) {
200                 memcpy(&conns[i], &conns[G.conncnt], sizeof(conns[0]));
201                 movefd(G.conncnt + FCS, s);
202         }
203
204         FD_CLR(G.conncnt + FCS, &G.readfds);
205 }
206
207 static int closeOldest(void)
208 {
209         time_t min = conns[0].lasttime;
210         int idx = 0;
211         int i;
212
213         for (i = 1; i < MAXCONNS; i++)
214                 if (conns[i].lasttime < min)
215                         idx = i;
216
217         replyError(idx + FCS, "X-SERVER-TOO-BUSY");
218         close(idx + FCS);
219
220         return idx;
221 }
222
223 static int checkInput(char *buf, int len, int l)
224 {
225         int i;
226         for (i = len; i < len + l; ++i)
227                 if (buf[i] == '\n')
228                         return 1;
229         return 0;
230 }
231
232 int fakeidentd_main(int argc, char **argv)
233 {
234         memset(conns, 0, sizeof(conns));
235         memset(&G, 0, sizeof(G));
236         FD_ZERO(&G.readfds);
237         FD_SET(0, &G.readfds);
238
239         /* handle -b <ip> parameter */
240         bb_getopt_ulflags(argc, argv, "b:", &bind_ip_address);
241         /* handle optional REPLY STRING */
242         if (optind < argc)
243                 G.identuser = argv[optind];
244         else
245                 G.identuser = nobodystr;
246
247         /* daemonize and have the parent return */
248         if (godaemon() == 0)
249                 return 0;
250
251         /* main loop where we process all events and never exit */
252         while (1) {
253         fd_set rfds = G.readfds;
254         struct timeval tv = { 15, 0 };
255         int i;
256         int tim = time(NULL);
257
258         select(G.conncnt + FCS, &rfds, NULL, NULL, G.conncnt? &tv: NULL);
259
260         for (i = G.conncnt - 1; i >= 0; i--) {
261                 int s = i + FCS;
262
263                 if (FD_ISSET(s, &rfds)) {
264                         char *buf = conns[i].buf;
265                         unsigned int len = conns[i].len;
266                         unsigned int l;
267
268                         if ((l = read(s, buf + len, sizeof(conns[0].buf) - len)) > 0) {
269                                 if (checkInput(buf, len, l)) {
270                                         reply(s, buf);
271                                         goto deleteconn;
272                                 } else if (len + l >= sizeof(conns[0].buf)) {
273                                         replyError(s, "X-INVALID-REQUEST");
274                                         goto deleteconn;
275                                 } else {
276                                         conns[i].len += l;
277                                 }
278                         } else {
279                                 goto deleteconn;
280                         }
281
282                         conns[i].lasttime = tim;
283                         continue;
284
285 deleteconn:
286                         deleteConn(s);
287                 } else {
288                         /* implement as time_after() in linux kernel sources ... */
289                         if (conns[i].lasttime + MAXIDLETIME <= tim) {
290                                 replyError(s, "X-TIMEOUT");
291                                 deleteConn(s);
292                         }
293                 }
294         }
295
296         if (FD_ISSET(0, &rfds)) {
297                 int s = accept(0, NULL, 0);
298
299                 if (s < 0) {
300                         if (errno != EINTR) /* EINTR */
301                                 syslog(LOG_ERR, "accept: %s", strerror(errno));
302                 } else {
303                         if (G.conncnt == MAXCONNS)
304                                 i = closeOldest();
305                         else
306                                 i = G.conncnt++;
307
308                         movefd(s, i + FCS); /* move if not already there */
309                         FD_SET(i + FCS, &G.readfds);
310
311                         conns[i].len = 0;
312                         conns[i].lasttime = time(NULL);
313                 }
314         }
315         } /* end of while(1) */
316
317         return 0;
318 }
319
320 static int parseAddrs(char *ptr, char **myaddr, char **heraddr);
321 static void reply(int s, char *buf)
322 {
323         char *myaddr, *heraddr;
324
325         myaddr = heraddr = NULL;
326
327         if (parseAddrs(buf, &myaddr, &heraddr))
328                 replyError(s, "X-INVALID-REQUEST");
329         else {
330                 struct iovec iv[6];
331                 iv[0].iov_base = myaddr;               iv[0].iov_len = strlen(myaddr);
332                 iv[1].iov_base = ", ";                 iv[1].iov_len = 2;
333                 iv[2].iov_base = heraddr;              iv[2].iov_len = strlen(heraddr);
334                 iv[3].iov_base = (void *)ident_substr; iv[3].iov_len = ident_substr_len;
335                 iv[4].iov_base = (void *)G.identuser;  iv[4].iov_len = strlen(G.identuser);
336                 iv[5].iov_base = "\r\n";               iv[5].iov_len = 2;
337                 writev(s, iv, 6);
338         }
339 }
340
341 static void replyError(int s, char *buf)
342 {
343         struct iovec iv[3];
344         iv[0].iov_base = "0, 0 : ERROR : ";   iv[0].iov_len = 15;
345         iv[1].iov_base = buf;                 iv[1].iov_len = strlen(buf);
346         iv[2].iov_base = "\r\n";              iv[2].iov_len = 2;
347         writev(s, iv, 3);
348 }
349
350 static int chmatch(char c, char *chars)
351 {
352         for (; *chars; chars++)
353                 if (c == *chars)
354                         return 1;
355         return 0;
356 }
357
358 static int skipchars(char **p, char *chars)
359 {
360         while (chmatch(**p, chars))
361                 (*p)++;
362         if (**p == '\r' || **p == '\n')
363                 return 0;
364         return 1;
365 }
366
367 static int parseAddrs(char *ptr, char **myaddr, char **heraddr)
368 {
369         /* parse <port-on-server> , <port-on-client> */
370
371         if (!skipchars(&ptr, " \t"))
372                 return -1;
373
374         *myaddr = ptr;
375
376         if (!skipchars(&ptr, "1234567890"))
377                 return -1;
378
379         if (!chmatch(*ptr, " \t,"))
380                 return -1;
381
382         *ptr++ = '\0';
383
384         if (!skipchars(&ptr, " \t,") )
385                 return -1;
386
387         *heraddr = ptr;
388
389         skipchars(&ptr, "1234567890");
390
391         if (!chmatch(*ptr, " \n\r"))
392                 return -1;
393
394         *ptr = '\0';
395
396         return 0;
397 }