main.c - geomyidae - A small C-based gopherd. | |
git clone git://bitreich.org/geomyidae/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfri… | |
Log | |
Files | |
Refs | |
Tags | |
README | |
LICENSE | |
--- | |
main.c (28355B) | |
--- | |
1 /* | |
2 * Copy me if you can. | |
3 * by 20h | |
4 */ | |
5 | |
6 #include <limits.h> | |
7 #include <unistd.h> | |
8 #include <dirent.h> | |
9 #include <memory.h> | |
10 #include <netdb.h> | |
11 #include <netinet/in.h> | |
12 #include <fcntl.h> | |
13 #include <stdio.h> | |
14 #include <stdlib.h> | |
15 #include <sys/socket.h> | |
16 #include <sys/stat.h> | |
17 #include <sys/wait.h> | |
18 #include <sys/types.h> | |
19 #include <netinet/tcp.h> | |
20 #include <signal.h> | |
21 #include <string.h> | |
22 #include <strings.h> | |
23 #include <time.h> | |
24 #include <pwd.h> | |
25 #include <grp.h> | |
26 #include <errno.h> | |
27 #include <arpa/inet.h> | |
28 #include <sys/select.h> | |
29 #include <sys/time.h> | |
30 #include <syslog.h> | |
31 | |
32 #ifdef ENABLE_TLS | |
33 #include <tls.h> | |
34 #endif /* ENABLE_TLS */ | |
35 | |
36 #include "ind.h" | |
37 #include "handlr.h" | |
38 #include "arg.h" | |
39 | |
40 enum { | |
41 NOLOG = 0, | |
42 FILES = 1, | |
43 DIRS = 2, | |
44 HTTP = 4, | |
45 ERRORS = 8, | |
46 CONN = 16, | |
47 GPLUS = 32 | |
48 }; | |
49 | |
50 int glfd = -1; | |
51 int dosyslog = 0; | |
52 int logpriority = LOG_INFO|LOG_DAEMON; | |
53 int loglvl = 47; | |
54 int revlookup = 0; | |
55 char *logfile = NULL; | |
56 | |
57 int *listfds = NULL; | |
58 int nlistfds = 0; | |
59 | |
60 char *argv0; | |
61 char stdbase[] = "/var/gopher"; | |
62 char *stdport = "70"; | |
63 char *indexf[] = {"index.gph", "index.cgi", "index.dcgi", "index.bob", "… | |
64 | |
65 char *nocgierr = "3Sorry, execution of the token '%s' was requested, but… | |
66 "is disabled in the server configuration.\tErr" | |
67 "\tlocalhost\t70\r\n"; | |
68 | |
69 char *notfounderr = "3Sorry, but the requested token '%s' could not be f… | |
70 "\tlocalhost\t70\r\n"; | |
71 | |
72 char *toolongerr = "3Sorry, but the requested token '%s' is a too long p… | |
73 "\tlocalhost\t70\r\n"; | |
74 | |
75 char *tlserr = "3Sorry, but the requested token '%s' requires an encrypt… | |
76 "\tlocalhost\t70\r\n"; | |
77 | |
78 /* TODO: Transform gopherspace to not need this anymore. See sacc(1). */ | |
79 char *htredir = "<!DOCTYPE html>\n" | |
80 "<html><head><title>gopher redirect</title>\n" | |
81 "<meta http-equiv=\"refresh\" content=\"1;url=%s\" />\n" | |
82 "</head><body>\n" | |
83 "Please consider using native gopher 'w' type.\n" | |
84 "HTML is insecure and bloated.<br/>\n" | |
85 "You will be redirected to: <a href=\"%s\">%s</a>.\n" | |
86 "</body></html>\n"; | |
87 | |
88 char *htescape = "3Happy helping ☃ here: " | |
89 "Sorry, your URI was not properly escaped." | |
90 "\tErr\tlocalhost\t70\r\n.\r\n\r\n"; | |
91 | |
92 char *selinval = "3Happy helping ☃ here: " | |
93 "Sorry, your selector does contains '..'. " | |
94 "That's illegal here.\tErr\tlocalhost\t70\r\n.\r\n\r\n"; | |
95 | |
96 int | |
97 dropprivileges(struct group *gr, struct passwd *pw) | |
98 { | |
99 if (gr != NULL) | |
100 if (setgroups(1, &gr->gr_gid) != 0 || setgid(gr->gr_gid)… | |
101 return -1; | |
102 if (pw != NULL) { | |
103 if (gr == NULL) { | |
104 if (setgroups(1, &pw->pw_gid) != 0 || | |
105 setgid(pw->pw_gid) != 0) | |
106 return -1; | |
107 } | |
108 if (setuid(pw->pw_uid) != 0) | |
109 return -1; | |
110 } | |
111 | |
112 return 0; | |
113 } | |
114 | |
115 void | |
116 logentry(char *host, char *port, char *qry, char *status) | |
117 { | |
118 time_t tim; | |
119 struct tm *ptr; | |
120 char timstr[128], *ahost; | |
121 | |
122 if (glfd >= 0 || dosyslog) { | |
123 ahost = revlookup ? reverselookup(host) : host; | |
124 if (dosyslog) { | |
125 syslog(logpriority, "[%s|%s|%s] %s\n", ahost, po… | |
126 status, qry); | |
127 } else { | |
128 tim = time(0); | |
129 ptr = gmtime(&tim); | |
130 strftime(timstr, sizeof(timstr), "%F %T %z", ptr… | |
131 dprintf(glfd, "[%s|%s|%s|%s] %s\n", | |
132 timstr, ahost, port, status, qry); | |
133 } | |
134 if (revlookup) | |
135 free(ahost); | |
136 } | |
137 | |
138 return; | |
139 } | |
140 | |
141 void | |
142 handlerequest(int sock, char *req, int rlen, char *base, char *ohost, | |
143 char *port, char *clienth, char *clientp, char *serverh, | |
144 char *serverp, int nocgi, int istls) | |
145 { | |
146 struct stat dir; | |
147 char recvc[1025], recvb[1025], path[PATH_MAX+1], args[1025], | |
148 argsc[1025], traverse[1025], traversec[1025], | |
149 *sear, *sep, *recvbp, *c; | |
150 int len = 0, fd, i, maxrecv, pathfallthrough = 0; | |
151 filetype *type; | |
152 | |
153 if (!istls) { | |
154 /* | |
155 * If sticky bit is set on base dir and encryption is not | |
156 * used, do not serve. | |
157 */ | |
158 if (stat(*base? base : "/", &dir) == -1) | |
159 return; | |
160 if (dir.st_mode & S_ISVTX) { | |
161 dprintf(sock, tlserr, recvc); | |
162 if (loglvl & ERRORS) { | |
163 logentry(clienth, clientp, recvc, | |
164 "encryption only"); | |
165 } | |
166 return; | |
167 } | |
168 } | |
169 | |
170 memset(&dir, 0, sizeof(dir)); | |
171 memset(recvb, 0, sizeof(recvb)); | |
172 memset(recvc, 0, sizeof(recvc)); | |
173 memset(args, 0, sizeof(args)); | |
174 memset(argsc, 0, sizeof(argsc)); | |
175 memset(traverse, 0, sizeof(traverse)); | |
176 memset(traversec, 0, sizeof(traversec)); | |
177 | |
178 maxrecv = sizeof(recvb) - 1; | |
179 if (rlen > maxrecv || rlen < 0) | |
180 return; | |
181 memcpy(recvb, req, rlen); | |
182 | |
183 c = strchr(recvb, '\r'); | |
184 if (c) | |
185 c[0] = '\0'; | |
186 c = strchr(recvb, '\n'); | |
187 if (c) | |
188 c[0] = '\0'; | |
189 | |
190 memmove(recvc, recvb, rlen+1); | |
191 /* | |
192 * Try to guess if we have some HTTP-like protocol compatibility | |
193 * mode. | |
194 */ | |
195 if (!nocgi && recvb[0] != '/' && (c = strchr(recvb, ' '))) { | |
196 *c = '\0'; | |
197 if (strchr(recvb, '/')) | |
198 goto dothegopher; | |
199 if (snprintf(path, sizeof(path), "%s/%s", base, recvb) <… | |
200 if (stat(path, &dir) == 0) { | |
201 if (loglvl & FILES) | |
202 logentry(clienth, clientp, recvc… | |
203 | |
204 handlecgi(sock, path, port, base, "", ""… | |
205 clienth, serverh, istls, req, ""… | |
206 return; | |
207 } | |
208 } | |
209 dothegopher: | |
210 *c = ' '; | |
211 } | |
212 | |
213 /* Do not allow requests including "..". */ | |
214 if (strstr(recvb, "..")) { | |
215 dprintf(sock, "%s", selinval); | |
216 return; | |
217 } | |
218 | |
219 sear = strchr(recvb, '\t'); | |
220 if (sear != NULL) { | |
221 *sear++ = '\0'; | |
222 | |
223 /* | |
224 * This is a compatibility layer to geomyidae for users … | |
225 * the original gopher(1) client. Gopher+ is by default | |
226 * requesting the metadata. We are using a trick in the | |
227 * gopher(1) parsing code to jump back to gopher compati… | |
228 * mode. DO NOT ADD ANY OTHER GOPHER+ SUPPORT. GOPHER+ IS | |
229 * CRAP. | |
230 */ | |
231 if ((sear[0] == '+' && sear[1] == '\0') | |
232 || (sear[0] == '$' && sear[1] == '\0') | |
233 || (sear[0] == '!' && sear[1] == '\0') | |
234 || sear[0] == '\0') { | |
235 if (loglvl & GPLUS) | |
236 logentry(clienth, clientp, recvb, "gophe… | |
237 dprintf(sock, "+-2\r\n"); | |
238 dprintf(sock, "+INFO: 1gopher+\t\t%s\t%s\r\n", | |
239 ohost, port); | |
240 dprintf(sock, "+ADMIN:\r\n Admin: Me\r\n"); | |
241 return; | |
242 } | |
243 } | |
244 | |
245 memmove(recvc, recvb, rlen+1); | |
246 | |
247 /* Redirect to HTML redirecting to the specified URI. */ | |
248 /* TODO: Fix gopherspace to not require this. */ | |
249 if (!strncmp(recvb, "URL:", 4)) { | |
250 for (i = 4; i < sizeof(recvb)-1; i++) { | |
251 switch (recvb[i]) { | |
252 case '\0': | |
253 i = sizeof(recvb); | |
254 break; | |
255 case '"': | |
256 case '&': | |
257 case '>': | |
258 case '<': | |
259 case ' ': | |
260 case '\'': | |
261 case '\\': | |
262 write(sock, htescape, strlen(htescape)); | |
263 if (loglvl & ERRORS) | |
264 logentry(clienth, clientp, recvc… | |
265 return; | |
266 } | |
267 } | |
268 len = snprintf(path, sizeof(path), htredir, | |
269 recvb + 4, recvb + 4, recvb + 4); | |
270 if (len > sizeof(path)) | |
271 len = sizeof(path); | |
272 write(sock, path, len); | |
273 if (loglvl & HTTP) | |
274 logentry(clienth, clientp, recvc, "HTTP redirect… | |
275 return; | |
276 } | |
277 | |
278 /* Strip off the arguments of req?args style. */ | |
279 c = strchr(recvb, '?'); | |
280 if (c != NULL) { | |
281 *c++ = '\0'; | |
282 snprintf(args, sizeof(args), "%s", c); | |
283 } | |
284 | |
285 /* Strip '/' at the end of the request. */ | |
286 for (c = recvb + strlen(recvb) - 1; c >= recvb && c[0] == '/'; c… | |
287 memmove(traversec, traverse, strlen(traverse)); | |
288 /* Prepend to traverse. */ | |
289 snprintf(traverse, sizeof(traverse), "/%s", traversec); | |
290 c[0] = '\0'; | |
291 } | |
292 | |
293 /* path is now always at least '/' */ | |
294 if (snprintf(path, sizeof(path), "%s%s%s", base, | |
295 (*recvb != '/')? "/" : "", | |
296 recvb) > sizeof(path)) { | |
297 if (loglvl & ERRORS) { | |
298 logentry(clienth, clientp, recvc, | |
299 "path truncation occurred"); | |
300 } | |
301 dprintf(sock, toolongerr, recvc); | |
302 return; | |
303 } | |
304 | |
305 fd = -1; | |
306 /* | |
307 * If path could not be found, do: | |
308 * 1.) Traverse from base directory one dir by dir. | |
309 * 2.) If one path element, separated by "/", is not found, stop. | |
310 * 3.) Prepare new args string: | |
311 * | |
312 * $args = $rest_of_path + "?" + $args | |
313 */ | |
314 if (stat(path, &dir) == -1) { | |
315 memmove(traversec, traverse, strlen(traverse)); | |
316 snprintf(path, sizeof(path), "%s", base); | |
317 recvbp = recvb; | |
318 | |
319 /* | |
320 * Walk into the selector until some directory or file | |
321 * does not exist. Then reconstruct the args, selector | |
322 * etc. | |
323 */ | |
324 while (recvbp != NULL) { | |
325 /* Traverse multiple empty / in selector. */ | |
326 while(recvbp[0] == '/') | |
327 recvbp++; | |
328 sep = strchr(recvbp, '/'); | |
329 if (sep != NULL) | |
330 *sep++ = '\0'; | |
331 | |
332 snprintf(path+strlen(path), sizeof(path)-strlen(… | |
333 "/%s", recvbp); | |
334 /* path is now always at least '/' */ | |
335 if (stat(path, &dir) == -1) { | |
336 path[strlen(path)-strlen(recvbp)-1] = '\… | |
337 snprintf(traverse, sizeof(traverse), | |
338 "/%s%s%s%s", | |
339 recvbp, | |
340 (sep != NULL)? "/" : "", | |
341 (sep != NULL)? sep : "", | |
342 (traversec[0] != '\0')? traverse… | |
343 ); | |
344 /* path fallthrough */ | |
345 pathfallthrough = 1; | |
346 break; | |
347 } | |
348 /* Append found directory to path. */ | |
349 recvbp = sep; | |
350 } | |
351 } | |
352 | |
353 if (stat(path, &dir) != -1) { | |
354 /* | |
355 * If sticky bit is set, only serve if this is encrypted. | |
356 */ | |
357 if ((dir.st_mode & S_ISVTX) && !istls) { | |
358 dprintf(sock, tlserr, recvc); | |
359 if (loglvl & ERRORS) { | |
360 logentry(clienth, clientp, recvc, | |
361 "encryption only"); | |
362 } | |
363 return; | |
364 } | |
365 | |
366 if (S_ISDIR(dir.st_mode)) { | |
367 for (i = 0; i < sizeof(indexf)/sizeof(indexf[0]); | |
368 i++) { | |
369 len = strlen(path); | |
370 if (len + strlen(indexf[i]) + ((path[len… | |
371 >= sizeof(path)) { | |
372 if (loglvl & ERRORS) { | |
373 logentry(clienth, client… | |
374 recvc, | |
375 "path truncation… | |
376 } | |
377 return; | |
378 } | |
379 /* | |
380 * The size check for strcat to work is | |
381 * calculated above this comment. | |
382 * | |
383 * Until strlcat isn't properly in all | |
384 * linux libcs, we keep to this. OpenBSD | |
385 * will complain about strcat and | |
386 * smart-ass gcc will cmplain about | |
387 * strncat of one char static char array | |
388 * is an overflow. | |
389 */ | |
390 if (path[len-1] != '/') | |
391 strcat(path, "/"); | |
392 strcat(path, indexf[i]); | |
393 fd = open(path, O_RDONLY); | |
394 if (fd >= 0) | |
395 break; | |
396 | |
397 /* Not found. Clear path from indexf. */ | |
398 path[len] = '\0'; | |
399 } | |
400 } else { | |
401 fd = open(path, O_RDONLY); | |
402 if (fd < 0) { | |
403 dprintf(sock, notfounderr, recvc); | |
404 if (loglvl & ERRORS) { | |
405 logentry(clienth, clientp, recvc, | |
406 strerror(errno)); | |
407 } | |
408 return; | |
409 } | |
410 } | |
411 } | |
412 | |
413 /* Some file was opened. Serve it. */ | |
414 if (fd >= 0) { | |
415 close(fd); | |
416 | |
417 c = strrchr(path, '/'); | |
418 if (c == NULL) | |
419 c = path; | |
420 type = gettype(c); | |
421 | |
422 /* | |
423 * If we had to traverse the path to find some, only | |
424 * allow index.dcgi and index.cgi as handlers. | |
425 */ | |
426 if (pathfallthrough && | |
427 !(type->f == handledcgi || type->f == ha… | |
428 dprintf(sock, notfounderr, recvc); | |
429 if (loglvl & ERRORS) { | |
430 logentry(clienth, clientp, recvc, | |
431 "handler in path fallthrough not… | |
432 } | |
433 return; | |
434 } | |
435 | |
436 if (nocgi && (type->f == handledcgi || type->f == handle… | |
437 dprintf(sock, nocgierr, recvc); | |
438 if (loglvl & ERRORS) | |
439 logentry(clienth, clientp, recvc, "nocgi… | |
440 } else { | |
441 if (loglvl & FILES) | |
442 logentry(clienth, clientp, recvc, "servi… | |
443 | |
444 type->f(sock, path, port, base, args, sear, ohos… | |
445 clienth, serverh, istls, recvc, traverse… | |
446 } | |
447 } else { | |
448 if (pathfallthrough && S_ISDIR(dir.st_mode)) { | |
449 dprintf(sock, notfounderr, recvc); | |
450 if (loglvl & ERRORS) { | |
451 logentry(clienth, clientp, recvc, | |
452 "directory listing in traversal … | |
453 } | |
454 return; | |
455 } | |
456 | |
457 if (!pathfallthrough && S_ISDIR(dir.st_mode)) { | |
458 handledir(sock, path, port, base, args, sear, oh… | |
459 clienth, serverh, istls, recvc, traverse… | |
460 if (loglvl & DIRS) { | |
461 logentry(clienth, clientp, recvc, | |
462 "dir listing"); | |
463 } | |
464 return; | |
465 } | |
466 | |
467 dprintf(sock, notfounderr, recvc); | |
468 if (loglvl & ERRORS) | |
469 logentry(clienth, clientp, recvc, "not found"); | |
470 } | |
471 | |
472 return; | |
473 } | |
474 | |
475 void | |
476 sighandler(int sig) | |
477 { | |
478 int i; | |
479 | |
480 switch (sig) { | |
481 case SIGCHLD: | |
482 while (waitpid(-1, NULL, WNOHANG) > 0); | |
483 break; | |
484 case SIGINT: | |
485 case SIGQUIT: | |
486 case SIGABRT: | |
487 case SIGTERM: | |
488 if (dosyslog) { | |
489 closelog(); | |
490 } else if (logfile != NULL && glfd != -1) { | |
491 close(glfd); | |
492 glfd = -1; | |
493 } | |
494 | |
495 for (i = 0; i < nlistfds; i++) { | |
496 shutdown(listfds[i], SHUT_RDWR); | |
497 close(listfds[i]); | |
498 } | |
499 free(listfds); | |
500 exit(0); | |
501 break; | |
502 default: | |
503 break; | |
504 } | |
505 } | |
506 | |
507 void | |
508 initsignals(void) | |
509 { | |
510 signal(SIGCHLD, sighandler); | |
511 signal(SIGHUP, sighandler); | |
512 signal(SIGINT, sighandler); | |
513 signal(SIGQUIT, sighandler); | |
514 signal(SIGABRT, sighandler); | |
515 signal(SIGTERM, sighandler); | |
516 | |
517 signal(SIGPIPE, SIG_IGN); | |
518 } | |
519 | |
520 /* | |
521 * TODO: Move Linux and BSD to Plan 9 socket and bind handling, so we do… | |
522 * need the inconsistent return and exit on getaddrinfo. | |
523 */ | |
524 int * | |
525 getlistenfd(struct addrinfo *hints, char *bindip, char *port, int *rlfdn… | |
526 { | |
527 char addstr[INET6_ADDRSTRLEN]; | |
528 struct addrinfo *ai, *rp; | |
529 void *sinaddr; | |
530 int on, *listenfds, *listenfd, aierr, errno_save; | |
531 | |
532 if ((aierr = getaddrinfo(bindip, port, hints, &ai)) || ai == NUL… | |
533 fprintf(stderr, "getaddrinfo (%s:%s): %s\n", bindip, por… | |
534 gai_strerror(aierr)); | |
535 exit(1); | |
536 } | |
537 | |
538 *rlfdnum = 0; | |
539 listenfds = NULL; | |
540 on = 1; | |
541 for (rp = ai; rp != NULL; rp = rp->ai_next) { | |
542 listenfds = xrealloc(listenfds, | |
543 sizeof(*listenfds) * (++*rlfdnum)); | |
544 listenfd = &listenfds[*rlfdnum-1]; | |
545 | |
546 *listenfd = socket(rp->ai_family, rp->ai_socktype, | |
547 rp->ai_protocol); | |
548 if (*listenfd < 0) | |
549 continue; | |
550 if (setsockopt(*listenfd, SOL_SOCKET, SO_REUSEADDR, &on, | |
551 sizeof(on)) < 0) { | |
552 close(*listenfd); | |
553 (*rlfdnum)--; | |
554 continue; | |
555 } | |
556 | |
557 if (rp->ai_family == AF_INET6 && (setsockopt(*listenfd, | |
558 IPPROTO_IPV6, IPV6_V6ONLY, &on, | |
559 sizeof(on)) < 0)) { | |
560 close(*listenfd); | |
561 (*rlfdnum)--; | |
562 continue; | |
563 } | |
564 | |
565 sinaddr = (rp->ai_family == AF_INET) ? | |
566 (void *)&((struct sockaddr_in *)rp->ai_addr)->… | |
567 (void *)&((struct sockaddr_in6 *)rp->ai_addr)-… | |
568 | |
569 if (bind(*listenfd, rp->ai_addr, rp->ai_addrlen) == 0) { | |
570 if (loglvl & CONN && inet_ntop(rp->ai_family, si… | |
571 addstr, sizeof(addstr))) { | |
572 /* Do not revlookup here. */ | |
573 on = revlookup; | |
574 revlookup = 0; | |
575 logentry(addstr, port, "-", "listening"); | |
576 revlookup = on; | |
577 } | |
578 continue; | |
579 } | |
580 | |
581 /* Save errno, because fprintf in logentry overwrites it… | |
582 errno_save = errno; | |
583 close(*listenfd); | |
584 (*rlfdnum)--; | |
585 if (loglvl & CONN && inet_ntop(rp->ai_family, sinaddr, | |
586 addstr, sizeof(addstr))) { | |
587 /* Do not revlookup here. */ | |
588 on = revlookup; | |
589 revlookup = 0; | |
590 logentry(addstr, port, "-", "could not bind"); | |
591 revlookup = on; | |
592 } | |
593 errno = errno_save; | |
594 } | |
595 freeaddrinfo(ai); | |
596 if (*rlfdnum < 1) { | |
597 free(listenfds); | |
598 return NULL; | |
599 } | |
600 | |
601 return listenfds; | |
602 } | |
603 | |
604 void | |
605 usage(void) | |
606 { | |
607 dprintf(2, "usage: %s [-46cdensy] [-l logfile] " | |
608 #ifdef ENABLE_TLS | |
609 "[-t keyfile certfile] " | |
610 #endif /* ENABLE_TLS */ | |
611 "[-v loglvl] [-b base] [-p port] [-o sport] " | |
612 "[-u user] [-g group] [-h host] [-i interface ...]\n", | |
613 argv0); | |
614 exit(1); | |
615 } | |
616 | |
617 int | |
618 main(int argc, char *argv[]) | |
619 { | |
620 struct addrinfo hints; | |
621 struct sockaddr_storage clt, slt; | |
622 socklen_t cltlen, sltlen; | |
623 int sock, dofork = 1, inetf = AF_UNSPEC, usechroot = 0, | |
624 nocgi = 0, errno_save, nbindips = 0, i, j, | |
625 nlfdret, *lfdret, listfd, maxlfd, istls = 0, | |
626 dotls = 0, dohaproxy = 0, tcpver = -1, haret = 0, | |
627 #ifdef ENABLE_TLS | |
628 tlssocks[2], shufbuf[1025], | |
629 shuflen, wlen, shufpos, tlsclientreader, | |
630 #endif /* ENABLE_TLS */ | |
631 maxrecv, retl, | |
632 rlen = 0; | |
633 fd_set rfd; | |
634 char *port, *base, clienth[NI_MAXHOST], clientp[NI_MAXSERV], | |
635 *user = NULL, *group = NULL, **bindips = NULL, | |
636 *ohost = NULL, *sport = NULL, *p; | |
637 /* Must be as large as recvb, due to scanf restrictions. */ | |
638 char hachost[1025], hashost[1025], hacport[1025], hasport[1025], | |
639 #ifdef ENABLE_TLS | |
640 *certfile = NULL, *keyfile = NULL, | |
641 #endif /* ENABLE_TLS */ | |
642 byte0, recvb[1025], serverh[NI_MAXHOST], serverp[NI_MAXSERV… | |
643 struct passwd *us = NULL; | |
644 struct group *gr = NULL; | |
645 #ifdef ENABLE_TLS | |
646 struct tls_config *tlsconfig = NULL; | |
647 struct tls *tlsctx = NULL, *tlsclientctx; | |
648 #endif /* ENABLE_TLS */ | |
649 | |
650 base = stdbase; | |
651 port = stdport; | |
652 | |
653 ARGBEGIN { | |
654 case '4': | |
655 inetf = AF_INET; | |
656 tcpver = 4; | |
657 break; | |
658 case '6': | |
659 inetf = AF_INET6; | |
660 tcpver = 6; | |
661 break; | |
662 case 'b': | |
663 base = EARGF(usage()); | |
664 break; | |
665 case 'c': | |
666 usechroot = 1; | |
667 break; | |
668 case 'd': | |
669 dofork = 0; | |
670 break; | |
671 case 'e': | |
672 nocgi = 1; | |
673 break; | |
674 case 'g': | |
675 group = EARGF(usage()); | |
676 break; | |
677 case 'h': | |
678 ohost = EARGF(usage()); | |
679 break; | |
680 case 'i': | |
681 bindips = xrealloc(bindips, sizeof(*bindips) * (++nbindi… | |
682 bindips[nbindips-1] = EARGF(usage()); | |
683 break; | |
684 case 'l': | |
685 logfile = EARGF(usage()); | |
686 break; | |
687 case 'n': | |
688 revlookup = 1; | |
689 break; | |
690 case 'o': | |
691 sport = EARGF(usage()); | |
692 break; | |
693 case 'p': | |
694 port = EARGF(usage()); | |
695 if (sport == NULL) | |
696 sport = port; | |
697 break; | |
698 case 's': | |
699 dosyslog = 1; | |
700 break; | |
701 #ifdef ENABLE_TLS | |
702 case 't': | |
703 dotls = 1; | |
704 keyfile = EARGF(usage()); | |
705 certfile = EARGF(usage()); | |
706 break; | |
707 #endif /* ENABLE_TLS */ | |
708 case 'u': | |
709 user = EARGF(usage()); | |
710 break; | |
711 case 'v': | |
712 loglvl = atoi(EARGF(usage())); | |
713 break; | |
714 case 'y': | |
715 dohaproxy = 1; | |
716 break; | |
717 default: | |
718 usage(); | |
719 } ARGEND; | |
720 | |
721 if (sport == NULL) | |
722 sport = port; | |
723 | |
724 if (argc != 0) | |
725 usage(); | |
726 | |
727 #ifdef ENABLE_TLS | |
728 if (dotls) { | |
729 if (tls_init() < 0) { | |
730 perror("tls_init"); | |
731 return 1; | |
732 } | |
733 if ((tlsconfig = tls_config_new()) == NULL) { | |
734 perror("tls_config_new"); | |
735 return 1; | |
736 } | |
737 if ((tlsctx = tls_server()) == NULL) { | |
738 perror("tls_server"); | |
739 return 1; | |
740 } | |
741 if (tls_config_set_key_file(tlsconfig, keyfile) < 0) { | |
742 perror("tls_config_set_key_file"); | |
743 return 1; | |
744 } | |
745 if (tls_config_set_cert_file(tlsconfig, certfile) < 0) { | |
746 perror("tls_config_set_cert_file"); | |
747 return 1; | |
748 } | |
749 if (tls_configure(tlsctx, tlsconfig) < 0) { | |
750 perror("tls_configure"); | |
751 return 1; | |
752 } | |
753 } | |
754 #endif /* ENABLE_TLS */ | |
755 | |
756 if (ohost == NULL) { | |
757 /* Do not use HOST_NAME_MAX, it is not defined on NetBSD… | |
758 ohost = xcalloc(1, 256+1); | |
759 if (gethostname(ohost, 256) < 0) { | |
760 perror("gethostname"); | |
761 free(ohost); | |
762 return 1; | |
763 } | |
764 } else { | |
765 ohost = xstrdup(ohost); | |
766 } | |
767 | |
768 if (group != NULL) { | |
769 errno = 0; | |
770 if ((gr = getgrnam(group)) == NULL) { | |
771 if (errno == 0) { | |
772 fprintf(stderr, "no such group '%s'\n", … | |
773 } else { | |
774 perror("getgrnam"); | |
775 } | |
776 return 1; | |
777 } | |
778 } | |
779 | |
780 if (user != NULL) { | |
781 errno = 0; | |
782 if ((us = getpwnam(user)) == NULL) { | |
783 if (errno == 0) { | |
784 fprintf(stderr, "no such user '%s'\n", u… | |
785 } else { | |
786 perror("getpwnam"); | |
787 } | |
788 return 1; | |
789 } | |
790 } | |
791 | |
792 if (dofork) { | |
793 switch (fork()) { | |
794 case -1: | |
795 perror("fork"); | |
796 return 1; | |
797 case 0: | |
798 break; | |
799 default: | |
800 return 0; | |
801 } | |
802 } | |
803 | |
804 if (dosyslog) { | |
805 openlog("geomyidae", dofork? LOG_NDELAY|LOG_PID \ | |
806 : LOG_CONS|LOG_PERROR, logpriority); | |
807 } else if (logfile != NULL) { | |
808 glfd = open(logfile, O_APPEND | O_WRONLY | O_CREAT, 0644… | |
809 if (glfd < 0) { | |
810 perror("log"); | |
811 return 1; | |
812 } | |
813 } else if (!dofork) { | |
814 glfd = 1; | |
815 } | |
816 | |
817 if (bindips == NULL) { | |
818 if (inetf == AF_INET || inetf == AF_UNSPEC) { | |
819 bindips = xrealloc(bindips, sizeof(*bindips) * (… | |
820 bindips[nbindips-1] = "0.0.0.0"; | |
821 } | |
822 if (inetf == AF_INET6 || inetf == AF_UNSPEC) { | |
823 bindips = xrealloc(bindips, sizeof(*bindips) * (… | |
824 bindips[nbindips-1] = "::"; | |
825 } | |
826 } | |
827 | |
828 for (i = 0; i < nbindips; i++) { | |
829 memset(&hints, 0, sizeof(hints)); | |
830 hints.ai_family = inetf; | |
831 hints.ai_flags = AI_PASSIVE; | |
832 hints.ai_socktype = SOCK_STREAM; | |
833 if (bindips[i]) | |
834 hints.ai_flags |= AI_CANONNAME; | |
835 | |
836 nlfdret = 0; | |
837 lfdret = getlistenfd(&hints, bindips[i], port, &nlfdret); | |
838 if (nlfdret < 1) { | |
839 errno_save = errno; | |
840 fprintf(stderr, "Unable to get a binding socket … | |
841 "%s:%s\n", bindips[i], port); | |
842 errno = errno_save; | |
843 perror("getlistenfd"); | |
844 } | |
845 | |
846 for (j = 0; j < nlfdret; j++) { | |
847 if (listen(lfdret[j], 4096) < 0) { | |
848 perror("listen"); | |
849 close(lfdret[j]); | |
850 continue; | |
851 } | |
852 listfds = xrealloc(listfds, | |
853 sizeof(*listfds) * ++nlistfds); | |
854 listfds[nlistfds-1] = lfdret[j]; | |
855 } | |
856 free(lfdret); | |
857 } | |
858 free(bindips); | |
859 | |
860 if (nlistfds < 1) | |
861 return 1; | |
862 | |
863 if (usechroot) { | |
864 if (chdir(base) < 0) { | |
865 perror("chdir"); | |
866 return 1; | |
867 } | |
868 base = ""; | |
869 if (chroot(".") < 0) { | |
870 perror("chroot"); | |
871 return 1; | |
872 } | |
873 } else if (*base != '/' && !(base = realpath(base, NULL))) { | |
874 perror("realpath"); | |
875 return 1; | |
876 } | |
877 | |
878 /* strip / at the end of base */ | |
879 for (p = base + strlen(base) - 1; p >= base && p[0] == '/'; --p) | |
880 p[0] = '\0'; | |
881 | |
882 if (dropprivileges(gr, us) < 0) { | |
883 perror("dropprivileges"); | |
884 | |
885 for (i = 0; i < nlistfds; i++) { | |
886 shutdown(listfds[i], SHUT_RDWR); | |
887 close(listfds[i]); | |
888 } | |
889 free(listfds); | |
890 return 1; | |
891 } | |
892 | |
893 initsignals(); | |
894 | |
895 #ifdef HOT_COMPUTER | |
896 #warning "I love you too." | |
897 #endif | |
898 | |
899 #ifdef __OpenBSD__ | |
900 char promises[31]; /* check the size needed in the fork too */ | |
901 snprintf(promises, sizeof(promises), "rpath inet stdio proc exec… | |
902 revlookup ? "dns" : ""); | |
903 if (pledge(promises, NULL) == -1) { | |
904 perror("pledge"); | |
905 exit(1); | |
906 } | |
907 #endif /* __OpenBSD__ */ | |
908 | |
909 while (1) { | |
910 FD_ZERO(&rfd); | |
911 maxlfd = 0; | |
912 for (i = 0; i < nlistfds; i++) { | |
913 FD_SET(listfds[i], &rfd); | |
914 if (listfds[i] > maxlfd) | |
915 maxlfd = listfds[i]; | |
916 } | |
917 | |
918 if (pselect(maxlfd+1, &rfd, NULL, NULL, NULL, NULL) < 0)… | |
919 if (errno == EINTR) | |
920 continue; | |
921 perror("pselect"); | |
922 break; | |
923 } | |
924 | |
925 listfd = -1; | |
926 for (i = 0; i < nlistfds; i++) { | |
927 if (FD_ISSET(listfds[i], &rfd)) { | |
928 listfd = listfds[i]; | |
929 break; | |
930 } | |
931 } | |
932 if (listfd < 0) | |
933 continue; | |
934 | |
935 cltlen = sizeof(clt); | |
936 sock = accept(listfd, (struct sockaddr *)&clt, &cltlen); | |
937 if (sock < 0) { | |
938 switch (errno) { | |
939 case ECONNABORTED: | |
940 case EINTR: | |
941 continue; | |
942 default: | |
943 perror("accept"); | |
944 close(listfd); | |
945 return 1; | |
946 } | |
947 } | |
948 | |
949 sltlen = sizeof(slt); | |
950 serverh[0] = serverp[0] = '\0'; | |
951 if (getsockname(sock, (struct sockaddr *)&slt, &sltlen) … | |
952 getnameinfo((struct sockaddr *)&slt, sltlen, ser… | |
953 sizeof(serverh), serverp, sizeof… | |
954 NI_NUMERICHOST|NI_NUMERICSERV); | |
955 } | |
956 if (!strncmp(serverh, "::ffff:", 7)) | |
957 memmove(serverh, serverh+7, strlen(serverh)-6); | |
958 | |
959 if (getnameinfo((struct sockaddr *)&clt, cltlen, clienth, | |
960 sizeof(clienth), clientp, sizeof(clientp… | |
961 NI_NUMERICHOST|NI_NUMERICSERV)) { | |
962 clienth[0] = clientp[0] = '\0'; | |
963 } | |
964 | |
965 if (!strncmp(clienth, "::ffff:", 7)) | |
966 memmove(clienth, clienth+7, strlen(clienth)-6); | |
967 | |
968 if (loglvl & CONN) | |
969 logentry(clienth, clientp, "-", "connected"); | |
970 | |
971 switch (fork()) { | |
972 case -1: | |
973 perror("fork"); | |
974 shutdown(sock, SHUT_RDWR); | |
975 break; | |
976 case 0: | |
977 close(listfd); | |
978 | |
979 signal(SIGHUP, SIG_DFL); | |
980 signal(SIGQUIT, SIG_DFL); | |
981 signal(SIGINT, SIG_DFL); | |
982 signal(SIGTERM, SIG_DFL); | |
983 signal(SIGALRM, SIG_DFL); | |
984 | |
985 #ifdef __OpenBSD__ | |
986 snprintf(promises, sizeof(promises), | |
987 "rpath inet stdio %s %s %s", | |
988 !nocgi || dotls ? "proc" : "", | |
989 nocgi ? "" : "exec", | |
990 revlookup ? "dns" : ""); | |
991 if (pledge(promises, NULL) == -1) { | |
992 perror("pledge"); | |
993 exit(1); | |
994 } | |
995 #endif /* __OpenBSD__ */ | |
996 | |
997 read_selector_again: | |
998 rlen = 0; | |
999 memset(recvb, 0, sizeof(recvb)); | |
1000 | |
1001 if (recv(sock, &byte0, 1, MSG_PEEK) < 1) | |
1002 return 1; | |
1003 | |
1004 #ifdef ENABLE_TLS | |
1005 /* | |
1006 * First byte is 0x16 == 22, which is the TLS | |
1007 * Handshake first byte. | |
1008 */ | |
1009 istls = 0; | |
1010 if (byte0 == 0x16 && dotls) { | |
1011 istls = 1; | |
1012 if (tls_accept_socket(tlsctx, &tlsclient… | |
1013 return 1; | |
1014 wlen = TLS_WANT_POLLIN; | |
1015 while (wlen == TLS_WANT_POLLIN \ | |
1016 || wlen == TLS_WANT_POLL… | |
1017 wlen = tls_handshake(tlsclientct… | |
1018 } | |
1019 if (wlen == -1) | |
1020 return 1; | |
1021 } | |
1022 #endif /* ENABLE_TLS */ | |
1023 /* | |
1024 * Some TLS request. Help them determine we only | |
1025 * serve plaintext. | |
1026 */ | |
1027 if (byte0 == 0x16 && !dotls) { | |
1028 if (loglvl & CONN) { | |
1029 logentry(clienth, clientp, "-", | |
1030 "disconnected"); | |
1031 } | |
1032 | |
1033 shutdown(sock, SHUT_RDWR); | |
1034 close(sock); | |
1035 | |
1036 return 1; | |
1037 } | |
1038 | |
1039 maxrecv = sizeof(recvb) - 1; | |
1040 do { | |
1041 #ifdef ENABLE_TLS | |
1042 if (istls) { | |
1043 retl = tls_read(tlsclientctx, | |
1044 recvb+rlen, 1); | |
1045 if (retl < 0) | |
1046 fprintf(stderr, "tls_rea… | |
1047 } else | |
1048 #endif /* ENABLE_TLS */ | |
1049 { | |
1050 retl = read(sock, recvb+rlen, | |
1051 1); | |
1052 if (retl < 0) | |
1053 perror("read"); | |
1054 } | |
1055 if (retl <= 0) | |
1056 break; | |
1057 rlen += retl; | |
1058 } while (recvb[rlen-1] != '\n' | |
1059 && --maxrecv > 0); | |
1060 if (rlen <= 0) | |
1061 return 1; | |
1062 | |
1063 /* | |
1064 * HAProxy v1 protocol support. | |
1065 * TODO: Add other protocol version support. | |
1066 */ | |
1067 if (dohaproxy && !strncmp(recvb, "PROXY TCP", 9)… | |
1068 if (p[-1] == '\r') | |
1069 p[-1] = '\0'; | |
1070 *p++ = '\0'; | |
1071 | |
1072 /* | |
1073 * Be careful, we are using scanf. | |
1074 * TODO: Use some better parsing. | |
1075 */ | |
1076 memset(hachost, 0, sizeof(hachost)); | |
1077 memset(hashost, 0, sizeof(hashost)); | |
1078 memset(hacport, 0, sizeof(hacport)); | |
1079 memset(hasport, 0, sizeof(hasport)); | |
1080 | |
1081 haret = sscanf(recvb, "PROXY TCP%d %s %s… | |
1082 &tcpver, hachost, hashost, hacpo… | |
1083 hasport); | |
1084 if (haret != 5) | |
1085 return 1; | |
1086 | |
1087 /* | |
1088 * Be careful. Everything could be | |
1089 * malicious. | |
1090 */ | |
1091 memset(clienth, 0, sizeof(clienth)); | |
1092 memmove(clienth, hachost, sizeof(clienth… | |
1093 memset(serverh, 0, sizeof(serverh)); | |
1094 memmove(serverh, hashost, sizeof(serverh… | |
1095 memset(clientp, 0, sizeof(clientp)); | |
1096 memmove(clientp, hacport, sizeof(clientp… | |
1097 memset(serverp, 0, sizeof(serverp)); | |
1098 memmove(serverp, hasport, sizeof(serverp… | |
1099 | |
1100 if (!strncmp(serverh, "::ffff:", 7)) { | |
1101 memmove(serverh, serverh+7, | |
1102 strlen(serverh)-… | |
1103 } | |
1104 if (!strncmp(clienth, "::ffff:", 7)) { | |
1105 memmove(clienth, clienth+7, | |
1106 strlen(clienth)-… | |
1107 } | |
1108 if (loglvl & CONN) { | |
1109 logentry(clienth, clientp, "-", | |
1110 "haproxy connect… | |
1111 } | |
1112 | |
1113 goto read_selector_again; | |
1114 } | |
1115 | |
1116 #ifdef ENABLE_TLS | |
1117 if (istls) { | |
1118 if (socketpair(AF_LOCAL, SOCK_STREAM, 0,… | |
1119 perror("tls_socketpair"); | |
1120 return 1; | |
1121 } | |
1122 | |
1123 switch(fork()) { | |
1124 case 0: | |
1125 sock = tlssocks[1]; | |
1126 close(tlssocks[0]); | |
1127 break; | |
1128 case -1: | |
1129 perror("fork"); | |
1130 return 1; | |
1131 default: | |
1132 tlsclientreader = 1; | |
1133 switch(fork()) { | |
1134 case 0: | |
1135 break; | |
1136 case -1: | |
1137 perror("fork"); | |
1138 return 1; | |
1139 default: | |
1140 tlsclientreader = 0; | |
1141 } | |
1142 | |
1143 close(tlssocks[tlsclientreader? … | |
1144 do { | |
1145 if (tlsclientreader) { | |
1146 shuflen = read(t… | |
1147 shufbuf, | |
1148 sizeof(s… | |
1149 } else { | |
1150 shuflen = tls_re… | |
1151 shufbuf, | |
1152 sizeof(s… | |
1153 if (shuflen == T… | |
1154 … | |
1155 continue; | |
1156 } | |
1157 } | |
1158 if (shuflen == -1 && err… | |
1159 continue; | |
1160 for (shufpos = 0; shufpo… | |
1161 shufpos … | |
1162 if (tlsclientrea… | |
1163 wlen = t… | |
1164 … | |
1165 … | |
1166 if (wlen… | |
1167 … | |
1168 … | |
1169 … | |
1170 } | |
1171 if (wlen… | |
1172 … | |
1173 … | |
1174 … | |
1175 … | |
1176 } | |
1177 } else { | |
1178 wlen = w… | |
1179 … | |
1180 … | |
1181 if (wlen… | |
1182 … | |
1183 … | |
1184 } | |
1185 } | |
1186 } | |
1187 } while (shuflen > 0); | |
1188 | |
1189 if (tlsclientreader) { | |
1190 wlen = TLS_WANT_POLLIN; | |
1191 while (wlen == TLS_WANT_… | |
1192 || wlen … | |
1193 wlen = tls_close… | |
1194 } | |
1195 tls_free(tlsclientctx); | |
1196 } | |
1197 | |
1198 lingersock(tlssocks[tlsclientrea… | |
1199 shutdown(tlssocks[tlsclientreade… | |
1200 tlsclientreader?… | |
1201 close(tlssocks[tlsclientreader? … | |
1202 | |
1203 if (tlsclientreader) { | |
1204 lingersock(sock); | |
1205 close(sock); | |
1206 } | |
1207 return 0; | |
1208 } | |
1209 } | |
1210 #endif /* ENABLE_TLS */ | |
1211 | |
1212 handlerequest(sock, recvb, rlen, base, | |
1213 (dohaproxy)? serverh : ohost, | |
1214 (dohaproxy)? serverp : sport, | |
1215 clienth, clientp, serverh, serve… | |
1216 nocgi, istls); | |
1217 | |
1218 lingersock(sock); | |
1219 shutdown(sock, SHUT_RDWR); | |
1220 close(sock); | |
1221 | |
1222 if (loglvl & CONN) { | |
1223 logentry(clienth, clientp, "-", | |
1224 "disconnected"); | |
1225 } | |
1226 | |
1227 return 0; | |
1228 default: | |
1229 break; | |
1230 } | |
1231 close(sock); | |
1232 } | |
1233 | |
1234 if (dosyslog) { | |
1235 closelog(); | |
1236 } else if (logfile != NULL && glfd != -1) { | |
1237 close(glfd); | |
1238 glfd = -1; | |
1239 } | |
1240 free(ohost); | |
1241 | |
1242 for (i = 0; i < nlistfds; i++) { | |
1243 shutdown(listfds[i], SHUT_RDWR); | |
1244 close(listfds[i]); | |
1245 } | |
1246 free(listfds); | |
1247 | |
1248 #ifdef ENABLE_TLS | |
1249 if (dotls) { | |
1250 tls_close(tlsctx); | |
1251 tls_free(tlsctx); | |
1252 tls_config_free(tlsconfig); | |
1253 } | |
1254 #endif /* ENABLE_TLS */ | |
1255 | |
1256 return 0; | |
1257 } | |
1258 |