a09befbdda8b2416bea8cf7b07592aa7dddb281f
[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 __tostr(x)  #x
52 #define STR(x)      __tostr(x)
53
54 /* srs encoder/decoder/listener worker {{{ */
55
56 typedef struct srsd_t {
57     unsigned listener : 1;
58     unsigned decoder  : 1;
59     unsigned watchwr  : 1;
60     int fd;
61     buffer_t ibuf;
62     buffer_t obuf;
63 } srsd_t;
64
65 static srsd_t *srsd_new(void)
66 {
67     srsd_t *srsd = p_new(srsd_t, 1);
68     srsd->fd = -1;
69     return srsd;
70 }
71
72 static void srsd_delete(srsd_t **srsd)
73 {
74     if (*srsd) {
75         if ((*srsd)->fd >= 0)
76             close((*srsd)->fd);
77         buffer_wipe(&(*srsd)->ibuf);
78         buffer_wipe(&(*srsd)->obuf);
79         p_delete(srsd);
80     }
81 }
82
83 void urldecode(char *s, char *end)
84 {
85     char *p = s;
86
87     while (*p) {
88         if (*p == '%' && end - p >= 3) {
89             int h = (hexval(p[1]) << 4) | hexval(p[2]);
90
91             if (h >= 0) {
92                 *s++ = h;
93                 p += 3;
94                 continue;
95             }
96         }
97
98         *s++ = *p++;
99     }
100     *s++ = '\0';
101 }
102
103 int process_srs(srs_t *srs, const char *domain, srsd_t *srsd)
104 {
105     while (srsd->ibuf.len > 4) {
106         char buf[BUFSIZ], *p, *q, *nl;
107         int err;
108
109         nl = strchr(srsd->ibuf.data + 4, '\n');
110         if (!nl) {
111             if (srsd->ibuf.len > BUFSIZ) {
112                 syslog(LOG_ERR, "unreasonnable amount of data without a \\n");
113                 return -1;
114             }
115             return 0;
116         }
117
118         if (strncmp("get ", srsd->ibuf.data, 4)) {
119             syslog(LOG_ERR, "bad request, not starting with \"get \"");
120             return -1;
121         }
122
123         for (p = srsd->ibuf.data + 4; p < nl && isspace(*p); p++);
124         for (q = nl++; q >= p && isspace(*q); *q-- = '\0');
125
126         if (p == q) {
127             buffer_addstr(&srsd->obuf, "400 empty request ???\n");
128             syslog(LOG_WARNING, "empty request");
129             goto skip;
130         }
131
132         urldecode(p, q);
133
134         if (srsd->decoder) {
135             err = srs_reverse(srs, buf, ssizeof(buf), p);
136         } else {
137             err = srs_forward(srs, buf, ssizeof(buf), p, domain);
138         }
139
140         if (err == 0) {
141             buffer_addstr(&srsd->obuf, "200 ");
142             buffer_addstr(&srsd->obuf, buf);
143         } else {
144             switch (SRS_ERROR_TYPE(err)) {
145               case SRS_ERRTYPE_SRS:
146               case SRS_ERRTYPE_SYNTAX:
147                 buffer_addstr(&srsd->obuf, "500 ");
148                 break;
149               default:
150                 buffer_addstr(&srsd->obuf, "400 ");
151                 break;
152             }
153             buffer_addstr(&srsd->obuf, srs_strerror(err));
154         }
155         buffer_addch(&srsd->obuf, '\n');
156
157       skip:
158         buffer_consume(&srsd->ibuf, nl - srsd->ibuf.data);
159     }
160
161     return 0;
162 }
163
164 int start_listener(int epollfd, int port, bool decoder)
165 {
166     struct sockaddr_in addr = {
167         .sin_family = AF_INET,
168         .sin_addr   = { htonl(INADDR_LOOPBACK) },
169     };
170     struct epoll_event evt = { .events = EPOLLIN };
171     srsd_t *tmp;
172     int sock;
173
174     addr.sin_port = htons(port);
175     sock = tcp_listen_nonblock((const struct sockaddr *)&addr, sizeof(addr));
176     if (sock < 0) {
177         return -1;
178     }
179
180     evt.data.ptr  = tmp = srsd_new();
181     tmp->fd       = sock;
182     tmp->decoder  = decoder;
183     tmp->listener = true;
184     if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &evt) < 0) {
185         UNIXERR("epoll_ctl");
186         return -1;
187     }
188     return 0;
189 }
190
191 /* }}} */
192 /* administrivia {{{ */
193
194 static int main_initialize(void)
195 {
196     openlog(DAEMON_NAME, LOG_PID, LOG_MAIL);
197     signal(SIGPIPE, SIG_IGN);
198     signal(SIGINT,  &common_sighandler);
199     signal(SIGTERM, &common_sighandler);
200     signal(SIGHUP,  &common_sighandler);
201     syslog(LOG_INFO, "Starting...");
202     return 0;
203 }
204
205 static void main_shutdown(void)
206 {
207     syslog(LOG_INFO, cleanexit ? "Stopping..." : "Unclean exit...");
208     closelog();
209 }
210
211 module_init(main_initialize);
212 module_exit(main_shutdown);
213
214 void usage(void)
215 {
216     fputs("usage: "DAEMON_NAME" [options] domain secrets\n"
217           "\n"
218           "Options:\n"
219           "    -e <port>    port to listen to for encoding requests\n"
220           "                 (default: "STR(DEFAULT_ENCODER_PORT)")\n"
221           "    -d <port>    port to listen to for decoding requests\n"
222           "                 (default: "STR(DEFAULT_DECODER_PORT)")\n"
223           "    -p <pidfile> file to write our pid to\n"
224          , stderr);
225 }
226
227 /* }}} */
228
229 int main_loop(srs_t *srs, const char *domain, int port_enc, int port_dec)
230 {
231     int exitcode = EXIT_SUCCESS;
232     int epollfd = epoll_create(128);
233
234     if (epollfd < 0) {
235         UNIXERR("epoll_create");
236         exitcode = EXIT_FAILURE;
237         goto error;
238     }
239
240     if (start_listener(epollfd, port_enc, false) < 0)
241         return EXIT_FAILURE;
242     if (start_listener(epollfd, port_dec, true) < 0)
243         return EXIT_FAILURE;
244
245     while (!sigint) {
246         struct epoll_event evts[1024];
247         int n;
248
249         n = epoll_wait(epollfd, evts, countof(evts), -1);
250         if (n < 0) {
251             if (errno != EAGAIN && errno != EINTR) {
252                 UNIXERR("epoll_wait");
253                 exitcode = EXIT_FAILURE;
254                 break;
255             }
256             continue;
257         }
258
259         while (--n >= 0) {
260             srsd_t *srsd = evts[n].data.ptr;
261
262             if (srsd->listener) {
263                 struct epoll_event evt = { .events = EPOLLIN };
264                 srsd_t *tmp;
265                 int sock;
266
267                 sock = accept_nonblock(srsd->fd);
268                 if (sock < 0) {
269                     UNIXERR("accept");
270                     continue;
271                 }
272
273                 evt.data.ptr = tmp = srsd_new();
274                 tmp->decoder = srsd->decoder;
275                 tmp->fd      = sock;
276                 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sock, &evt) < 0) {
277                     UNIXERR("epoll_ctl");
278                     srsd_delete(&tmp);
279                     close(sock);
280                 }
281                 continue;
282             }
283
284             if (evts[n].events & EPOLLIN) {
285                 int res = buffer_read(&srsd->ibuf, srsd->fd, -1);
286
287                 if ((res < 0 && errno != EINTR && errno != EAGAIN)
288                 ||  res == 0)
289                 {
290                     srsd_delete(&srsd);
291                     continue;
292                 }
293
294                 if (process_srs(srs, domain, srsd) < 0) {
295                     srsd_delete(&srsd);
296                     continue;
297                 }
298             }
299
300             if ((evts[n].events & EPOLLOUT) && srsd->obuf.len) {
301                 int res = write(srsd->fd, srsd->obuf.data, srsd->obuf.len);
302
303                 if (res < 0 && errno != EINTR && errno != EAGAIN) {
304                     srsd_delete(&srsd);
305                     continue;
306                 }
307
308                 if (res > 0) {
309                     buffer_consume(&srsd->obuf, res);
310                 }
311             }
312
313             if (srsd->watchwr == !srsd->obuf.len) {
314                 struct epoll_event evt = {
315                     .events   = EPOLLIN | (srsd->obuf.len ? EPOLLOUT : 0),
316                     .data.ptr = srsd,
317                 };
318                 if (epoll_ctl(epollfd, EPOLL_CTL_MOD, srsd->fd, &evt) < 0) {
319                     UNIXERR("epoll_ctl");
320                     srsd_delete(&srsd);
321                     continue;
322                 }
323                 srsd->watchwr = srsd->obuf.len != 0;
324             }
325         }
326     }
327
328     close(epollfd);
329
330   error:
331     cleanexit = true;
332     return exitcode;
333 }
334
335 static srs_t *srs_read_secrets(const char *sfile)
336 {
337     srs_t *srs;
338     char buf[BUFSIZ];
339     FILE *f;
340     int lineno = 0;
341
342     f = fopen(sfile, "r");
343     if (!f) {
344         UNIXERR("fopen");
345         return NULL;
346     }
347
348     srs = srs_new();
349
350     while (fgets(buf, sizeof(buf), f)) {
351         int n = strlen(buf);
352
353         ++lineno;
354         if (n == sizeof(buf) - 1 && buf[n - 1] != '\n') {
355             syslog(LOG_CRIT, "%s:%d: line too long", sfile, lineno);
356             goto error;
357         }
358         m_strrtrim(buf);
359         srs_add_secret(srs, skipspaces(buf));
360     }
361
362     if (!lineno) {
363         syslog(LOG_CRIT, "%s: empty file, no secrets", sfile);
364         goto error;
365     }
366
367     fclose(f);
368     return srs;
369
370   error:
371     fclose(f);
372     srs_free(srs);
373     return NULL;
374 }
375
376 int main(int argc, char *argv[])
377 {
378     int port_enc = DEFAULT_ENCODER_PORT;
379     int port_dec = DEFAULT_DECODER_PORT;
380     const char *pidfile = NULL;
381
382     FILE *f = NULL;
383     int res;
384     srs_t *srs;
385
386     if (atexit(common_shutdown)) {
387         fputs("Cannot hook my atexit function, quitting !\n", stderr);
388         return EXIT_FAILURE;
389     }
390     common_initialize();
391
392     for (int c = 0; (c = getopt(argc, argv, "he:d:p:")) >= 0; ) {
393         switch (c) {
394           case 'e':
395             port_enc = atoi(optarg);
396             break;
397           case 'd':
398             port_dec = atoi(optarg);
399             break;
400           case 'p':
401             pidfile = optarg;
402             break;
403           default:
404             usage();
405             return EXIT_FAILURE;
406         }
407     }
408
409     if (argc - optind != 2) {
410         usage();
411         return EXIT_FAILURE;
412     }
413
414     srs = srs_read_secrets(argv[optind + 1]);
415     if (!srs) {
416         return EXIT_FAILURE;
417     }
418
419     if (pidfile) {
420         f = fopen(pidfile, "w");
421         if (!f) {
422             syslog(LOG_CRIT, "unable to write pidfile %s", pidfile);
423         }
424         fprintf(f, "%d\n", getpid());
425         fflush(f);
426     }
427     if (daemon_detach() < 0) {
428         syslog(LOG_CRIT, "unable to fork");
429         return EXIT_FAILURE;
430     }
431     if (f) {
432         rewind(f);
433         ftruncate(fileno(f), 0);
434         fprintf(f, "%d\n", getpid());
435         fclose(f);
436         f = NULL;
437     }
438     res = main_loop(srs, argv[optind], port_enc, port_dec);
439     if (pidfile) {
440         unlink(pidfile);
441     }
442     return res;
443 }