Asynchronous DNS queries on iplist.
[apps/pfixtools.git] / pfix-srsd / main-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  * Copyright © 2008 Florent Bruneau
35  */
36
37 #include "common.h"
38
39 #include <srs2.h>
40
41 #include "epoll.h"
42 #include "mem.h"
43 #include "buffer.h"
44 #include "server.h"
45
46 #define DAEMON_NAME             "pfix-srsd"
47 #define DAEMON_VERSION          "0.1"
48 #define DEFAULT_ENCODER_PORT    10001
49 #define DEFAULT_DECODER_PORT    10002
50 #define RUNAS_USER              "nobody"
51 #define RUNAS_GROUP             "nogroup"
52
53 DECLARE_MAIN
54
55 typedef struct srs_config_t {
56     srs_t* srs;
57     const char* domain;
58 } srs_config_t;
59
60
61 /* Server {{{1
62  */
63
64 static const char* const decoder_ptr = "decoder";
65 static const char* const encoder_ptr = "encoder";
66
67 static void *srsd_new_decoder(void)
68 {
69     return (void*)decoder_ptr;
70 }
71
72 static void *srsd_new_encoder(void)
73 {
74     return (void*)encoder_ptr;
75 }
76
77 static void *srsd_starter(server_t *server)
78 {
79     return server->data;
80 }
81
82 int start_listener(int port, bool decoder)
83 {
84     return start_server(port, decoder ? srsd_new_decoder : srsd_new_encoder, NULL);
85 }
86
87
88 /* Processing {{{1
89  */
90
91 void urldecode(char *s, char *end)
92 {
93     char *p = s;
94
95     while (*p) {
96         if (*p == '%' && end - p >= 3) {
97             int h = (hexval(p[1]) << 4) | hexval(p[2]);
98
99             if (h >= 0) {
100                 *s++ = h;
101                 p += 3;
102                 continue;
103             }
104         }
105
106         *s++ = *p++;
107     }
108     *s++ = '\0';
109 }
110
111 int process_srs(server_t *srsd, void* vconfig)
112 {
113     srs_config_t* config = vconfig;
114     int res = buffer_read(&srsd->ibuf, srsd->fd, -1);
115
116     if ((res < 0 && errno != EINTR && errno != EAGAIN) || res == 0)
117         return -1;
118
119     while (srsd->ibuf.len > 4) {
120         char buf[BUFSIZ], *p, *q, *nl;
121         int err;
122
123         nl = strchr(srsd->ibuf.data + 4, '\n');
124         if (!nl) {
125             if (srsd->ibuf.len > BUFSIZ) {
126                 err("unreasonnable amount of data without a \\n");
127                 return -1;
128             }
129             if (srsd->obuf.len) {
130               epoll_modify(srsd->fd, EPOLLIN | EPOLLOUT, srsd);
131             }
132             return 0;
133         }
134
135         if (strncmp("get ", srsd->ibuf.data, 4)) {
136                                                 err("bad request, not starting with \"get \"");
137             return -1;
138         }
139
140         for (p = srsd->ibuf.data + 4; p < nl && isspace(*p); p++);
141         for (q = nl++; q >= p && isspace(*q); *q-- = '\0');
142
143         if (p == q) {
144             buffer_addstr(&srsd->obuf, "400 empty request ???\n");
145             warn("empty request");
146             goto skip;
147         }
148
149         urldecode(p, q);
150
151         if (srsd->data == (void*)decoder_ptr) {
152             err = srs_reverse(config->srs, buf, ssizeof(buf), p);
153         } else {
154             err = srs_forward(config->srs, buf, ssizeof(buf), p, config->domain);
155         }
156
157         if (err == 0) {
158             buffer_addstr(&srsd->obuf, "200 ");
159             buffer_addstr(&srsd->obuf, buf);
160         } else {
161             switch (SRS_ERROR_TYPE(err)) {
162               case SRS_ERRTYPE_SRS:
163               case SRS_ERRTYPE_SYNTAX:
164                 buffer_addstr(&srsd->obuf, "500 ");
165                 break;
166               default:
167                 buffer_addstr(&srsd->obuf, "400 ");
168                 break;
169             }
170             buffer_addstr(&srsd->obuf, srs_strerror(err));
171         }
172         buffer_addch(&srsd->obuf, '\n');
173
174       skip:
175         buffer_consume(&srsd->ibuf, nl - srsd->ibuf.data);
176     }
177     if (srsd->obuf.len) {
178       epoll_modify(srsd->fd, EPOLLIN | EPOLLOUT, srsd);
179     }
180     return 0;
181 }
182
183
184 /* config {{{1
185  */
186
187 static srs_config_t config = {
188     .srs = NULL,
189     .domain = NULL
190 };
191
192 /** overload srs_free since the lib is not properly maintained.
193  */
194 inline void srs_free(srs_t* srs)
195 {
196     int  i;
197     for (i = 0; i < srs->numsecrets; i++) {
198         memset(srs->secrets[i], 0, strlen(srs->secrets[i]));
199         free(srs->secrets[i]);
200         srs->secrets[i] = '\0';
201     }
202     if (srs->secrets) {
203         free(srs->secrets);
204     }
205     free(srs);
206 }
207
208 static void config_shutdown(void)
209 {
210     if (config.srs) {
211         srs_free(config.srs);
212         config.srs = NULL;
213     }
214 }
215
216 module_exit(config_shutdown);
217
218 static srs_t *srs_read_secrets(const char *sfile)
219 {
220     srs_t *srs;
221     char buf[BUFSIZ];
222     FILE *f;
223     int lineno = 0;
224
225     f = fopen(sfile, "r");
226     if (!f) {
227         UNIXERR("fopen");
228         return NULL;
229     }
230
231     srs = srs_new();
232
233     while (fgets(buf, sizeof(buf), f)) {
234         int n = strlen(buf);
235
236         ++lineno;
237         if (n == sizeof(buf) - 1 && buf[n - 1] != '\n') {
238             crit("%s:%d: line too long", sfile, lineno);
239             goto error;
240         }
241         m_strrtrim(buf);
242         srs_add_secret(srs, skipspaces(buf));
243     }
244
245     if (!lineno) {
246         crit("%s: empty file, no secrets", sfile);
247         goto error;
248     }
249
250     fclose(f);
251     return srs;
252
253   error:
254     fclose(f);
255     srs_free(srs);
256     return NULL;
257 }
258
259 /* administrivia {{{1
260  */
261
262 void usage(void)
263 {
264     fputs("usage: "DAEMON_NAME" [options] domain secrets\n"
265           "\n"
266           "Options:\n"
267           "    -e <port>    port to listen to for encoding requests\n"
268           "                 (default: "STR(DEFAULT_ENCODER_PORT)")\n"
269           "    -d <port>    port to listen to for decoding requests\n"
270           "                 (default: "STR(DEFAULT_DECODER_PORT)")\n"
271           "    -p <pidfile> file to write our pid to\n"
272           "    -u           unsafe mode: don't drop privilegies\n"
273           "    -f           stay in foreground\n"
274          , stderr);
275 }
276
277 /* }}}
278  */
279
280 int main(int argc, char *argv[])
281 {
282     bool unsafe  = false;
283     bool daemonize = true;
284     int port_enc = DEFAULT_ENCODER_PORT;
285     int port_dec = DEFAULT_DECODER_PORT;
286     const char *pidfile = NULL;
287
288     for (int c = 0; (c = getopt(argc, argv, "hfu" "e:d:p:")) >= 0; ) {
289         switch (c) {
290           case 'e':
291             port_enc = atoi(optarg);
292             break;
293           case 'f':
294             daemonize = false;
295             break;
296           case 'd':
297             port_dec = atoi(optarg);
298             break;
299           case 'p':
300             pidfile = optarg;
301             break;
302           case 'u':
303             unsafe = true;
304             break;
305           default:
306             usage();
307             return EXIT_FAILURE;
308         }
309     }
310
311     if (!daemonize) {
312         log_syslog = false;
313     }
314
315     if (argc - optind != 2) {
316         usage();
317         return EXIT_FAILURE;
318     }
319
320     info("starting %s v%s...", DAEMON_NAME, DAEMON_VERSION);
321
322     config.domain = argv[optind];
323     config.srs = srs_read_secrets(argv[optind + 1]);
324     if (!config.srs
325         || common_setup(pidfile, unsafe, RUNAS_USER, RUNAS_GROUP,
326                         daemonize) != EXIT_SUCCESS
327         || start_listener(port_enc, false) < 0
328         || start_listener(port_dec, true) < 0) {
329         return EXIT_FAILURE;
330     }
331     return server_loop(srsd_starter, NULL, process_srs, NULL, &config);
332 }