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