Port strlist to use async DNS.
[apps/pfixtools.git] / postlicyd / main-postlicyd.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 © 2006-2007 Pierre Habouzit
34  * Copyright © 2008 Florent Bruneau
35  */
36
37 #include <getopt.h>
38
39 #include "buffer.h"
40 #include "common.h"
41 #include "epoll.h"
42 #include "policy_tokens.h"
43 #include "server.h"
44 #include "config.h"
45 #include "postlicyd.h"
46
47 #define DAEMON_NAME             "postlicyd"
48 #define DAEMON_VERSION          "0.2"
49 #define DEFAULT_PORT            10000
50 #define RUNAS_USER              "nobody"
51 #define RUNAS_GROUP             "nogroup"
52
53 DECLARE_MAIN
54
55 static config_t *config = NULL;
56
57
58 static void *query_starter(server_t* server)
59 {
60     query_context_t *context = p_new(query_context_t, 1);
61     filter_context_prepare(&context->context, context);
62     return context;
63 }
64
65 static void query_stopper(void *data)
66 {
67     query_context_t **context = data;
68     if (*context) {
69         filter_context_wipe(&(*context)->context);
70         p_delete(context);
71     }
72 }
73
74 static bool config_refresh(void *mconfig)
75 {
76     return config_reload(mconfig);
77 }
78
79 __attribute__((format(printf,2,0)))
80 static void policy_answer(server_t *pcy, const char *fmt, ...)
81 {
82     va_list args;
83     query_context_t *context = pcy->data;
84     const query_t* query = &context->query;
85
86     buffer_addstr(&pcy->obuf, "action=");
87     va_start(args, fmt);
88     buffer_addvf(&pcy->obuf, fmt, args);
89     va_end(args);
90     buffer_addstr(&pcy->obuf, "\n\n");
91     buffer_consume(&pcy->ibuf, query->eoq - pcy->ibuf.data);
92     epoll_modify(pcy->fd, EPOLLIN | EPOLLOUT, pcy);
93 }
94
95 static const filter_t *next_filter(server_t *pcy, const filter_t *filter,
96                                    const query_t *query, const filter_hook_t *hook, bool *ok) {
97     if (hook == NULL) {
98         warn("request client=%s, from=<%s>, to=<%s>: aborted",
99              query->client_name,
100              query->sender == NULL ? "undefined" : query->sender,
101              query->recipient == NULL ? "undefined" : query->recipient);
102         *ok = false;
103         return NULL;
104     } else if (hook->async) {
105         debug("request client=%s, from=<%s>, to=<%s>: "
106               "asynchronous filter from filter %s",
107                query->client_name,
108                query->sender == NULL ? "undefined" : query->sender,
109                query->recipient == NULL ? "undefined" : query->recipient,
110                filter->name);
111         *ok = true;
112         return NULL;
113     } else if (hook->postfix) {
114         info("request client=%s, from=<%s>, to=<%s>: "
115              "awswer %s from filter %s: \"%s\"",
116              query->client_name,
117              query->sender == NULL ? "undefined" : query->sender,
118              query->recipient == NULL ? "undefined" : query->recipient,
119              htokens[hook->type], filter->name, hook->value);
120         policy_answer(pcy, "%s", hook->value);
121         *ok = true;
122         return NULL;
123     } else {
124         debug("request client=%s, from=<%s>, to=<%s>: "
125                "awswer %s from filter %s: next filter %s",
126                query->client_name,
127                query->sender == NULL ? "undefined" : query->sender,
128                query->recipient == NULL ? "undefined" : query->recipient,
129                htokens[hook->type], filter->name,
130                (array_ptr(config->filters, hook->filter_id))->name);
131         return array_ptr(config->filters, hook->filter_id);
132     }
133 }
134
135 static bool policy_process(server_t *pcy, const config_t *mconfig)
136 {
137     query_context_t *context = pcy->data;
138     const query_t* query = &context->query;
139     const filter_t *filter;
140     if (mconfig->entry_points[query->state] == -1) {
141         warn("no filter defined for current protocol_state (%d)", query->state);
142         return false;
143     }
144     if (context->context.current_filter != NULL) {
145         filter = context->context.current_filter;
146     } else {
147         filter = array_ptr(mconfig->filters, mconfig->entry_points[query->state]);
148     }
149     context->context.current_filter = NULL;
150     while (true) {
151         bool  ok = false;
152         const filter_hook_t *hook = filter_run(filter, query, &context->context);
153         filter = next_filter(pcy, filter, query, hook, &ok);
154         if (filter == NULL) {
155             return ok;
156         }
157     }
158 }
159
160 static int policy_run(server_t *pcy, void* vconfig)
161 {
162     int search_offs = MAX(0, (int)(pcy->ibuf.len - 1));
163     int nb = buffer_read(&pcy->ibuf, pcy->fd, -1);
164     const char *eoq;
165     query_context_t *context = pcy->data;
166     query_t  *query  = &context->query;
167     context->server = pcy;
168     const config_t *mconfig = vconfig;
169
170     if (nb < 0) {
171         if (errno == EAGAIN || errno == EINTR)
172             return 0;
173         UNIXERR("read");
174         return -1;
175     }
176     if (nb == 0) {
177         if (pcy->ibuf.len)
178             err("unexpected end of data");
179         return -1;
180     }
181
182     if (!(eoq = strstr(pcy->ibuf.data + search_offs, "\n\n")))
183         return 0;
184
185     if (!query_parse(pcy->data, pcy->ibuf.data))
186         return -1;
187     query->eoq = eoq + strlen("\n\n");
188     epoll_modify(pcy->fd, 0, pcy);
189     return policy_process(pcy, mconfig) ? 0 : -1;
190 }
191
192 static void policy_async_handler(filter_context_t *context,
193                                  const filter_hook_t *hook)
194 {
195     bool ok = false;
196     const filter_t *filter = context->current_filter;
197     query_context_t *qctx  = context->data;
198     query_t         *query = &qctx->query;
199     server_t        *server = qctx->server;
200
201     context->current_filter = next_filter(server, filter, query, hook, &ok);
202     if (context->current_filter != NULL) {
203         ok = policy_process(server, config);
204     }
205     if (!ok) {
206         server_release(server);
207     }
208 }
209
210 static int postlicyd_init(void)
211 {
212     filter_async_handler_register(policy_async_handler);
213     return 0;
214 }
215 module_init(postlicyd_init);
216
217 int start_listener(int port)
218 {
219     return start_server(port, NULL, NULL);
220 }
221
222 /* administrivia {{{ */
223
224 void usage(void)
225 {
226     fputs("usage: "DAEMON_NAME" [options] config\n"
227           "\n"
228           "Options:\n"
229           "    -l <port>    port to listen to\n"
230           "    -p <pidfile> file to write our pid to\n"
231           "    -f           stay in foreground\n"
232           "    -d           grow logging level\n"
233           "    -u           unsafe mode (don't drop privileges)\n"
234          , stderr);
235 }
236
237 /* }}} */
238
239 int main(int argc, char *argv[])
240 {
241     bool unsafe = false;
242     const char *pidfile = NULL;
243     bool daemonize = true;
244     int port = DEFAULT_PORT;
245     bool port_from_cli = false;
246
247     for (int c = 0; (c = getopt(argc, argv, "ufd" "l:p:")) >= 0; ) {
248         switch (c) {
249           case 'p':
250             pidfile = optarg;
251             break;
252           case 'u':
253             unsafe = true;
254             break;
255           case 'l':
256             port = atoi(optarg);
257             port_from_cli = true;
258             break;
259           case 'f':
260             daemonize = false;
261             break;
262           case 'd':
263             ++log_level;
264             break;
265           default:
266             usage();
267             return EXIT_FAILURE;
268         }
269     }
270
271     if (!daemonize) {
272         log_syslog = false;
273     }
274
275     if (argc - optind != 1) {
276         usage();
277         return EXIT_FAILURE;
278     }
279
280     info("starting %s v%s...", DAEMON_NAME, DAEMON_VERSION);
281
282     if (pidfile_open(pidfile) < 0) {
283         crit("unable to write pidfile %s", pidfile);
284         return EXIT_FAILURE;
285     }
286
287     if (drop_privileges(RUNAS_USER, RUNAS_GROUP) < 0) {
288         crit("unable to drop privileges");
289         return EXIT_FAILURE;
290     }
291
292     config = config_read(argv[optind]);
293     if (config == NULL) {
294         return EXIT_FAILURE;
295     }
296     if (port_from_cli || config->port == 0) {
297         config->port = port;
298     }
299
300     if (daemonize && daemon_detach() < 0) {
301         crit("unable to fork");
302         return EXIT_FAILURE;
303     }
304
305     pidfile_refresh();
306
307     if (start_listener(config->port) < 0) {
308         return EXIT_FAILURE;
309     } else {
310         return server_loop(query_starter, query_stopper,
311                            policy_run, config_refresh, config);
312     }
313 }