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 | }
|
---|