Fix typo, and use the privileges drop.
[apps/pfixtools.git] / srsd.c
1 /******************************************************************************/
2 /*          pfixtools: a collection of postfix related tools                  */
3 /*          ~~~~~~~~~                                                         */
4 /*  ________________________________________________________________________  */
5 /*                                                                            */
6 /*  Redistribution and use in source and binary forms, with or without        */
7 /*  modification, are permitted provided that the following conditions        */
8 /*  are met:                                                                  */
9 /*                                                                            */
10 /*  1. Redistributions of source code must retain the above copyright         */
11 /*     notice, this list of conditions and the following disclaimer.          */
12 /*  2. Redistributions in binary form must reproduce the above copyright      */
13 /*     notice, this list of conditions and the following disclaimer in the    */
14 /*     documentation and/or other materials provided with the distribution.   */
15 /*  3. The names of its contributors may not be used to endorse or promote    */
16 /*     products derived from this software without specific prior written     */
17 /*     permission.                                                            */
18 /*                                                                            */
19 /*  THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
20 /*  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
21 /*  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR        */
22 /*  PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS    */
23 /*  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR    */
24 /*  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF      */
25 /*  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  */
26 /*  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN   */
27 /*  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)   */
28 /*  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF    */
29 /*  THE POSSIBILITY OF SUCH DAMAGE.                                           */
30 /******************************************************************************/
31
32 /*
33  * Copyright © 2005-2007 Pierre Habouzit
34  */
35
36 #include <fcntl.h>
37 #include <netinet/in.h>
38 #include <sys/epoll.h>
39 #include <sys/stat.h>
40
41 #include <srs2.h>
42
43 #include "common.h"
44 #include "daemon.h"
45 #include "mem.h"
46 #include "buffer.h"
47
48 #define DAEMON_NAME             "pfix-srsd"
49 #define DEFAULT_ENCODER_PORT    10000
50 #define DEFAULT_DECODER_PORT    10001
51 #define RUNAS_USER              "nobody"
52 #define RUNAS_GROUP             "nogroup"
53
54 #define __tostr(x)  #x
55 #define STR(x)      __tostr(x)
56
57 /* srs encoder/decoder/listener worker {{{ */
58
59 typedef struct srsd_t {
60     unsigned listener : 1;
61     unsigned decoder  : 1;
62     unsigned watchwr  : 1;
63     int fd;
64     buffer_t ibuf;
65     buffer_t obuf;
66 } srsd_t;
67
68 static srsd_t *srsd_new(void)
69 {
70     srsd_t *srsd = p_new(srsd_t, 1);
71     srsd->fd = -1;
72     return srsd;
73 }
74
75 static void srsd_delete(srsd_t **srsd)
76 {
77     if (*srsd) {
78         if ((*srsd)->fd >= 0)
79             close((*srsd)->fd);
80         buffer_wipe(&(*srsd)->ibuf);
81         buffer_wipe(&(*srsd)->obuf);
82         p_delete(srsd);
83     }
84 }
85
86 void urldecode(char *s, char *end)
87 {
88     char *p = s;
89
90     while (*p) {
91         if (*p == '%' && end - p >= 3) {
92             int h = (hexval(p[1]) << 4) | hexval(p[2]);
93
94             if (h >= 0) {
95                 *s++ = h;
96                 p += 3;
97                 continue;
98             }
99         }
100
101         *s++ = *p++;
102     }
103     *s++ = '\0';
104 }
105
106 int process_srs(srs_t *srs, const char *domain, srsd_t *srsd)
107 {
108     while (srsd->ibuf.len > 4) {
109         char buf[BUFSIZ], *p, *q, *nl;
110         int err;
111
112         nl = strchr(srsd->ibuf.data + 4, '\n');
113         if (!nl) {
114             if (srsd->ibuf.len > BUFSIZ) {
115                 syslog(LOG_ERR, "unreasonnable amount of data without a \\n");
116                 return -1;
117             }
118             return 0;
119         }
120
121         if (strncmp("get ", srsd->ibuf.data, 4)) {
122             syslog(LOG_ERR, "bad request, not starting with \"get \"");
123             return -1;
124         }
125
126         for (p = srsd->ibuf.data + 4; p < nl && isspace(*p); p++);
127         for (q = nl++; q >= p && isspace(*q); *q-- = '\0');
128
129         if (p == q) {
130             buffer_addstr(&srsd->obuf, "400 empty request ???\n");
131             syslog(LOG_WARNING, "empty request");
132             goto skip;
133         }
134
135         urldecode(p, q);
136
137         if (srsd->decoder) {
138             err = srs_reverse(srs, buf, ssizeof(buf), p);
139         } else {
140             err = srs_forward(srs, buf, ssizeof(buf), p, domain);
141         }
142
143         if (err == 0) {
144             buffer_addstr(&srsd->obuf, "200 ");
145             buffer_addstr(&srsd->obuf, buf);
146         } else {
147             switch (SRS_ERROR_TYPE(err)) {
148               case SRS_ERRTYPE_SRS:
149               case SRS_ERRTYPE_SYNTAX:
150                 buffer_addstr(&srsd->obuf, "500 ");
151                 break;
152               default:
153                 buffer_addstr(&srsd->obuf, "400 ");
154                 break;
155             }
156             buffer_addstr(&srsd->obuf, srs_strerror(err));
157         }
158         buffer_addch(&srsd->obuf, '\n');
159
160       skip:
161         buffer_consume(&srsd->ibuf, nl - srsd->ibuf.data);
162     }
163
164     return 0;
165 }
166
167 int start_listener(int epollfd, int port, bool decoder)
168 {
169     struct sockaddr_in addr = {
170         .sin_family = AF_INET,
171         .sin_addr   = { htonl(INADDR_LOOPBACK) },
172     };
173     struct epoll_event evt = { .events = EPOLLIN };
174     srsd_t *tmp;
175     int sock;
176
177     addr.sin_port = htons(port);
178     sock = tcp_listen_nonblock((const struct sockaddr *)&addr, sizeof(addr));
179     if (sock < 0) {
180         return -1;
181     }
182
183     evt.data.ptr  = tmp = srsd_new();
184     tmp->fd       = sock;
185     tmp->decoder  = decoder;
186     tmp->listener = true;
187     if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &evt) < 0) {
188         UNIXERR("epoll_ctl");
189         return -1;
190     }
191     return 0;
192 }
193
194 /* }}} */
195 /* administrivia {{{ */
196
197 static int main_initialize(void)
198 {
199     openlog(DAEMON_NAME, LOG_PID, LOG_MAIL);
200     signal(SIGPIPE, SIG_IGN);
201     signal(SIGINT,  &common_sighandler);
202     signal(SIGTERM, &common_sighandler);
203     signal(SIGHUP,  &common_sighandler);
204     syslog(LOG_INFO, "Starting...");
205     return 0;
206 }
207
208 static void main_shutdown(void)
209 {
210     syslog(LOG_INFO, cleanexit ? "Stopping..." : "Unclean exit...");
211     closelog();
212 }
213
214 module_init(main_initialize);
215 module_exit(main_shutdown);
216
217 void usage(void)
218 {
219     fputs("usage: "DAEMON_NAME" [options] domain secrets\n"
220           "\n"
221           "Options:\n"
222           "    -e <port>    port to listen to for encoding requests\n"
223           "                 (default: "STR(DEFAULT_ENCODER_PORT)")\n"
224           "    -d <port>    port to listen to for decoding requests\n"
225           "                 (default: "STR(DEFAULT_DECODER_PORT)")\n"
226           "    -p <pidfile> file to write our pid to\n"
227           "    -u           unsafe mode: don't drop privilegies\n"
228          , stderr);
229 }
230
231 /* }}} */
232
233 int main_loop(srs_t *srs, const char *domain, int port_enc, int port_dec)
234 {
235     int exitcode = EXIT_SUCCESS;
236     int epollfd = epoll_create(128);
237
238     if (epollfd < 0) {
239         UNIXERR("epoll_create");
240         exitcode = EXIT_FAILURE;
241         goto error;
242     }
243
244     if (start_listener(epollfd, port_enc, false) < 0)
245         return EXIT_FAILURE;
246     if (start_listener(epollfd, port_dec, true) < 0)
247         return EXIT_FAILURE;
248
249     while (!sigint) {
250         struct epoll_event evts[1024];
251         int n;
252
253         n = epoll_wait(epollfd, evts, countof(evts), -1);
254         if (n < 0) {
255             if (errno != EAGAIN && errno != EINTR) {
256                 UNIXERR("epoll_wait");
257                 exitcode = EXIT_FAILURE;
258                 break;
259             }
260             continue;
261         }
262
263         while (--n >= 0) {
264             srsd_t *srsd = evts[n].data.ptr;
265
266             if (srsd->listener) {
267                 struct epoll_event evt = { .events = EPOLLIN };
268                 srsd_t *tmp;
269                 int sock;
270
271                 sock = accept_nonblock(srsd->fd);
272                 if (sock < 0) {
273                     UNIXERR("accept");
274                     continue;
275                 }
276
277                 evt.data.ptr = tmp = srsd_new();
278                 tmp->decoder = srsd->decoder;
279                 tmp->fd      = sock;
280                 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &evt) < 0) {
281                     UNIXERR("epoll_ctl");
282                     srsd_delete(&tmp);
283                     close(sock);
284                 }
285                 continue;
286             }
287
288             if (evts[n].events & EPOLLIN) {
289                 int res = buffer_read(&srsd->ibuf, srsd->fd, -1);
290
291                 if ((res < 0 && errno != EINTR && errno != EAGAIN)
292                 ||  res == 0)
293                 {
294                     srsd_delete(&srsd);
295                     continue;
296                 }
297
298                 if (process_srs(srs, domain, srsd) < 0) {
299                     srsd_delete(&srsd);
300                     continue;
301                 }
302             }
303
304             if ((evts[n].events & EPOLLOUT) && srsd->obuf.len) {
305                 int res = write(srsd->fd, srsd->obuf.data, srsd->obuf.len);
306
307                 if (res < 0 && errno != EINTR && errno != EAGAIN) {
308                     srsd_delete(&srsd);
309                     continue;
310                 }
311
312                 if (res > 0) {
313                     buffer_consume(&srsd->obuf, res);
314                 }
315             }
316
317             if (srsd->watchwr == !srsd->obuf.len) {
318                 struct epoll_event evt = {
319                     .events   = EPOLLIN | (srsd->obuf.len ? EPOLLOUT : 0),
320                     .data.ptr = srsd,
321                 };
322                 if (epoll_ctl(epollfd, EPOLL_CTL_MOD, srsd->fd, &evt) < 0) {
323                     UNIXERR("epoll_ctl");
324                     srsd_delete(&srsd);
325                     continue;
326                 }
327                 srsd->watchwr = srsd->obuf.len != 0;
328             }
329         }
330     }
331
332     close(epollfd);
333
334   error:
335     cleanexit = true;
336     return exitcode;
337 }
338
339 static srs_t *srs_read_secrets(const char *sfile)
340 {
341     srs_t *srs;
342     char buf[BUFSIZ];
343     FILE *f;
344     int lineno = 0;
345
346     f = fopen(sfile, "r");
347     if (!f) {
348         UNIXERR("fopen");
349         return NULL;
350     }
351
352     srs = srs_new();
353
354     while (fgets(buf, sizeof(buf), f)) {
355         int n = strlen(buf);
356
357         ++lineno;
358         if (n == sizeof(buf) - 1 && buf[n - 1] != '\n') {
359             syslog(LOG_CRIT, "%s:%d: line too long", sfile, lineno);
360             goto error;
361         }
362         m_strrtrim(buf);
363         srs_add_secret(srs, skipspaces(buf));
364     }
365
366     if (!lineno) {
367         syslog(LOG_CRIT, "%s: empty file, no secrets", sfile);
368         goto error;
369     }
370
371     fclose(f);
372     return srs;
373
374   error:
375     fclose(f);
376     srs_free(srs);
377     return NULL;
378 }
379
380 int main(int argc, char *argv[])
381 {
382     bool unsafe  = false;
383     int port_enc = DEFAULT_ENCODER_PORT;
384     int port_dec = DEFAULT_DECODER_PORT;
385     const char *pidfile = NULL;
386
387     FILE *f = NULL;
388     int res;
389     srs_t *srs;
390
391     if (atexit(common_shutdown)) {
392         fputs("Cannot hook my atexit function, quitting !\n", stderr);
393         return EXIT_FAILURE;
394     }
395     common_initialize();
396
397     for (int c = 0; (c = getopt(argc, argv, "he:d:p:u")) >= 0; ) {
398         switch (c) {
399           case 'e':
400             port_enc = atoi(optarg);
401             break;
402           case 'd':
403             port_dec = atoi(optarg);
404             break;
405           case 'p':
406             pidfile = optarg;
407             break;
408           case 'u':
409             unsafe = true;
410             break;
411           default:
412             usage();
413             return EXIT_FAILURE;
414         }
415     }
416
417     if (argc - optind != 2) {
418         usage();
419         return EXIT_FAILURE;
420     }
421
422     srs = srs_read_secrets(argv[optind + 1]);
423     if (!srs) {
424         return EXIT_FAILURE;
425     }
426
427     if (pidfile) {
428         f = fopen(pidfile, "w");
429         if (!f) {
430             syslog(LOG_CRIT, "unable to write pidfile %s", pidfile);
431         }
432         fprintf(f, "%d\n", getpid());
433         fflush(f);
434     }
435
436     if (!unsafe && drop_privileges(RUNAS_USER, RUNAS_GROUP) < 0) {
437         syslog(LOG_CRIT, "unable to drop privileges");
438         return EXIT_FAILURE;
439     }
440
441     if (daemon_detach() < 0) {
442         syslog(LOG_CRIT, "unable to fork");
443         return EXIT_FAILURE;
444     }
445
446     if (f) {
447         rewind(f);
448         ftruncate(fileno(f), 0);
449         fprintf(f, "%d\n", getpid());
450         fflush(f);
451     }
452     res = main_loop(srs, argv[optind], port_enc, port_dec);
453     if (f) {
454         rewind(f);
455         ftruncate(fileno(f), 0);
456         fclose(f);
457         f = NULL;
458     }
459     return res;
460 }