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