1 /*        $NetBSD: milter.c,v 1.5 2022/10/08 16:12:46 christos Exp $  */
2 
3 /*++
4 /* NAME
5 /*        milter 3
6 /* SUMMARY
7 /*        generic MTA-side mail filter interface
8 /* SYNOPSIS
9 /*        #include <milter.h>
10 /*
11 /*        MILTERS   *milter_create(milter_names, conn_timeout, cmd_timeout,
12 /*                                                msg_timeout, protocol, def_action,
13 /*                                                conn_macros, helo_macros,
14 /*                                                mail_macros, rcpt_macros,
15 /*                                                data_macros, eoh_macros,
16 /*                                                eod_macros, unk_macros,
17 /*                                                macro_deflts)
18 /*        const char *milter_names;
19 /*        int       conn_timeout;
20 /*        int       cmd_timeout;
21 /*        int       msg_timeout;
22 /*        const char *protocol;
23 /*        const char *def_action;
24 /*        const char *conn_macros;
25 /*        const char *helo_macros;
26 /*        const char *mail_macros;
27 /*        const char *rcpt_macrps;
28 /*        const char *data_macros;
29 /*        const char *eoh_macros;
30 /*        const char *eod_macros;
31 /*        const char *unk_macros;
32 /*        const char *macro_deflts;
33 /*
34 /*        void      milter_free(milters)
35 /*        MILTERS   *milters;
36 /*
37 /*        void      milter_macro_callback(milters, mac_lookup, mac_context)
38 /*        const char *(*mac_lookup)(const char *name, void *context);
39 /*        void      *mac_context;
40 /*
41 /*        void      milter_edit_callback(milters, add_header, upd_header,
42 /*                                                ins_header, del_header, chg_from,
43 /*                                                add_rcpt, add_rcpt_par, del_rcpt,
44 /*                                                repl_body, context)
45 /*        MILTERS   *milters;
46 /*        MILTER_ADD_HEADER_FN add_header;
47 /*        MILTER_EDIT_HEADER_FN upd_header;
48 /*        MILTER_EDIT_HEADER_FN ins_header;
49 /*        MILTER_DEL_HEADER_FN del_header;
50 /*        MILTER_EDIT_FROM_FN chg_from;
51 /*        MILTER_EDIT_RCPT_FN add_rcpt;
52 /*        MILTER_EDIT_RCPT_PAR_FN add_rcpt_par;
53 /*        MILTER_EDIT_RCPT_FN del_rcpt;
54 /*        MILTER_EDIT_BODY_FN repl_body;
55 /*        void      *context;
56 /*
57 /*        const char *milter_conn_event(milters, client_name, client_addr,
58 /*                                                client_port, addr_family)
59 /*        MILTERS   *milters;
60 /*        const char *client_name;
61 /*        const char *client_addr;
62 /*        const char *client_port;
63 /*        int       addr_family;
64 /*
65 /*        const char *milter_disc_event(milters)
66 /*        MILTERS   *milters;
67 /*
68 /*        const char *milter_helo_event(milters, helo_name, esmtp_flag)
69 /*        MILTERS   *milters;
70 /*        const char *helo_name;
71 /*        int       esmtp_flag;
72 /*
73 /*        const char *milter_mail_event(milters, argv)
74 /*        MILTERS   *milters;
75 /*        const char **argv;
76 /*
77 /*        const char *milter_rcpt_event(milters, flags, argv)
78 /*        MILTERS   *milters;
79 /*        int       flags;
80 /*        const char **argv;
81 /*
82 /*        const char *milter_data_event(milters)
83 /*        MILTERS   *milters;
84 /*
85 /*        const char *milter_unknown_event(milters, command)
86 /*        MILTERS   *milters;
87 /*        const char *command;
88 /*
89 /*        const char *milter_other_event(milters)
90 /*        MILTERS   *milters;
91 /*
92 /*        const char *milter_message(milters, qfile, data_offset, auto_hdrs)
93 /*        MILTERS   *milters;
94 /*        VSTREAM *qfile;
95 /*        off_t     data_offset;
96 /*        ARGV      *auto_hdrs;
97 /*
98 /*        const char *milter_abort(milters)
99 /*        MILTERS   *milters;
100 /*
101 /*        int       milter_send(milters, fp)
102 /*        MILTERS   *milters;
103 /*        VSTREAM *fp;
104 /*
105 /*        MILTERS   *milter_receive(fp, count)
106 /*        VSTREAM   *fp;
107 /*        int       count;
108 /*
109 /*        int       milter_dummy(milters, fp)
110 /*        MILTERS   *milters;
111 /*        VSTREAM *fp;
112 /* DESCRIPTION
113 /*        The functions in this module manage one or more milter (mail
114 /*        filter) clients. Currently, only the Sendmail 8 filter
115 /*        protocol is supported.
116 /*
117 /*        The functions that inspect content or envelope commands
118 /*        return either an SMTP reply ([45]XX followed by enhanced
119 /*        status code and text), "D" (discard), "H" (quarantine),
120 /*        "S" (shutdown connection), or a null pointer, which means
121 /*        "no news is good news".
122 /*
123 /*        milter_create() instantiates the milter clients specified
124 /*        with the milter_names argument.  The conn_macros etc.
125 /*        arguments specify the names of macros that are sent to the
126 /*        mail filter applications upon a connect etc. event, and the
127 /*        macro_deflts argument specifies macro defaults that will be used
128 /*        only if the application's lookup call-back returns null. This
129 /*        function should be called during process initialization,
130 /*        before entering a chroot jail. The timeout parameters specify
131 /*        time limits for the completion of the specified request
132 /*        classes. The protocol parameter specifies a protocol version
133 /*        and optional extensions.  When the milter application is
134 /*        unavailable, the milter client will go into a suitable error
135 /*        state as specified with the def_action parameter (i.e.
136 /*        reject, tempfail or accept all subsequent events).
137 /*
138 /*        milter_free() disconnects from the milter instances that
139 /*        are still opened, and destroys the data structures created
140 /*        by milter_create(). This function is safe to call at any
141 /*        point after milter_create().
142 /*
143 /*        milter_macro_callback() specifies a call-back function and
144 /*        context for macro lookup. This function must be called
145 /*        before milter_conn_event().
146 /*
147 /*        milter_edit_callback() specifies call-back functions and
148 /*        context for editing the queue file after the end-of-data
149 /*        is received. This function must be called before milter_message();
150 /*
151 /*        milter_conn_event() reports an SMTP client connection event
152 /*        to the specified milter instances, after sending the macros
153 /*        specified with the milter_create() conn_macros argument.
154 /*        This function must be called before reporting any other
155 /*        events.
156 /*
157 /*        milter_disc_event() reports an SMTP client disconnection
158 /*        event to the specified milter instances. No events can
159 /*        reported after this call. To simplify usage, redundant calls
160 /*        of this function are NO-OPs and don't raise a run-time
161 /*        error.
162 /*
163 /*        milter_helo_event() reports a HELO or EHLO event to the
164 /*        specified milter instances, after sending the macros that
165 /*        were specified with the milter_create() helo_macros argument.
166 /*
167 /*        milter_mail_event() reports a MAIL FROM event to the specified
168 /*        milter instances, after sending the macros that were specified
169 /*        with the milter_create() mail_macros argument.
170 /*
171 /*        milter_rcpt_event() reports an RCPT TO event to the specified
172 /*        milter instances, after sending the macros that were specified
173 /*        with the milter_create() rcpt_macros argument. The flags
174 /*        argument supports the following:
175 /* .IP MILTER_FLAG_WANT_RCPT_REJ
176 /*        When this flag is cleared, invoke all milters.  When this
177 /*        flag is set, invoke only milters that want to receive
178 /*        rejected recipients; with Sendmail V8 Milters, {rcpt_mailer}
179 /*        is set to "error", {rcpt_host} is set to an enhanced status
180 /*        code, and {rcpt_addr} is set to descriptive text.
181 /* .PP
182 /*        milter_data_event() reports a DATA event to the specified
183 /*        milter instances, after sending the macros that were specified
184 /*        with the milter_create() data_macros argument.
185 /*
186 /*        milter_unknown_event() reports an unknown command event to
187 /*        the specified milter instances, after sending the macros
188 /*        that were specified with the milter_create() unk_macros
189 /*        argument.
190 /*
191 /*        milter_other_event() returns the current default mail filter
192 /*        reply for the current SMTP connection state; it does not
193 /*        change milter states. A null pointer result means that all
194 /*        is well. This function can be used for SMTP commands such
195 /*        as AUTH, STARTTLS that don't have their own milter event
196 /*        routine.
197 /*
198 /*        milter_message() sends the message header and body to the
199 /*        to the specified milter instances, and sends the macros
200 /*        specified with the milter_create() eoh_macros after the
201 /*        message header, and with the eod_macros argument at
202 /*        the end.  Each milter sees the result of any changes made
203 /*        by a preceding milter. This function must be called with
204 /*        as argument an open Postfix queue file.
205 /*
206 /*        milter_abort() cancels a mail transaction in progress.  To
207 /*        simplify usage, redundant calls of this function are NO-OPs
208 /*        and don't raise a run-time error.
209 /*
210 /*        milter_send() sends a list of mail filters over the specified
211 /*        stream. When given a null list pointer, a "no filter"
212 /*        indication is sent.  The result is non-zero in case of
213 /*        error.
214 /*
215 /*        milter_receive() receives the specified number of mail
216 /*        filters over the specified stream. The result is a null
217 /*        pointer when no milters were sent, or when an error happened.
218 /*
219 /*        milter_dummy() is like milter_send(), except that it sends
220 /*        a dummy, but entirely valid, mail filter list.
221 /* SEE ALSO
222 /*        milter8(3) Sendmail 8 Milter protocol
223 /* DIAGNOSTICS
224 /*        Panic: interface violation.
225 /*        Fatal errors: memory allocation problem.
226 /* LICENSE
227 /* .ad
228 /* .fi
229 /*        The Secure Mailer license must be distributed with this software.
230 /* AUTHOR(S)
231 /*        Wietse Venema
232 /*        IBM T.J. Watson Research
233 /*        P.O. Box 704
234 /*        Yorktown Heights, NY 10598, USA
235 /*
236 /*        Wietse Venema
237 /*        Google, Inc.
238 /*        111 8th Avenue
239 /*        New York, NY 10011, USA
240 /*--*/
241 
242 /* System library. */
243 
244 #include <sys_defs.h>
245 
246 /* Utility library. */
247 
248 #include <msg.h>
249 #include <mymalloc.h>
250 #include <stringops.h>
251 #include <argv.h>
252 #include <attr.h>
253 #include <htable.h>
254 
255 /* Global library. */
256 
257 #include <mail_proto.h>
258 #include <record.h>
259 #include <rec_type.h>
260 #include <mail_params.h>
261 #include <attr_override.h>
262 
263 /* Postfix Milter library. */
264 
265 #include <milter.h>
266 
267 /* Application-specific. */
268 
269  /*
270   * SLMs.
271   */
272 #define STR(x)      vstring_str(x)
273 
274 /* milter_macro_defaults_create - parse default macro entries */
275 
milter_macro_defaults_create(const char * macro_defaults)276 HTABLE *milter_macro_defaults_create(const char *macro_defaults)
277 {
278     const char myname[] = "milter_macro_defaults_create";
279     char   *saved_defaults = mystrdup(macro_defaults);
280     char   *cp = saved_defaults;
281     HTABLE *table = 0;
282     VSTRING *canon_buf = 0;
283     char   *nameval;
284 
285     while ((nameval = mystrtokq(&cp, CHARS_COMMA_SP, CHARS_BRACE)) != 0) {
286           const char *err;
287           char   *name;
288           char   *value;
289 
290           /*
291            * Split the input into (name, value) pairs. Allow the forms
292            * name=value and  { name = value }, where the last form ignores
293            * whitespace after the opening "{", around the "=", and before the
294            * closing "}". A name may also be specified as {name}.
295            *
296            * Use the form {name} for table lookups, because that is the form of
297            * the S8_MAC_* macro names.
298            */
299           if (*nameval == CHARS_BRACE[0]
300               && nameval[balpar(nameval, CHARS_BRACE)] != '='
301               && (err = extpar(&nameval, CHARS_BRACE, EXTPAR_FLAG_NONE)) != 0)
302               msg_fatal("malformed default macro entry: %s in \"%s\"",
303                           err, macro_defaults);
304           if ((err = split_nameval(nameval, &name, &value)) != 0)
305               msg_fatal("malformed default macro entry: %s in \"%s\"",
306                           err, macro_defaults);
307           if (*name != '{')                       /* } */
308               name = STR(vstring_sprintf(canon_buf ? canon_buf :
309                                   (canon_buf = vstring_alloc(20)), "{%s}", name));
310           if (table == 0)
311               table = htable_create(1);
312           if (htable_find(table, name) != 0) {
313               msg_warn("ignoring multiple default macro entries for %s in \"%s\"",
314                          name, macro_defaults);
315           } else {
316               (void) htable_enter(table, name, mystrdup(value));
317               if (msg_verbose)
318                     msg_info("%s: add name=%s default=%s", myname, name, value);
319           }
320     }
321     myfree(saved_defaults);
322     if (canon_buf)
323           vstring_free(canon_buf);
324     return (table);
325 }
326 
327 /* milter_macro_lookup - look up macros */
328 
milter_macro_lookup(MILTERS * milters,const char * macro_names)329 static ARGV *milter_macro_lookup(MILTERS *milters, const char *macro_names)
330 {
331     const char *myname = "milter_macro_lookup";
332     char   *saved_names = mystrdup(macro_names);
333     char   *cp = saved_names;
334     ARGV   *argv = argv_alloc(10);
335     VSTRING *canon_buf = vstring_alloc(20);
336     const char *value;
337     const char *name;
338     const char *cname;
339 
340     while ((name = mystrtok(&cp, CHARS_COMMA_SP)) != 0) {
341           if (msg_verbose)
342               msg_info("%s: \"%s\"", myname, name);
343           if (*name != '{')                       /* } */
344               cname = STR(vstring_sprintf(canon_buf, "{%s}", name));
345           else
346               cname = name;
347           if ((value = milters->mac_lookup(cname, milters->mac_context)) != 0) {
348               if (msg_verbose)
349                     msg_info("%s: result \"%s\"", myname, value);
350               argv_add(argv, name, value, (char *) 0);
351           } else if (milters->macro_defaults != 0
352               && (value = htable_find(milters->macro_defaults, cname)) != 0) {
353               if (msg_verbose)
354                     msg_info("%s: using default \"%s\"", myname, value);
355               argv_add(argv, name, value, (char *) 0);
356           }
357     }
358     myfree(saved_names);
359     vstring_free(canon_buf);
360     return (argv);
361 }
362 
363 /* milter_macro_callback - specify macro lookup */
364 
milter_macro_callback(MILTERS * milters,const char * (* mac_lookup)(const char *,void *),void * mac_context)365 void    milter_macro_callback(MILTERS *milters,
366                                const char *(*mac_lookup) (const char *, void *),
367                                             void *mac_context)
368 {
369     milters->mac_lookup = mac_lookup;
370     milters->mac_context = mac_context;
371 }
372 
373 /* milter_edit_callback - specify queue file edit call-back information */
374 
milter_edit_callback(MILTERS * milters,MILTER_ADD_HEADER_FN add_header,MILTER_EDIT_HEADER_FN upd_header,MILTER_EDIT_HEADER_FN ins_header,MILTER_DEL_HEADER_FN del_header,MILTER_EDIT_FROM_FN chg_from,MILTER_EDIT_RCPT_FN add_rcpt,MILTER_EDIT_RCPT_PAR_FN add_rcpt_par,MILTER_EDIT_RCPT_FN del_rcpt,MILTER_EDIT_BODY_FN repl_body,void * chg_context)375 void    milter_edit_callback(MILTERS *milters,
376                                            MILTER_ADD_HEADER_FN add_header,
377                                            MILTER_EDIT_HEADER_FN upd_header,
378                                            MILTER_EDIT_HEADER_FN ins_header,
379                                            MILTER_DEL_HEADER_FN del_header,
380                                            MILTER_EDIT_FROM_FN chg_from,
381                                            MILTER_EDIT_RCPT_FN add_rcpt,
382                                            MILTER_EDIT_RCPT_PAR_FN add_rcpt_par,
383                                            MILTER_EDIT_RCPT_FN del_rcpt,
384                                            MILTER_EDIT_BODY_FN repl_body,
385                                            void *chg_context)
386 {
387     milters->add_header = add_header;
388     milters->upd_header = upd_header;
389     milters->ins_header = ins_header;
390     milters->del_header = del_header;
391     milters->chg_from = chg_from;
392     milters->add_rcpt = add_rcpt;
393     milters->add_rcpt_par = add_rcpt_par;
394     milters->del_rcpt = del_rcpt;
395     milters->repl_body = repl_body;
396     milters->chg_context = chg_context;
397 }
398 
399 /* milter_conn_event - report connect event */
400 
milter_conn_event(MILTERS * milters,const char * client_name,const char * client_addr,const char * client_port,unsigned addr_family)401 const char *milter_conn_event(MILTERS *milters,
402                                             const char *client_name,
403                                             const char *client_addr,
404                                             const char *client_port,
405                                             unsigned addr_family)
406 {
407     const char *resp;
408     MILTER *m;
409     ARGV   *global_macros = 0;
410     ARGV   *any_macros;
411 
412 #define MILTER_MACRO_EVAL(global_macros, m, milters, member) \
413           ((m->macros && m->macros->member[0]) ? \
414               milter_macro_lookup(milters, m->macros->member) : \
415                     global_macros ? global_macros : \
416                         (global_macros = \
417                              milter_macro_lookup(milters, milters->macros->member)))
418 
419     if (msg_verbose)
420           msg_info("report connect to all milters");
421     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
422           if (m->connect_on_demand != 0)
423               m->connect_on_demand(m);
424           any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, conn_macros);
425           resp = m->conn_event(m, client_name, client_addr, client_port,
426                                    addr_family, any_macros);
427           if (any_macros != global_macros)
428               argv_free(any_macros);
429     }
430     if (global_macros)
431           argv_free(global_macros);
432     return (resp);
433 }
434 
435 /* milter_helo_event - report helo event */
436 
milter_helo_event(MILTERS * milters,const char * helo_name,int esmtp_flag)437 const char *milter_helo_event(MILTERS *milters, const char *helo_name,
438                                             int esmtp_flag)
439 {
440     const char *resp;
441     MILTER *m;
442     ARGV   *global_macros = 0;
443     ARGV   *any_macros;
444 
445     if (msg_verbose)
446           msg_info("report helo to all milters");
447     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
448           any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, helo_macros);
449           resp = m->helo_event(m, helo_name, esmtp_flag, any_macros);
450           if (any_macros != global_macros)
451               argv_free(any_macros);
452     }
453     if (global_macros)
454           argv_free(global_macros);
455     return (resp);
456 }
457 
458 /* milter_mail_event - report mail from event */
459 
milter_mail_event(MILTERS * milters,const char ** argv)460 const char *milter_mail_event(MILTERS *milters, const char **argv)
461 {
462     const char *resp;
463     MILTER *m;
464     ARGV   *global_macros = 0;
465     ARGV   *any_macros;
466 
467     if (msg_verbose)
468           msg_info("report sender to all milters");
469     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
470           any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, mail_macros);
471           resp = m->mail_event(m, argv, any_macros);
472           if (any_macros != global_macros)
473               argv_free(any_macros);
474     }
475     if (global_macros)
476           argv_free(global_macros);
477     return (resp);
478 }
479 
480 /* milter_rcpt_event - report rcpt to event */
481 
milter_rcpt_event(MILTERS * milters,int flags,const char ** argv)482 const char *milter_rcpt_event(MILTERS *milters, int flags, const char **argv)
483 {
484     const char *resp;
485     MILTER *m;
486     ARGV   *global_macros = 0;
487     ARGV   *any_macros;
488 
489     if (msg_verbose)
490           msg_info("report recipient to all milters (flags=0x%x)", flags);
491     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
492           if ((flags & MILTER_FLAG_WANT_RCPT_REJ) == 0
493               || (m->flags & MILTER_FLAG_WANT_RCPT_REJ) != 0) {
494               any_macros =
495                     MILTER_MACRO_EVAL(global_macros, m, milters, rcpt_macros);
496               resp = m->rcpt_event(m, argv, any_macros);
497               if (any_macros != global_macros)
498                     argv_free(any_macros);
499           }
500     }
501     if (global_macros)
502           argv_free(global_macros);
503     return (resp);
504 }
505 
506 /* milter_data_event - report data event */
507 
milter_data_event(MILTERS * milters)508 const char *milter_data_event(MILTERS *milters)
509 {
510     const char *resp;
511     MILTER *m;
512     ARGV   *global_macros = 0;
513     ARGV   *any_macros;
514 
515     if (msg_verbose)
516           msg_info("report data to all milters");
517     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
518           any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, data_macros);
519           resp = m->data_event(m, any_macros);
520           if (any_macros != global_macros)
521               argv_free(any_macros);
522     }
523     if (global_macros)
524           argv_free(global_macros);
525     return (resp);
526 }
527 
528 /* milter_unknown_event - report unknown command */
529 
milter_unknown_event(MILTERS * milters,const char * command)530 const char *milter_unknown_event(MILTERS *milters, const char *command)
531 {
532     const char *resp;
533     MILTER *m;
534     ARGV   *global_macros = 0;
535     ARGV   *any_macros;
536 
537     if (msg_verbose)
538           msg_info("report unknown command to all milters");
539     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
540           any_macros = MILTER_MACRO_EVAL(global_macros, m, milters, unk_macros);
541           resp = m->unknown_event(m, command, any_macros);
542           if (any_macros != global_macros)
543               argv_free(any_macros);
544     }
545     if (global_macros)
546           argv_free(global_macros);
547     return (resp);
548 }
549 
550 /* milter_other_event - other SMTP event */
551 
milter_other_event(MILTERS * milters)552 const char *milter_other_event(MILTERS *milters)
553 {
554     const char *resp;
555     MILTER *m;
556 
557     if (msg_verbose)
558           msg_info("query milter states for other event");
559     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next)
560           resp = m->other_event(m);
561     return (resp);
562 }
563 
564 /* milter_message - inspect message content */
565 
milter_message(MILTERS * milters,VSTREAM * fp,off_t data_offset,ARGV * auto_hdrs)566 const char *milter_message(MILTERS *milters, VSTREAM *fp, off_t data_offset,
567                                          ARGV *auto_hdrs)
568 {
569     const char *resp;
570     MILTER *m;
571     ARGV   *global_eoh_macros = 0;
572     ARGV   *global_eod_macros = 0;
573     ARGV   *any_eoh_macros;
574     ARGV   *any_eod_macros;
575 
576     if (msg_verbose)
577           msg_info("inspect content by all milters");
578     for (resp = 0, m = milters->milter_list; resp == 0 && m != 0; m = m->next) {
579           any_eoh_macros = MILTER_MACRO_EVAL(global_eoh_macros, m, milters, eoh_macros);
580           any_eod_macros = MILTER_MACRO_EVAL(global_eod_macros, m, milters, eod_macros);
581           resp = m->message(m, fp, data_offset, any_eoh_macros, any_eod_macros,
582                                 auto_hdrs);
583           if (any_eoh_macros != global_eoh_macros)
584               argv_free(any_eoh_macros);
585           if (any_eod_macros != global_eod_macros)
586               argv_free(any_eod_macros);
587     }
588     if (global_eoh_macros)
589           argv_free(global_eoh_macros);
590     if (global_eod_macros)
591           argv_free(global_eod_macros);
592     return (resp);
593 }
594 
595 /* milter_abort - cancel message receiving state, all milters */
596 
milter_abort(MILTERS * milters)597 void    milter_abort(MILTERS *milters)
598 {
599     MILTER *m;
600 
601     if (msg_verbose)
602           msg_info("abort all milters");
603     for (m = milters->milter_list; m != 0; m = m->next)
604           m->abort(m);
605 }
606 
607 /* milter_disc_event - report client disconnect event to all milters */
608 
milter_disc_event(MILTERS * milters)609 void    milter_disc_event(MILTERS *milters)
610 {
611     MILTER *m;
612 
613     if (msg_verbose)
614           msg_info("disconnect event to all milters");
615     for (m = milters->milter_list; m != 0; m = m->next)
616           m->disc_event(m);
617 }
618 
619  /*
620   * Table-driven parsing of main.cf parameter overrides for specific Milters.
621   * We derive the override names from the corresponding main.cf parameter
622   * names by skipping the redundant "milter_" prefix.
623   */
624 static ATTR_OVER_TIME time_table[] = {
625     7 + (const char *) VAR_MILT_CONN_TIME, DEF_MILT_CONN_TIME, 0, 1, 0,
626     7 + (const char *) VAR_MILT_CMD_TIME, DEF_MILT_CMD_TIME, 0, 1, 0,
627     7 + (const char *) VAR_MILT_MSG_TIME, DEF_MILT_MSG_TIME, 0, 1, 0,
628     0,
629 };
630 static ATTR_OVER_STR str_table[] = {
631     7 + (const char *) VAR_MILT_PROTOCOL, 0, 1, 0,
632     7 + (const char *) VAR_MILT_DEF_ACTION, 0, 1, 0,
633     0,
634 };
635 
636 #define link_override_table_to_variable(table, var) \
637           do { table[var##_offset].target = &var; } while (0)
638 
639 #define my_conn_timeout_offset          0
640 #define my_cmd_timeout_offset 1
641 #define my_msg_timeout_offset 2
642 
643 #define   my_protocol_offset  0
644 #define   my_def_action_offset          1
645 
646 /* milter_new - create milter list */
647 
milter_new(const char * names,int conn_timeout,int cmd_timeout,int msg_timeout,const char * protocol,const char * def_action,MILTER_MACROS * macros,HTABLE * macro_defaults)648 MILTERS *milter_new(const char *names,
649                                 int conn_timeout,
650                                 int cmd_timeout,
651                                 int msg_timeout,
652                                 const char *protocol,
653                                 const char *def_action,
654                                 MILTER_MACROS *macros,
655                                 HTABLE *macro_defaults)
656 {
657     MILTERS *milters;
658     MILTER *head = 0;
659     MILTER *tail = 0;
660     char   *name;
661     MILTER *milter;
662     const char *sep = CHARS_COMMA_SP;
663     const char *parens = CHARS_BRACE;
664     int     my_conn_timeout;
665     int     my_cmd_timeout;
666     int     my_msg_timeout;
667     const char *my_protocol;
668     const char *my_def_action;
669 
670     /*
671      * Initialize.
672      */
673     link_override_table_to_variable(time_table, my_conn_timeout);
674     link_override_table_to_variable(time_table, my_cmd_timeout);
675     link_override_table_to_variable(time_table, my_msg_timeout);
676     link_override_table_to_variable(str_table, my_protocol);
677     link_override_table_to_variable(str_table, my_def_action);
678 
679     /*
680      * Parse the milter list.
681      */
682     milters = (MILTERS *) mymalloc(sizeof(*milters));
683     if (names != 0 && *names != 0) {
684           char   *saved_names = mystrdup(names);
685           char   *cp = saved_names;
686           char   *op;
687           char   *err;
688 
689           /*
690            * Instantiate Milters, allowing for per-Milter overrides.
691            */
692           while ((name = mystrtokq(&cp, sep, parens)) != 0) {
693               my_conn_timeout = conn_timeout;
694               my_cmd_timeout = cmd_timeout;
695               my_msg_timeout = msg_timeout;
696               my_protocol = protocol;
697               my_def_action = def_action;
698               if (name[0] == parens[0]) {
699                     op = name;
700                     if ((err = extpar(&op, parens, EXTPAR_FLAG_NONE)) != 0)
701                         msg_fatal("milter service syntax error: %s", err);
702                     if ((name = mystrtok(&op, sep)) == 0)
703                         msg_fatal("empty milter definition: \"%s\"", names);
704                     attr_override(op, sep, parens,
705                                     CA_ATTR_OVER_STR_TABLE(str_table),
706                                     CA_ATTR_OVER_TIME_TABLE(time_table),
707                                     CA_ATTR_OVER_END);
708               }
709               milter = milter8_create(name, my_conn_timeout, my_cmd_timeout,
710                                             my_msg_timeout, my_protocol,
711                                             my_def_action, milters);
712               if (head == 0) {
713                     head = milter;
714               } else {
715                     tail->next = milter;
716               }
717               tail = milter;
718           }
719           myfree(saved_names);
720     }
721     milters->milter_list = head;
722     milters->mac_lookup = 0;
723     milters->mac_context = 0;
724     milters->macros = macros;
725     milters->macro_defaults = macro_defaults;
726     milters->add_header = 0;
727     milters->upd_header = milters->ins_header = 0;
728     milters->del_header = 0;
729     milters->add_rcpt = milters->del_rcpt = 0;
730     milters->repl_body = 0;
731     milters->chg_context = 0;
732     return (milters);
733 }
734 
735 /* milter_free - destroy all milters */
736 
milter_free(MILTERS * milters)737 void    milter_free(MILTERS *milters)
738 {
739     MILTER *m;
740     MILTER *next;
741 
742     if (msg_verbose)
743           msg_info("free all milters");
744     for (m = milters->milter_list; m != 0; m = next)
745           next = m->next, m->free(m);
746     if (milters->macros)
747           milter_macros_free(milters->macros);
748     if (milters->macro_defaults)
749           htable_free(milters->macro_defaults, myfree);
750     myfree((void *) milters);
751 }
752 
753 /* milter_dummy - send empty milter list */
754 
milter_dummy(MILTERS * milters,VSTREAM * stream)755 int     milter_dummy(MILTERS *milters, VSTREAM *stream)
756 {
757     MILTERS dummy = *milters;
758 
759     dummy.milter_list = 0;
760     return (milter_send(&dummy, stream));
761 }
762 
763 /* milter_send - send Milter instances over stream */
764 
milter_send(MILTERS * milters,VSTREAM * stream)765 int     milter_send(MILTERS *milters, VSTREAM *stream)
766 {
767     MILTER *m;
768     int     status = 0;
769     int     count = 0;
770 
771     /*
772      * XXX Optimization: send only the filters that are actually used in the
773      * remote process. No point sending a filter that looks at HELO commands
774      * to a cleanup server. For now we skip only the filters that are known
775      * to be disabled (either in global error state or in global accept
776      * state).
777      *
778      * XXX We must send *some* information, even when there are no active
779      * filters, otherwise the cleanup server would try to apply its own
780      * non_smtpd_milters settings.
781      */
782     if (milters != 0)
783           for (m = milters->milter_list; m != 0; m = m->next)
784               if (m->active(m))
785                     count++;
786     (void) rec_fprintf(stream, REC_TYPE_MILT_COUNT, "%d", count);
787 
788     if (msg_verbose)
789           msg_info("send %d milters", count);
790 
791     /*
792      * XXX Optimization: don't send or receive further information when there
793      * aren't any active filters.
794      */
795     if (count <= 0)
796           return (0);
797 
798     /*
799      * Send the filter macro name lists.
800      */
801     (void) attr_print(stream, ATTR_FLAG_MORE,
802                           SEND_ATTR_FUNC(milter_macros_print,
803                                              (const void *) milters->macros),
804                           ATTR_TYPE_END);
805 
806     /*
807      * Send the filter macro defaults.
808      */
809     count = milters->macro_defaults ? milters->macro_defaults->used : 0;
810     (void) attr_print(stream, ATTR_FLAG_MORE,
811                           SEND_ATTR_INT(MAIL_ATTR_SIZE, count),
812                           ATTR_TYPE_END);
813     if (count > 0)
814           (void) attr_print(stream, ATTR_FLAG_MORE,
815                                 SEND_ATTR_HASH(milters->macro_defaults),
816                                 ATTR_TYPE_END);
817 
818     /*
819      * Send the filter instances.
820      */
821     for (m = milters->milter_list; m != 0; m = m->next)
822           if (m->active(m) && (status = m->send(m, stream)) != 0)
823               break;
824 
825     /*
826      * Over to you.
827      */
828     if (status != 0
829           || attr_scan(stream, ATTR_FLAG_STRICT,
830                          RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
831                          ATTR_TYPE_END) != 1
832           || status != 0) {
833           msg_warn("cannot send milters to service %s", VSTREAM_PATH(stream));
834           return (-1);
835     }
836     return (0);
837 }
838 
839 /* milter_receive - receive milters from stream */
840 
milter_receive(VSTREAM * stream,int count)841 MILTERS *milter_receive(VSTREAM *stream, int count)
842 {
843     MILTERS *milters;
844     MILTER *head = 0;
845     MILTER *tail = 0;
846     MILTER *milter = 0;
847     int     macro_default_count;
848 
849     if (msg_verbose)
850           msg_info("receive %d milters", count);
851 
852     /*
853      * XXX We must instantiate a MILTERS structure even when the sender has
854      * no active filters, otherwise the cleanup server would try to use its
855      * own non_smtpd_milters settings.
856      */
857 #define NO_MILTERS  ((char *) 0)
858 #define NO_TIMEOUTS 0, 0, 0
859 #define NO_PROTOCOL ((char *) 0)
860 #define NO_ACTION   ((char *) 0)
861 #define NO_MACROS   ((MILTER_MACROS *) 0)
862 #define NO_MACRO_DEFLTS       ((HTABLE *) 0)
863 
864     milters = milter_new(NO_MILTERS, NO_TIMEOUTS, NO_PROTOCOL, NO_ACTION,
865                                NO_MACROS, NO_MACRO_DEFLTS);
866 
867     /*
868      * XXX Optimization: don't send or receive further information when there
869      * aren't any active filters.
870      */
871     if (count <= 0)
872           return (milters);
873 
874     /*
875      * Receive the global macro name lists.
876      */
877     milters->macros = milter_macros_alloc(MILTER_MACROS_ALLOC_ZERO);
878     if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
879                       RECV_ATTR_FUNC(milter_macros_scan,
880                                          (void *) milters->macros),
881                       ATTR_TYPE_END) != 1) {
882           milter_free(milters);
883           return (0);
884     }
885 
886     /*
887      * Receive the filter macro defaults.
888      */
889     if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
890                       RECV_ATTR_INT(MAIL_ATTR_SIZE, &macro_default_count),
891                       ATTR_TYPE_END) != 1
892           || (macro_default_count > 0
893               && attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE,
894                                RECV_ATTR_HASH(milters->macro_defaults
895                                                   = htable_create(1)),
896                                ATTR_TYPE_END) != macro_default_count)) {
897           milter_free(milters);
898           return (0);
899     }
900 
901     /*
902      * Receive the filters.
903      */
904     for (; count > 0; count--) {
905           if ((milter = milter8_receive(stream, milters)) == 0) {
906               msg_warn("cannot receive milters via service %s socket",
907                          VSTREAM_PATH(stream));
908               milter_free(milters);
909               return (0);
910           }
911           if (head == 0) {
912               /* Coverity: milter_free() depends on milters->milter_list. */
913               milters->milter_list = head = milter;
914           } else {
915               tail->next = milter;
916           }
917           tail = milter;
918     }
919 
920     /*
921      * Over to you.
922      */
923     (void) attr_print(stream, ATTR_FLAG_NONE,
924                           SEND_ATTR_INT(MAIL_ATTR_STATUS, 0),
925                           ATTR_TYPE_END);
926     return (milters);
927 }
928 
929 #ifdef TEST
930 
931  /*
932   * Proof-of-concept test program. This can be used interactively, but is
933   * typically used for automated regression tests from a script.
934   */
935 
936 /* System library. */
937 
938 #include <sys/socket.h>
939 #include <stdlib.h>
940 #include <string.h>
941 
942 /* Utility library. */
943 
944 #include "msg_vstream.h"
945 #include "vstring_vstream.h"
946 
947 /* Global library. */
948 
949 #include <mail_params.h>
950 
951 int     var_milt_conn_time = 10;
952 int     var_milt_cmd_time = 10;
953 int     var_milt_msg_time = 100;
954 char   *var_milt_protocol = DEF_MILT_PROTOCOL;
955 char   *var_milt_def_action = DEF_MILT_DEF_ACTION;
956 
usage(void)957 static void usage(void)
958 {
959     vstream_fprintf(VSTREAM_ERR, "usage: \n"
960                         "     create names...               create and connect\n"
961 #if 0
962                         "     conn_macros names...          define connect macros\n"
963                         "     helo_macros names...          define helo command macros\n"
964                         "     mail_macros names...          define mail command macros\n"
965                         "     rcpt_macros names...          define rcpt command macros\n"
966                         "     data_macros names...          define data command macros\n"
967                         "     unk_macros names... unknown command macros\n"
968                         "     message_macros names...       define message macros\n"
969 #endif
970                         "     free                          disconnect and destroy\n"
971                         "     connect name addr port family\n"
972                         "     helo hostname\n"
973                         "     ehlo hostname\n"
974                         "     mail from sender...\n"
975                         "     rcpt to recipient...\n"
976                         "     data\n"
977                         "     disconnect\n"
978                         "     unknown command\n");
979     vstream_fflush(VSTREAM_ERR);
980 }
981 
main(int argc,char ** argv)982 int     main(int argc, char **argv)
983 {
984     MILTERS *milters = 0;
985     char   *conn_macros, *helo_macros, *mail_macros, *rcpt_macros;
986     char   *data_macros, *eoh_macros, *eod_macros, *unk_macros;
987     char   *macro_deflts;
988     VSTRING *inbuf = vstring_alloc(100);
989     char   *bufp;
990     char   *cmd;
991     int     ch;
992     int     istty = isatty(vstream_fileno(VSTREAM_IN));
993 
994     conn_macros = helo_macros = mail_macros = rcpt_macros = data_macros
995           = eoh_macros = eod_macros = unk_macros = macro_deflts = "";
996 
997     msg_vstream_init(argv[0], VSTREAM_ERR);
998     while ((ch = GETOPT(argc, argv, "V:v")) > 0) {
999           switch (ch) {
1000           default:
1001               msg_fatal("usage: %s [-a action] [-p protocol] [-v]", argv[0]);
1002           case 'a':
1003               var_milt_def_action = optarg;
1004               break;
1005           case 'p':
1006               var_milt_protocol = optarg;
1007               break;
1008           case 'v':
1009               msg_verbose++;
1010               break;
1011           }
1012     }
1013     optind = OPTIND;
1014 
1015     for (;;) {
1016           const char *resp = 0;
1017           ARGV   *argv;
1018           char  **args;
1019 
1020           if (istty) {
1021               vstream_printf("- ");
1022               vstream_fflush(VSTREAM_OUT);
1023           }
1024           if (vstring_fgets_nonl(inbuf, VSTREAM_IN) <= 0)
1025               break;
1026           bufp = vstring_str(inbuf);
1027           if (!istty) {
1028               vstream_printf("> %s\n", bufp);
1029               vstream_fflush(VSTREAM_OUT);
1030           }
1031           if (*bufp == '#')
1032               continue;
1033           cmd = mystrtok(&bufp, " ");
1034           if (cmd == 0) {
1035               usage();
1036               continue;
1037           }
1038           argv = argv_split(bufp, " ");
1039           args = argv->argv;
1040           if (strcmp(cmd, "create") == 0 && argv->argc == 1) {
1041               if (milters != 0) {
1042                     msg_warn("deleting existing milters");
1043                     milter_free(milters);
1044               }
1045               milters = milter_create(args[0], var_milt_conn_time,
1046                                             var_milt_cmd_time, var_milt_msg_time,
1047                                             var_milt_protocol, var_milt_def_action,
1048                                             conn_macros, helo_macros, mail_macros,
1049                                             rcpt_macros, data_macros, eoh_macros,
1050                                             eod_macros, unk_macros, macro_deflts);
1051           } else if (strcmp(cmd, "free") == 0 && argv->argc == 0) {
1052               if (milters == 0) {
1053                     msg_warn("no milters");
1054                     continue;
1055               }
1056               milter_free(milters);
1057               milters = 0;
1058           } else if (strcmp(cmd, "connect") == 0 && argv->argc == 4) {
1059               if (milters == 0) {
1060                     msg_warn("no milters");
1061                     continue;
1062               }
1063               resp = milter_conn_event(milters, args[0], args[1], args[2],
1064                                          strcmp(args[3], "AF_INET") == 0 ? AF_INET :
1065                                      strcmp(args[3], "AF_INET6") == 0 ? AF_INET6 :
1066                                          strcmp(args[3], "AF_UNIX") == 0 ? AF_UNIX :
1067                                              AF_UNSPEC);
1068           } else if (strcmp(cmd, "helo") == 0 && argv->argc == 1) {
1069               if (milters == 0) {
1070                     msg_warn("no milters");
1071                     continue;
1072               }
1073               resp = milter_helo_event(milters, args[0], 0);
1074           } else if (strcmp(cmd, "ehlo") == 0 && argv->argc == 1) {
1075               if (milters == 0) {
1076                     msg_warn("no milters");
1077                     continue;
1078               }
1079               resp = milter_helo_event(milters, args[0], 1);
1080           } else if (strcmp(cmd, "mail") == 0 && argv->argc > 0) {
1081               if (milters == 0) {
1082                     msg_warn("no milters");
1083                     continue;
1084               }
1085               resp = milter_mail_event(milters, (const char **) args);
1086           } else if (strcmp(cmd, "rcpt") == 0 && argv->argc > 0) {
1087               if (milters == 0) {
1088                     msg_warn("no milters");
1089                     continue;
1090               }
1091               resp = milter_rcpt_event(milters, 0, (const char **) args);
1092           } else if (strcmp(cmd, "unknown") == 0 && argv->argc > 0) {
1093               if (milters == 0) {
1094                     msg_warn("no milters");
1095                     continue;
1096               }
1097               resp = milter_unknown_event(milters, args[0]);
1098           } else if (strcmp(cmd, "data") == 0 && argv->argc == 0) {
1099               if (milters == 0) {
1100                     msg_warn("no milters");
1101                     continue;
1102               }
1103               resp = milter_data_event(milters);
1104           } else if (strcmp(cmd, "disconnect") == 0 && argv->argc == 0) {
1105               if (milters == 0) {
1106                     msg_warn("no milters");
1107                     continue;
1108               }
1109               milter_disc_event(milters);
1110           } else {
1111               usage();
1112           }
1113           if (resp != 0)
1114               msg_info("%s", resp);
1115           argv_free(argv);
1116     }
1117     if (milters != 0)
1118           milter_free(milters);
1119     vstring_free(inbuf);
1120     return (0);
1121 }
1122 
1123 #endif
1124