1*510586acSclaudio /* $OpenBSD: mda.c,v 1.147 2024/01/20 09:01:03 claudio Exp $ */
21f3c2bcfSsobrado
33ef9cbf7Sgilles /*
465c4fdfbSgilles * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
53ef9cbf7Sgilles * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
6e5b07014Sgilles * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7719ffb95Seric * Copyright (c) 2012 Eric Faurot <eric@openbsd.org>
83ef9cbf7Sgilles *
93ef9cbf7Sgilles * Permission to use, copy, modify, and distribute this software for any
103ef9cbf7Sgilles * purpose with or without fee is hereby granted, provided that the above
113ef9cbf7Sgilles * copyright notice and this permission notice appear in all copies.
123ef9cbf7Sgilles *
133ef9cbf7Sgilles * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
143ef9cbf7Sgilles * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
153ef9cbf7Sgilles * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
163ef9cbf7Sgilles * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
173ef9cbf7Sgilles * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
183ef9cbf7Sgilles * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
193ef9cbf7Sgilles * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
203ef9cbf7Sgilles */
213ef9cbf7Sgilles
22a7b2e833Seric #include <ctype.h>
23c50073caSchl #include <inttypes.h>
243ef9cbf7Sgilles #include <stdlib.h>
25a05ec33aSgilles #include <string.h>
26eb268927Sgilles #include <sysexits.h>
270dcffd0dSop #include <time.h>
283ef9cbf7Sgilles #include <unistd.h>
291743d5a3Sjacekm #include <vis.h>
303ef9cbf7Sgilles
313ef9cbf7Sgilles #include "smtpd.h"
325eb8dddaSgilles #include "log.h"
333ef9cbf7Sgilles
34719ffb95Seric #define MDA_HIWAT 65536
35719ffb95Seric
36985341b1Seric struct mda_envelope {
37985341b1Seric TAILQ_ENTRY(mda_envelope) entry;
381e265b4eSgilles uint64_t session_id;
39985341b1Seric uint64_t id;
40985341b1Seric time_t creation;
41985341b1Seric char *sender;
42985341b1Seric char *rcpt;
43a8e22235Sgilles char *dest;
44985341b1Seric char *user;
45a8e22235Sgilles char *dispatcher;
46f67bab36Sgilles char *mda_subaddress;
47a8e22235Sgilles char *mda_exec;
48985341b1Seric };
49985341b1Seric
507eed50e8Seric #define USER_WAITINFO 0x01
517eed50e8Seric #define USER_RUNNABLE 0x02
527eed50e8Seric #define USER_ONHOLD 0x04
537eed50e8Seric #define USER_HOLDQ 0x08
54985341b1Seric
55dc65775cSeric struct mda_user {
567eed50e8Seric uint64_t id;
57dc65775cSeric TAILQ_ENTRY(mda_user) entry;
58793ff23cSeric TAILQ_ENTRY(mda_user) entry_runnable;
59953aae25Sderaadt char name[LOGIN_NAME_MAX];
60953aae25Sderaadt char usertable[PATH_MAX];
61793ff23cSeric size_t evpcount;
62985341b1Seric TAILQ_HEAD(, mda_envelope) envelopes;
63985341b1Seric int flags;
64dc65775cSeric size_t running;
6565c4fdfbSgilles struct userinfo userinfo;
66dc65775cSeric };
67dc65775cSeric
68793ff23cSeric struct mda_session {
6965c4fdfbSgilles uint64_t id;
70793ff23cSeric struct mda_user *user;
71985341b1Seric struct mda_envelope *evp;
728d3f7f0dSeric struct io *io;
73793ff23cSeric FILE *datafp;
74793ff23cSeric };
75793ff23cSeric
76b556a8d3Seric static void mda_io(struct io *, int, void *);
77985341b1Seric static int mda_check_loop(FILE *, struct mda_envelope *);
78719ffb95Seric static int mda_getlastline(int, char *, size_t);
7965c4fdfbSgilles static void mda_done(struct mda_session *);
8063b53c7eSsunil static void mda_fail(struct mda_user *, int, const char *,
8163b53c7eSsunil enum enhanced_status_code);
82793ff23cSeric static void mda_drain(void);
83985341b1Seric static void mda_log(const struct mda_envelope *, const char *, const char *);
84aa1d5973Seric static void mda_queue_ok(uint64_t);
8563b53c7eSsunil static void mda_queue_tempfail(uint64_t, const char *,
8663b53c7eSsunil enum enhanced_status_code);
87aa1d5973Seric static void mda_queue_permfail(uint64_t, const char *, enum enhanced_status_code);
88aa1d5973Seric static void mda_queue_loop(uint64_t);
897eed50e8Seric static struct mda_user *mda_user(const struct envelope *);
907eed50e8Seric static void mda_user_free(struct mda_user *);
917eed50e8Seric static const char *mda_user_to_text(const struct mda_user *);
921e265b4eSgilles static struct mda_envelope *mda_envelope(uint64_t, const struct envelope *);
937eed50e8Seric static void mda_envelope_free(struct mda_envelope *);
947eed50e8Seric static struct mda_session * mda_session(struct mda_user *);
95eb268927Sgilles static const char *mda_sysexit_to_str(int);
961743d5a3Sjacekm
9776d9cc75Seric static struct tree sessions;
987eed50e8Seric static struct tree users;
99e5b07014Sgilles
100793ff23cSeric static TAILQ_HEAD(, mda_user) runnable;
101dc65775cSeric
102aa1d5973Seric void
mda_imsg(struct mproc * p,struct imsg * imsg)10365c4fdfbSgilles mda_imsg(struct mproc *p, struct imsg *imsg)
1043ef9cbf7Sgilles {
105ed1929b6Sjacekm struct mda_session *s;
106793ff23cSeric struct mda_user *u;
107985341b1Seric struct mda_envelope *e;
108985341b1Seric struct envelope evp;
10965c4fdfbSgilles struct deliver deliver;
11065c4fdfbSgilles struct msg m;
11165c4fdfbSgilles const void *data;
112eb268927Sgilles const char *error, *parent_error, *syserror;
11365c4fdfbSgilles uint64_t reqid;
11465c4fdfbSgilles size_t sz;
115953aae25Sderaadt char out[256], buf[LINE_MAX];
116*510586acSclaudio int n, fd;
117985341b1Seric enum lka_resp_status status;
118eb268927Sgilles enum mda_resp_status mda_status;
119eb268927Sgilles int mda_sysexit;
120ed1929b6Sjacekm
12165c4fdfbSgilles switch (imsg->hdr.type) {
122aa1d5973Seric case IMSG_MDA_LOOKUP_USERINFO:
12365c4fdfbSgilles m_msg(&m, imsg);
1247eed50e8Seric m_get_id(&m, &reqid);
125985341b1Seric m_get_int(&m, (int *)&status);
12665c4fdfbSgilles if (status == LKA_OK)
12765c4fdfbSgilles m_get_data(&m, &data, &sz);
12865c4fdfbSgilles m_end(&m);
12965c4fdfbSgilles
1307eed50e8Seric u = tree_xget(&users, reqid);
13165c4fdfbSgilles
13265c4fdfbSgilles if (status == LKA_TEMPFAIL)
13365c4fdfbSgilles mda_fail(u, 0,
134fe95d8d0Seric "Temporary failure in user lookup",
135fe95d8d0Seric ESC_OTHER_ADDRESS_STATUS);
13665c4fdfbSgilles else if (status == LKA_PERMFAIL)
13765c4fdfbSgilles mda_fail(u, 1,
138fe95d8d0Seric "Permanent failure in user lookup",
139fe95d8d0Seric ESC_DESTINATION_MAILBOX_HAS_MOVED);
14065c4fdfbSgilles else {
141bc1ed85bSsunil if (sz != sizeof(u->userinfo))
142bc1ed85bSsunil fatalx("mda: userinfo size mismatch");
14365c4fdfbSgilles memmove(&u->userinfo, data, sz);
1447eed50e8Seric u->flags &= ~USER_WAITINFO;
1457eed50e8Seric u->flags |= USER_RUNNABLE;
14665c4fdfbSgilles TAILQ_INSERT_TAIL(&runnable, u, entry_runnable);
14765c4fdfbSgilles mda_drain();
14865c4fdfbSgilles }
14965c4fdfbSgilles return;
150793ff23cSeric
151aa1d5973Seric case IMSG_QUEUE_DELIVER:
15265c4fdfbSgilles m_msg(&m, imsg);
153985341b1Seric m_get_envelope(&m, &evp);
15465c4fdfbSgilles m_end(&m);
155793ff23cSeric
1567eed50e8Seric u = mda_user(&evp);
157985341b1Seric
1587eed50e8Seric if (u->evpcount >= env->sc_mda_task_hiwat) {
1597eed50e8Seric if (!(u->flags & USER_ONHOLD)) {
1607eed50e8Seric log_debug("debug: mda: hiwat reached for "
1617eed50e8Seric "user \"%s\": holding envelopes",
1627eed50e8Seric mda_user_to_text(u));
1637eed50e8Seric u->flags |= USER_ONHOLD;
1647eed50e8Seric }
1657eed50e8Seric }
1667eed50e8Seric
1677eed50e8Seric if (u->flags & USER_ONHOLD) {
1687eed50e8Seric u->flags |= USER_HOLDQ;
16963b53c7eSsunil m_create(p_queue, IMSG_MDA_DELIVERY_HOLD,
17063b53c7eSsunil 0, 0, -1);
1717eed50e8Seric m_add_evpid(p_queue, evp.id);
1727eed50e8Seric m_add_id(p_queue, u->id);
1737eed50e8Seric m_close(p_queue);
174793ff23cSeric return;
175793ff23cSeric }
176793ff23cSeric
1771e265b4eSgilles e = mda_envelope(u->id, &evp);
1787eed50e8Seric TAILQ_INSERT_TAIL(&u->envelopes, e, entry);
1797eed50e8Seric u->evpcount += 1;
180793ff23cSeric stat_increment("mda.pending", 1);
181793ff23cSeric
1827eed50e8Seric if (!(u->flags & USER_RUNNABLE) &&
1837eed50e8Seric !(u->flags & USER_WAITINFO)) {
1847eed50e8Seric u->flags |= USER_RUNNABLE;
1857eed50e8Seric TAILQ_INSERT_TAIL(&runnable, u, entry_runnable);
1867eed50e8Seric }
187985341b1Seric
188793ff23cSeric mda_drain();
189793ff23cSeric return;
190793ff23cSeric
191aa1d5973Seric case IMSG_MDA_OPEN_MESSAGE:
19265c4fdfbSgilles m_msg(&m, imsg);
19365c4fdfbSgilles m_get_id(&m, &reqid);
19465c4fdfbSgilles m_end(&m);
19565c4fdfbSgilles
19665c4fdfbSgilles s = tree_xget(&sessions, reqid);
19765c4fdfbSgilles e = s->evp;
198793ff23cSeric
199*510586acSclaudio fd = imsg_get_fd(imsg);
200*510586acSclaudio if (fd == -1) {
20182614934Seric log_debug("debug: mda: cannot get message fd");
20263b53c7eSsunil mda_queue_tempfail(e->id,
20363b53c7eSsunil "Cannot get message fd",
204fe95d8d0Seric ESC_OTHER_MAIL_SYSTEM_STATUS);
205985341b1Seric mda_log(e, "TempFail", "Cannot get message fd");
20665c4fdfbSgilles mda_done(s);
207719ffb95Seric return;
208719ffb95Seric }
209a7b2e833Seric
210d7bcae4dSeric log_debug("debug: mda: got message fd %d "
211985341b1Seric "for session %016"PRIx64 " evpid %016"PRIx64,
212*510586acSclaudio fd, s->id, e->id);
213985341b1Seric
214*510586acSclaudio if ((s->datafp = fdopen(fd, "r")) == NULL) {
21582614934Seric log_warn("warn: mda: fdopen");
216*510586acSclaudio close(fd);
217aa1d5973Seric mda_queue_tempfail(e->id, "fdopen failed",
218fe95d8d0Seric ESC_OTHER_MAIL_SYSTEM_STATUS);
21965c4fdfbSgilles mda_log(e, "TempFail", "fdopen failed");
22065c4fdfbSgilles mda_done(s);
221719ffb95Seric return;
222719ffb95Seric }
223719ffb95Seric
224719ffb95Seric /* check delivery loop */
225985341b1Seric if (mda_check_loop(s->datafp, e)) {
22682614934Seric log_debug("debug: mda: loop detected");
227aa1d5973Seric mda_queue_loop(e->id);
22865c4fdfbSgilles mda_log(e, "PermFail", "Loop detected");
22965c4fdfbSgilles mda_done(s);
230a7b2e833Seric return;
231a7b2e833Seric }
232a7b2e833Seric
233719ffb95Seric /* start queueing delivery headers */
2344809a514Sgilles if (e->sender[0])
23563b53c7eSsunil /*
23663b53c7eSsunil * XXX: remove existing Return-Path,
23763b53c7eSsunil * if any
23863b53c7eSsunil */
2398d3f7f0dSeric n = io_printf(s->io,
2404ddc25d9Sgilles "Return-Path: <%s>\n"
2410173e407Schrisz "Delivered-To: %s\n",
24263b53c7eSsunil e->sender,
24363b53c7eSsunil e->rcpt ? e->rcpt : e->dest);
244719ffb95Seric else
2458d3f7f0dSeric n = io_printf(s->io,
246985341b1Seric "Delivered-To: %s\n",
247985341b1Seric e->rcpt ? e->rcpt : e->dest);
248719ffb95Seric if (n == -1) {
24965c4fdfbSgilles log_warn("warn: mda: "
25065c4fdfbSgilles "fail to write delivery info");
251aa1d5973Seric mda_queue_tempfail(e->id, "Out of memory",
252fe95d8d0Seric ESC_OTHER_MAIL_SYSTEM_STATUS);
253985341b1Seric mda_log(e, "TempFail", "Out of memory");
25465c4fdfbSgilles mda_done(s);
255719ffb95Seric return;
256719ffb95Seric }
257719ffb95Seric
258e5b07014Sgilles /* request parent to fork a helper process */
259c1392a69Seric memset(&deliver, 0, sizeof deliver);
2608ba4ad8bSgilles (void)text_to_mailaddr(&deliver.sender, s->evp->sender);
2618ba4ad8bSgilles (void)text_to_mailaddr(&deliver.rcpt, s->evp->rcpt);
2628ba4ad8bSgilles (void)text_to_mailaddr(&deliver.dest, s->evp->dest);
263a8e22235Sgilles if (s->evp->mda_exec)
264a8e22235Sgilles (void)strlcpy(deliver.mda_exec, s->evp->mda_exec, sizeof deliver.mda_exec);
265f67bab36Sgilles if (s->evp->mda_subaddress)
266f67bab36Sgilles (void)strlcpy(deliver.mda_subaddress, s->evp->mda_subaddress, sizeof deliver.mda_subaddress);
267a8e22235Sgilles (void)strlcpy(deliver.dispatcher, s->evp->dispatcher, sizeof deliver.dispatcher);
268a8e22235Sgilles deliver.userinfo = s->user->userinfo;
269e5b07014Sgilles
270985341b1Seric log_debug("debug: mda: querying mda fd "
271985341b1Seric "for session %016"PRIx64 " evpid %016"PRIx64,
272985341b1Seric s->id, s->evp->id);
273985341b1Seric
274aa1d5973Seric m_create(p_parent, IMSG_MDA_FORK, 0, 0, -1);
27565c4fdfbSgilles m_add_id(p_parent, reqid);
27665c4fdfbSgilles m_add_data(p_parent, &deliver, sizeof(deliver));
27765c4fdfbSgilles m_close(p_parent);
2783ef9cbf7Sgilles return;
2791eb29730Sjacekm
280aa1d5973Seric case IMSG_MDA_FORK:
28165c4fdfbSgilles m_msg(&m, imsg);
28265c4fdfbSgilles m_get_id(&m, &reqid);
28365c4fdfbSgilles m_end(&m);
28465c4fdfbSgilles
28565c4fdfbSgilles s = tree_xget(&sessions, reqid);
28665c4fdfbSgilles e = s->evp;
287*510586acSclaudio fd = imsg_get_fd(imsg);
288*510586acSclaudio if (fd == -1) {
289b317af72Snicm log_warn("warn: mda: fail to retrieve mda fd");
290aa1d5973Seric mda_queue_tempfail(e->id, "Cannot get mda fd",
291fe95d8d0Seric ESC_OTHER_MAIL_SYSTEM_STATUS);
29265c4fdfbSgilles mda_log(e, "TempFail", "Cannot get mda fd");
29365c4fdfbSgilles mda_done(s);
294719ffb95Seric return;
295719ffb95Seric }
296719ffb95Seric
297d7bcae4dSeric log_debug("debug: mda: got mda fd %d "
298985341b1Seric "for session %016"PRIx64 " evpid %016"PRIx64,
299*510586acSclaudio fd, s->id, s->evp->id);
300985341b1Seric
301*510586acSclaudio io_set_nonblocking(fd);
302*510586acSclaudio io_set_fd(s->io, fd);
3038d3f7f0dSeric io_set_write(s->io);
304ed1929b6Sjacekm return;
3053ef9cbf7Sgilles
306ed1929b6Sjacekm case IMSG_MDA_DONE:
30765c4fdfbSgilles m_msg(&m, imsg);
30865c4fdfbSgilles m_get_id(&m, &reqid);
309eb268927Sgilles m_get_int(&m, (int *)&mda_status);
310eb268927Sgilles m_get_int(&m, (int *)&mda_sysexit);
31165c4fdfbSgilles m_get_string(&m, &parent_error);
31265c4fdfbSgilles m_end(&m);
31365c4fdfbSgilles
31465c4fdfbSgilles s = tree_xget(&sessions, reqid);
315985341b1Seric e = s->evp;
3161743d5a3Sjacekm /*
3171743d5a3Sjacekm * Grab last line of mda stdout/stderr if available.
3181743d5a3Sjacekm */
31965c4fdfbSgilles out[0] = '\0';
320*510586acSclaudio fd = imsg_get_fd(imsg);
321*510586acSclaudio if (fd != -1)
322*510586acSclaudio mda_getlastline(fd, out, sizeof(out));
323eb268927Sgilles
32411dbc40fSjacekm /*
3251743d5a3Sjacekm * Choose between parent's description of error and
3261743d5a3Sjacekm * child's output, the latter having preference over
3271743d5a3Sjacekm * the former.
32811dbc40fSjacekm */
329e5b07014Sgilles error = NULL;
330eb268927Sgilles if (mda_status == MDA_OK) {
331eb268927Sgilles if (s->datafp || (s->io && io_queued(s->io))) {
332e5b07014Sgilles error = "mda exited prematurely";
333eb268927Sgilles mda_status = MDA_TEMPFAIL;
334eb268927Sgilles }
335719ffb95Seric } else
33665c4fdfbSgilles error = out[0] ? out : parent_error;
3373ef9cbf7Sgilles
338eb268927Sgilles syserror = NULL;
339e570109dSgilles if (mda_sysexit)
340eb268927Sgilles syserror = mda_sysexit_to_str(mda_sysexit);
341eb268927Sgilles
3421743d5a3Sjacekm /* update queue entry */
343eb268927Sgilles switch (mda_status) {
344eb268927Sgilles case MDA_TEMPFAIL:
345aa1d5973Seric mda_queue_tempfail(e->id, error,
346fe95d8d0Seric ESC_OTHER_MAIL_SYSTEM_STATUS);
34763b53c7eSsunil (void)snprintf(buf, sizeof buf,
348e570109dSgilles "Error (%s%s%s)",
349e570109dSgilles syserror ? syserror : "",
350e570109dSgilles syserror ? ": " : "",
351e570109dSgilles error);
352985341b1Seric mda_log(e, "TempFail", buf);
353eb268927Sgilles break;
354eb268927Sgilles case MDA_PERMFAIL:
355eb268927Sgilles mda_queue_permfail(e->id, error,
356eb268927Sgilles ESC_OTHER_MAIL_SYSTEM_STATUS);
357eb268927Sgilles (void)snprintf(buf, sizeof buf,
358e570109dSgilles "Error (%s%s%s)",
359e570109dSgilles syserror ? syserror : "",
360e570109dSgilles syserror ? ": " : "",
361e570109dSgilles error);
362eb268927Sgilles mda_log(e, "PermFail", buf);
363eb268927Sgilles break;
364eb268927Sgilles case MDA_OK:
365aa1d5973Seric mda_queue_ok(e->id);
366985341b1Seric mda_log(e, "Ok", "Delivered");
367eb268927Sgilles break;
36865c4fdfbSgilles }
36965c4fdfbSgilles mda_done(s);
370ed1929b6Sjacekm return;
3713ef9cbf7Sgilles }
3721eb29730Sjacekm
373ff01b044Seric fatalx("mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type));
374af8de4b6Sgilles }
375af8de4b6Sgilles
376aa1d5973Seric void
mda_postfork(void)37704fee684Stb mda_postfork(void)
378af8de4b6Sgilles {
3793ef9cbf7Sgilles }
3803ef9cbf7Sgilles
381aa1d5973Seric void
mda_postprivdrop(void)38204fee684Stb mda_postprivdrop(void)
3833ef9cbf7Sgilles {
38476d9cc75Seric tree_init(&sessions);
3857eed50e8Seric tree_init(&users);
386793ff23cSeric TAILQ_INIT(&runnable);
3873ef9cbf7Sgilles }
3883ef9cbf7Sgilles
389be925435Sgilles static void
mda_io(struct io * io,int evt,void * arg)390b556a8d3Seric mda_io(struct io *io, int evt, void *arg)
3913ef9cbf7Sgilles {
392b556a8d3Seric struct mda_session *s = arg;
393cec67479Ssunil char *ln = NULL;
394cec67479Ssunil size_t sz = 0;
395cec67479Ssunil ssize_t len;
396689fc259Sjacekm
39765c4fdfbSgilles log_trace(TRACE_IO, "mda: %p: %s %s", s, io_strevent(evt),
39865c4fdfbSgilles io_strio(io));
39989953979Sjacekm
400719ffb95Seric switch (evt) {
401719ffb95Seric case IO_LOWAT:
402719ffb95Seric
403719ffb95Seric /* done */
404299c4efeSeric done:
405719ffb95Seric if (s->datafp == NULL) {
406985341b1Seric log_debug("debug: mda: all data sent for session"
407985341b1Seric " %016"PRIx64 " evpid %016"PRIx64,
408985341b1Seric s->id, s->evp->id);
4098d3f7f0dSeric io_free(io);
4108d3f7f0dSeric s->io = NULL;
41189953979Sjacekm return;
41289953979Sjacekm }
41389953979Sjacekm
4148d3f7f0dSeric while (io_queued(s->io) < MDA_HIWAT) {
415cec67479Ssunil if ((len = getline(&ln, &sz, s->datafp)) == -1)
416719ffb95Seric break;
4178d3f7f0dSeric if (io_write(s->io, ln, len) == -1) {
418aa1d5973Seric m_create(p_parent, IMSG_MDA_KILL,
419299c4efeSeric 0, 0, -1);
42065c4fdfbSgilles m_add_id(p_parent, s->id);
42165c4fdfbSgilles m_add_string(p_parent, "Out of memory");
42265c4fdfbSgilles m_close(p_parent);
423f80a744fSeric io_pause(io, IO_OUT);
424cec67479Ssunil free(ln);
425719ffb95Seric return;
426719ffb95Seric }
427719ffb95Seric }
428719ffb95Seric
429cec67479Ssunil free(ln);
430a0d73992Sjsg ln = NULL;
431719ffb95Seric if (ferror(s->datafp)) {
432985341b1Seric log_debug("debug: mda: ferror on session %016"PRIx64,
433985341b1Seric s->id);
434aa1d5973Seric m_create(p_parent, IMSG_MDA_KILL, 0, 0, -1);
43565c4fdfbSgilles m_add_id(p_parent, s->id);
43665c4fdfbSgilles m_add_string(p_parent, "Error reading body");
43765c4fdfbSgilles m_close(p_parent);
438f80a744fSeric io_pause(io, IO_OUT);
439719ffb95Seric return;
440719ffb95Seric }
441719ffb95Seric
442719ffb95Seric if (feof(s->datafp)) {
443985341b1Seric log_debug("debug: mda: end-of-file for session"
444985341b1Seric " %016"PRIx64 " evpid %016"PRIx64,
445985341b1Seric s->id, s->evp->id);
446719ffb95Seric fclose(s->datafp);
447719ffb95Seric s->datafp = NULL;
4488d3f7f0dSeric if (io_queued(s->io) == 0)
449299c4efeSeric goto done;
450719ffb95Seric }
451719ffb95Seric return;
452719ffb95Seric
453719ffb95Seric case IO_TIMEOUT:
454985341b1Seric log_debug("debug: mda: timeout on session %016"PRIx64, s->id);
455f80a744fSeric io_pause(io, IO_OUT);
456719ffb95Seric return;
457719ffb95Seric
458719ffb95Seric case IO_ERROR:
459985341b1Seric log_debug("debug: mda: io error on session %016"PRIx64": %s",
460219e2fd6Seric s->id, io_error(io));
461f80a744fSeric io_pause(io, IO_OUT);
462719ffb95Seric return;
463719ffb95Seric
464719ffb95Seric case IO_DISCONNECTED:
465985341b1Seric log_debug("debug: mda: io disconnected on session %016"PRIx64,
466985341b1Seric s->id);
467f80a744fSeric io_pause(io, IO_OUT);
468719ffb95Seric return;
469719ffb95Seric
470719ffb95Seric default:
471985341b1Seric log_debug("debug: mda: unexpected event on session %016"PRIx64,
472985341b1Seric s->id);
473f80a744fSeric io_pause(io, IO_OUT);
474719ffb95Seric return;
475719ffb95Seric }
476689fc259Sjacekm }
477689fc259Sjacekm
478a7b2e833Seric static int
mda_check_loop(FILE * fp,struct mda_envelope * e)479985341b1Seric mda_check_loop(FILE *fp, struct mda_envelope *e)
480a7b2e833Seric {
481cec67479Ssunil char *buf = NULL;
482cec67479Ssunil size_t sz = 0;
483cec67479Ssunil ssize_t len;
484a7b2e833Seric int ret = 0;
485a7b2e833Seric
486cec67479Ssunil while ((len = getline(&buf, &sz, fp)) != -1) {
487a7b2e833Seric if (buf[len - 1] == '\n')
488a7b2e833Seric buf[len - 1] = '\0';
489a7b2e833Seric
490fc3a8311Seric if (strchr(buf, ':') == NULL && !isspace((unsigned char)*buf))
491a7b2e833Seric break;
492a7b2e833Seric
493a7b2e833Seric if (strncasecmp("Delivered-To: ", buf, 14) == 0) {
494985341b1Seric if (strcasecmp(buf + 14, e->dest) == 0) {
495a7b2e833Seric ret = 1;
496a7b2e833Seric break;
497a7b2e833Seric }
498a7b2e833Seric }
499a7b2e833Seric }
500a7b2e833Seric
501cec67479Ssunil free(buf);
502a7b2e833Seric fseek(fp, SEEK_SET, 0);
503a7b2e833Seric return (ret);
504a7b2e833Seric }
505dc65775cSeric
506719ffb95Seric static int
mda_getlastline(int fd,char * dst,size_t dstsz)507719ffb95Seric mda_getlastline(int fd, char *dst, size_t dstsz)
508719ffb95Seric {
509719ffb95Seric FILE *fp;
510cec67479Ssunil char *ln = NULL;
511cec67479Ssunil size_t sz = 0;
512cec67479Ssunil ssize_t len;
51340837cf2Sgilles int out = 0;
514719ffb95Seric
515df69c215Sderaadt if (lseek(fd, 0, SEEK_SET) == -1) {
51682614934Seric log_warn("warn: mda: lseek");
517719ffb95Seric close(fd);
518719ffb95Seric return (-1);
519719ffb95Seric }
520719ffb95Seric fp = fdopen(fd, "r");
521719ffb95Seric if (fp == NULL) {
52282614934Seric log_warn("warn: mda: fdopen");
523719ffb95Seric close(fd);
524719ffb95Seric return (-1);
525719ffb95Seric }
526cec67479Ssunil while ((len = getline(&ln, &sz, fp)) != -1) {
527719ffb95Seric if (ln[len - 1] == '\n')
528cec67479Ssunil ln[len - 1] = '\0';
52940837cf2Sgilles out = 1;
530719ffb95Seric }
531719ffb95Seric fclose(fp);
532719ffb95Seric
53340837cf2Sgilles if (out) {
534d124c6e2Sgilles (void)strlcpy(dst, "\"", dstsz);
535a9d5072aSgilles (void)strnvis(dst + 1, ln, dstsz - 2, VIS_SAFE | VIS_CSTYLE | VIS_NL);
536d124c6e2Sgilles (void)strlcat(dst, "\"", dstsz);
537719ffb95Seric }
538719ffb95Seric
539cec67479Ssunil free(ln);
540719ffb95Seric return (0);
541719ffb95Seric }
542719ffb95Seric
543793ff23cSeric static void
mda_fail(struct mda_user * user,int permfail,const char * error,enum enhanced_status_code code)54463b53c7eSsunil mda_fail(struct mda_user *user, int permfail, const char *error,
54563b53c7eSsunil enum enhanced_status_code code)
54665c4fdfbSgilles {
547985341b1Seric struct mda_envelope *e;
54865c4fdfbSgilles
54965c4fdfbSgilles while ((e = TAILQ_FIRST(&user->envelopes))) {
55065c4fdfbSgilles TAILQ_REMOVE(&user->envelopes, e, entry);
55165c4fdfbSgilles if (permfail) {
55265c4fdfbSgilles mda_log(e, "PermFail", error);
553aa1d5973Seric mda_queue_permfail(e->id, error, code);
55465c4fdfbSgilles }
55565c4fdfbSgilles else {
55665c4fdfbSgilles mda_log(e, "TempFail", error);
557aa1d5973Seric mda_queue_tempfail(e->id, error, code);
55865c4fdfbSgilles }
5597eed50e8Seric mda_envelope_free(e);
56065c4fdfbSgilles }
56165c4fdfbSgilles
5627eed50e8Seric mda_user_free(user);
56365c4fdfbSgilles }
56465c4fdfbSgilles
56565c4fdfbSgilles static void
mda_drain(void)566793ff23cSeric mda_drain(void)
567dc65775cSeric {
568985341b1Seric struct mda_user *u;
569dc65775cSeric
570985341b1Seric while ((u = (TAILQ_FIRST(&runnable)))) {
5717eed50e8Seric
572985341b1Seric TAILQ_REMOVE(&runnable, u, entry_runnable);
573985341b1Seric
574985341b1Seric if (u->evpcount == 0 && u->running == 0) {
575985341b1Seric log_debug("debug: mda: all done for user \"%s\"",
5767eed50e8Seric mda_user_to_text(u));
5777eed50e8Seric mda_user_free(u);
578985341b1Seric continue;
579985341b1Seric }
580985341b1Seric
581985341b1Seric if (u->evpcount == 0) {
582985341b1Seric log_debug("debug: mda: no more envelope for \"%s\"",
5837eed50e8Seric mda_user_to_text(u));
5847eed50e8Seric u->flags &= ~USER_RUNNABLE;
585985341b1Seric continue;
586985341b1Seric }
587985341b1Seric
5887eed50e8Seric if (u->running >= env->sc_mda_max_user_session) {
589985341b1Seric log_debug("debug: mda: "
590985341b1Seric "maximum number of session reached for user \"%s\"",
5917eed50e8Seric mda_user_to_text(u));
5927eed50e8Seric u->flags &= ~USER_RUNNABLE;
593985341b1Seric continue;
594985341b1Seric }
595793ff23cSeric
5967eed50e8Seric if (tree_count(&sessions) >= env->sc_mda_max_session) {
59765c4fdfbSgilles log_debug("debug: mda: "
59865c4fdfbSgilles "maximum number of session reached");
599985341b1Seric TAILQ_INSERT_HEAD(&runnable, u, entry_runnable);
600793ff23cSeric return;
601dc65775cSeric }
602dc65775cSeric
6037eed50e8Seric mda_session(u);
60465c4fdfbSgilles
6057eed50e8Seric if (u->evpcount == env->sc_mda_task_lowat) {
6067eed50e8Seric if (u->flags & USER_ONHOLD) {
60763b53c7eSsunil log_debug("debug: mda: down to lowat for user "
60863b53c7eSsunil "\"%s\": releasing",
6097eed50e8Seric mda_user_to_text(u));
6107eed50e8Seric u->flags &= ~USER_ONHOLD;
6117eed50e8Seric }
6127eed50e8Seric if (u->flags & USER_HOLDQ) {
61363b53c7eSsunil m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE,
61463b53c7eSsunil 0, 0, -1);
6157eed50e8Seric m_add_id(p_queue, u->id);
6167eed50e8Seric m_add_int(p_queue, env->sc_mda_task_release);
61765c4fdfbSgilles m_close(p_queue);
6187eed50e8Seric }
6197eed50e8Seric }
620793ff23cSeric
6217eed50e8Seric /* re-add the user at the tail of the queue */
622985341b1Seric TAILQ_INSERT_TAIL(&runnable, u, entry_runnable);
623793ff23cSeric }
624dc65775cSeric }
625dc65775cSeric
626dc65775cSeric static void
mda_done(struct mda_session * s)62765c4fdfbSgilles mda_done(struct mda_session *s)
628dc65775cSeric {
6297eed50e8Seric log_debug("debug: mda: session %016" PRIx64 " done", s->id);
630985341b1Seric
631793ff23cSeric tree_xpop(&sessions, s->id);
632dc65775cSeric
6337eed50e8Seric mda_envelope_free(s->evp);
634985341b1Seric
6357eed50e8Seric s->user->running--;
6367eed50e8Seric if (!(s->user->flags & USER_RUNNABLE)) {
6377eed50e8Seric log_debug("debug: mda: user \"%s\" becomes runnable",
6387eed50e8Seric s->user->name);
6397eed50e8Seric TAILQ_INSERT_TAIL(&runnable, s->user, entry_runnable);
6407eed50e8Seric s->user->flags |= USER_RUNNABLE;
6417eed50e8Seric }
642793ff23cSeric
643793ff23cSeric if (s->datafp)
644793ff23cSeric fclose(s->datafp);
6458d3f7f0dSeric if (s->io)
6468d3f7f0dSeric io_free(s->io);
647985341b1Seric
648793ff23cSeric free(s);
649985341b1Seric
6507eed50e8Seric stat_decrement("mda.running", 1);
651793ff23cSeric
652793ff23cSeric mda_drain();
653dc65775cSeric }
65465c4fdfbSgilles
65565c4fdfbSgilles static void
mda_log(const struct mda_envelope * evp,const char * prefix,const char * status)656985341b1Seric mda_log(const struct mda_envelope *evp, const char *prefix, const char *status)
65765c4fdfbSgilles {
658953aae25Sderaadt char rcpt[LINE_MAX];
65965c4fdfbSgilles
66065c4fdfbSgilles rcpt[0] = '\0';
661985341b1Seric if (evp->rcpt)
662ee64380fSgilles (void)snprintf(rcpt, sizeof rcpt, "rcpt=<%s> ", evp->rcpt);
66365c4fdfbSgilles
664cf540174Sgilles log_info("%016"PRIx64" mda delivery evpid=%016" PRIx64 " from=<%s> to=<%s> "
665a8e22235Sgilles "%suser=%s delay=%s result=%s stat=%s",
6661e265b4eSgilles evp->session_id,
667e55799c3Seric evp->id,
668985341b1Seric evp->sender ? evp->sender : "",
669985341b1Seric evp->dest,
67065c4fdfbSgilles rcpt,
671985341b1Seric evp->user,
67265c4fdfbSgilles duration_to_text(time(NULL) - evp->creation),
6737a93bdaaSgilles prefix,
67465c4fdfbSgilles status);
67565c4fdfbSgilles }
6767eed50e8Seric
677aa1d5973Seric static void
mda_queue_ok(uint64_t evpid)678aa1d5973Seric mda_queue_ok(uint64_t evpid)
679aa1d5973Seric {
680aa1d5973Seric m_create(p_queue, IMSG_MDA_DELIVERY_OK, 0, 0, -1);
681aa1d5973Seric m_add_evpid(p_queue, evpid);
682aa1d5973Seric m_close(p_queue);
683aa1d5973Seric }
684aa1d5973Seric
685aa1d5973Seric static void
mda_queue_tempfail(uint64_t evpid,const char * reason,enum enhanced_status_code code)68663b53c7eSsunil mda_queue_tempfail(uint64_t evpid, const char *reason,
68763b53c7eSsunil enum enhanced_status_code code)
688aa1d5973Seric {
689aa1d5973Seric m_create(p_queue, IMSG_MDA_DELIVERY_TEMPFAIL, 0, 0, -1);
690aa1d5973Seric m_add_evpid(p_queue, evpid);
691aa1d5973Seric m_add_string(p_queue, reason);
692aa1d5973Seric m_add_int(p_queue, (int)code);
693aa1d5973Seric m_close(p_queue);
694aa1d5973Seric }
695aa1d5973Seric
696aa1d5973Seric static void
mda_queue_permfail(uint64_t evpid,const char * reason,enum enhanced_status_code code)69763b53c7eSsunil mda_queue_permfail(uint64_t evpid, const char *reason,
69863b53c7eSsunil enum enhanced_status_code code)
699aa1d5973Seric {
700aa1d5973Seric m_create(p_queue, IMSG_MDA_DELIVERY_PERMFAIL, 0, 0, -1);
701aa1d5973Seric m_add_evpid(p_queue, evpid);
702aa1d5973Seric m_add_string(p_queue, reason);
703aa1d5973Seric m_add_int(p_queue, (int)code);
704aa1d5973Seric m_close(p_queue);
705aa1d5973Seric }
706aa1d5973Seric
707aa1d5973Seric static void
mda_queue_loop(uint64_t evpid)708aa1d5973Seric mda_queue_loop(uint64_t evpid)
709aa1d5973Seric {
710aa1d5973Seric m_create(p_queue, IMSG_MDA_DELIVERY_LOOP, 0, 0, -1);
711aa1d5973Seric m_add_evpid(p_queue, evpid);
712aa1d5973Seric m_close(p_queue);
713aa1d5973Seric }
714aa1d5973Seric
7157eed50e8Seric static struct mda_user *
mda_user(const struct envelope * evp)7167eed50e8Seric mda_user(const struct envelope *evp)
7177eed50e8Seric {
718a8e22235Sgilles struct dispatcher *dsp;
7197eed50e8Seric struct mda_user *u;
7207eed50e8Seric void *i;
7217eed50e8Seric
7227eed50e8Seric i = NULL;
723a8e22235Sgilles dsp = dict_xget(env->sc_dispatchers, evp->dispatcher);
7247eed50e8Seric while (tree_iter(&users, &i, NULL, (void**)(&u))) {
725a8e22235Sgilles if (!strcmp(evp->mda_user, u->name) &&
726a8e22235Sgilles !strcmp(dsp->u.local.table_userbase, u->usertable))
7277eed50e8Seric return (u);
7287eed50e8Seric }
7297eed50e8Seric
730118c16f3Sgilles u = xcalloc(1, sizeof *u);
7317eed50e8Seric u->id = generate_uid();
7327eed50e8Seric TAILQ_INIT(&u->envelopes);
733a8e22235Sgilles (void)strlcpy(u->name, evp->mda_user, sizeof(u->name));
734a8e22235Sgilles (void)strlcpy(u->usertable, dsp->u.local.table_userbase,
7357eed50e8Seric sizeof(u->usertable));
7367eed50e8Seric
7377eed50e8Seric tree_xset(&users, u->id, u);
7387eed50e8Seric
739aa1d5973Seric m_create(p_lka, IMSG_MDA_LOOKUP_USERINFO, 0, 0, -1);
7407eed50e8Seric m_add_id(p_lka, u->id);
741a8e22235Sgilles m_add_string(p_lka, dsp->u.local.table_userbase);
742a8e22235Sgilles m_add_string(p_lka, evp->mda_user);
7437eed50e8Seric m_close(p_lka);
7447eed50e8Seric u->flags |= USER_WAITINFO;
7457eed50e8Seric
7467eed50e8Seric stat_increment("mda.user", 1);
7477eed50e8Seric
748a8e22235Sgilles if (dsp->u.local.user)
74963b53c7eSsunil log_debug("mda: new user %016" PRIx64
75063b53c7eSsunil " for \"%s\" delivering as \"%s\"",
751a8e22235Sgilles u->id, mda_user_to_text(u), dsp->u.local.user);
7528e10b5b2Ssunil else
75363b53c7eSsunil log_debug("mda: new user %016" PRIx64
75463b53c7eSsunil " for \"%s\"", u->id, mda_user_to_text(u));
7557eed50e8Seric
7567eed50e8Seric return (u);
7577eed50e8Seric }
7587eed50e8Seric
7597eed50e8Seric static void
mda_user_free(struct mda_user * u)7607eed50e8Seric mda_user_free(struct mda_user *u)
7617eed50e8Seric {
7627eed50e8Seric tree_xpop(&users, u->id);
7637eed50e8Seric
7647eed50e8Seric if (u->flags & USER_HOLDQ) {
765aa1d5973Seric m_create(p_queue, IMSG_MDA_HOLDQ_RELEASE, 0, 0, -1);
7667eed50e8Seric m_add_id(p_queue, u->id);
7677eed50e8Seric m_add_int(p_queue, 0);
7687eed50e8Seric m_close(p_queue);
7697eed50e8Seric }
7707eed50e8Seric
7717eed50e8Seric free(u);
7727eed50e8Seric stat_decrement("mda.user", 1);
7737eed50e8Seric }
7747eed50e8Seric
7757eed50e8Seric static const char *
mda_user_to_text(const struct mda_user * u)7767eed50e8Seric mda_user_to_text(const struct mda_user *u)
7777eed50e8Seric {
7787eed50e8Seric static char buf[1024];
7797eed50e8Seric
78023c462a5Sgilles (void)snprintf(buf, sizeof(buf), "%s:%s", u->usertable, u->name);
7817eed50e8Seric
7827eed50e8Seric return (buf);
7837eed50e8Seric }
7847eed50e8Seric
7857eed50e8Seric static struct mda_envelope *
mda_envelope(uint64_t session_id,const struct envelope * evp)7861e265b4eSgilles mda_envelope(uint64_t session_id, const struct envelope *evp)
7877eed50e8Seric {
7887eed50e8Seric struct mda_envelope *e;
789953aae25Sderaadt char buf[LINE_MAX];
7907eed50e8Seric
791118c16f3Sgilles e = xcalloc(1, sizeof *e);
7921e265b4eSgilles e->session_id = session_id;
7937eed50e8Seric e->id = evp->id;
7947eed50e8Seric e->creation = evp->creation;
7957eed50e8Seric buf[0] = '\0';
7967eed50e8Seric if (evp->sender.user[0] && evp->sender.domain[0])
79723c462a5Sgilles (void)snprintf(buf, sizeof buf, "%s@%s",
7987eed50e8Seric evp->sender.user, evp->sender.domain);
799118c16f3Sgilles e->sender = xstrdup(buf);
80063b53c7eSsunil (void)snprintf(buf, sizeof buf, "%s@%s", evp->dest.user,
80163b53c7eSsunil evp->dest.domain);
802118c16f3Sgilles e->dest = xstrdup(buf);
80363b53c7eSsunil (void)snprintf(buf, sizeof buf, "%s@%s", evp->rcpt.user,
80463b53c7eSsunil evp->rcpt.domain);
805118c16f3Sgilles e->rcpt = xstrdup(buf);
806a8e22235Sgilles e->user = evp->mda_user[0] ?
807118c16f3Sgilles xstrdup(evp->mda_user) : xstrdup(evp->dest.user);
808118c16f3Sgilles e->dispatcher = xstrdup(evp->dispatcher);
809a8e22235Sgilles if (evp->mda_exec[0])
810118c16f3Sgilles e->mda_exec = xstrdup(evp->mda_exec);
811f67bab36Sgilles if (evp->mda_subaddress[0])
812f67bab36Sgilles e->mda_subaddress = xstrdup(evp->mda_subaddress);
8137eed50e8Seric stat_increment("mda.envelope", 1);
8147eed50e8Seric return (e);
8157eed50e8Seric }
8167eed50e8Seric
8177eed50e8Seric static void
mda_envelope_free(struct mda_envelope * e)8187eed50e8Seric mda_envelope_free(struct mda_envelope *e)
8197eed50e8Seric {
8207eed50e8Seric free(e->sender);
8217eed50e8Seric free(e->dest);
8227eed50e8Seric free(e->rcpt);
8237eed50e8Seric free(e->user);
824a8e22235Sgilles free(e->mda_exec);
8257eed50e8Seric free(e);
8267eed50e8Seric
8277eed50e8Seric stat_decrement("mda.envelope", 1);
8287eed50e8Seric }
8297eed50e8Seric
8307eed50e8Seric static struct mda_session *
mda_session(struct mda_user * u)8317eed50e8Seric mda_session(struct mda_user * u)
8327eed50e8Seric {
8337eed50e8Seric struct mda_session *s;
8347eed50e8Seric
835118c16f3Sgilles s = xcalloc(1, sizeof *s);
8367eed50e8Seric s->id = generate_uid();
8377eed50e8Seric s->user = u;
8388d3f7f0dSeric s->io = io_new();
8398d3f7f0dSeric io_set_callback(s->io, mda_io, s);
8407eed50e8Seric
8417eed50e8Seric tree_xset(&sessions, s->id, s);
8427eed50e8Seric
8437eed50e8Seric s->evp = TAILQ_FIRST(&u->envelopes);
8447eed50e8Seric TAILQ_REMOVE(&u->envelopes, s->evp, entry);
8457eed50e8Seric u->evpcount--;
8467eed50e8Seric u->running++;
8477eed50e8Seric
8487eed50e8Seric stat_decrement("mda.pending", 1);
8497eed50e8Seric stat_increment("mda.running", 1);
8507eed50e8Seric
8517eed50e8Seric log_debug("debug: mda: new session %016" PRIx64
8527eed50e8Seric " for user \"%s\" evpid %016" PRIx64, s->id,
8537eed50e8Seric mda_user_to_text(u), s->evp->id);
8547eed50e8Seric
855aa1d5973Seric m_create(p_queue, IMSG_MDA_OPEN_MESSAGE, 0, 0, -1);
8567eed50e8Seric m_add_id(p_queue, s->id);
8577eed50e8Seric m_add_msgid(p_queue, evpid_to_msgid(s->evp->id));
8587eed50e8Seric m_close(p_queue);
8597eed50e8Seric
8607eed50e8Seric return (s);
8617eed50e8Seric }
862eb268927Sgilles
863eb268927Sgilles static const char *
mda_sysexit_to_str(int sysexit)864eb268927Sgilles mda_sysexit_to_str(int sysexit)
865eb268927Sgilles {
866eb268927Sgilles switch (sysexit) {
867eb268927Sgilles case EX_USAGE:
868eb268927Sgilles return "command line usage error";
869eb268927Sgilles case EX_DATAERR:
870eb268927Sgilles return "data format error";
871eb268927Sgilles case EX_NOINPUT:
872eb268927Sgilles return "cannot open input";
873eb268927Sgilles case EX_NOUSER:
874eb268927Sgilles return "user unknown";
875eb268927Sgilles case EX_NOHOST:
876eb268927Sgilles return "host name unknown";
877eb268927Sgilles case EX_UNAVAILABLE:
878eb268927Sgilles return "service unavailable";
879eb268927Sgilles case EX_SOFTWARE:
880eb268927Sgilles return "internal software error";
881eb268927Sgilles case EX_OSERR:
882eb268927Sgilles return "system resource problem";
883eb268927Sgilles case EX_OSFILE:
884eb268927Sgilles return "critical OS file missing";
885eb268927Sgilles case EX_CANTCREAT:
886eb268927Sgilles return "can't create user output file";
887eb268927Sgilles case EX_IOERR:
888eb268927Sgilles return "input/output error";
889eb268927Sgilles case EX_TEMPFAIL:
890eb268927Sgilles return "temporary failure";
891eb268927Sgilles case EX_PROTOCOL:
892eb268927Sgilles return "remote error in protocol";
893eb268927Sgilles case EX_NOPERM:
894eb268927Sgilles return "permission denied";
895eb268927Sgilles case EX_CONFIG:
896eb268927Sgilles return "local configuration error";
897eb268927Sgilles default:
898eb268927Sgilles break;
899eb268927Sgilles }
900eb268927Sgilles return NULL;
901eb268927Sgilles }
902eb268927Sgilles
903