1 /*        $NetBSD: resolve_clnt.c,v 1.4 2022/10/08 16:12:45 christos Exp $      */
2 
3 /*++
4 /* NAME
5 /*        resolve_clnt 3
6 /* SUMMARY
7 /*        address resolve service client (internal forms)
8 /* SYNOPSIS
9 /*        #include <resolve_clnt.h>
10 /*
11 /*        typedef struct {
12 /* .in +4
13 /*                  VSTRING *transport;
14 /*                  VSTRING *nexthop
15 /*                  VSTRING *recipient;
16 /*                  int       flags;
17 /* .in -4
18 /*        } RESOLVE_REPLY;
19 /*
20 /*        void      resolve_clnt_init(reply)
21 /*        RESOLVE_REPLY *reply;
22 /*
23 /*        void      resolve_clnt_query_from(sender, address, reply)
24 /*        const char *sender;
25 /*        const char *address;
26 /*        RESOLVE_REPLY *reply;
27 /*
28 /*        void      resolve_clnt_verify_from(sender, address, reply)
29 /*        const char *sender;
30 /*        const char *address;
31 /*        RESOLVE_REPLY *reply;
32 /*
33 /*        void      resolve_clnt_free(reply)
34 /*        RESOLVE_REPLY *reply;
35 /* DESCRIPTION
36 /*        This module implements a mail address resolver client.
37 /*
38 /*        resolve_clnt_init() initializes a reply data structure for use
39 /*        by resolve_clnt_query(). The structure is destroyed by passing
40 /*        it to resolve_clnt_free().
41 /*
42 /*        resolve_clnt_query_from() sends an internal-form recipient address
43 /*        (user@domain) to the resolver daemon and returns the resulting
44 /*        transport name, next_hop host name, and internal-form recipient
45 /*        address. In case of communication failure the program keeps trying
46 /*        until the mail system goes down. The internal-form sender
47 /*        information is used for sender-dependent relayhost lookup.
48 /*        Specify RESOLVE_NULL_FROM when the sender is unavailable.
49 /*
50 /*        resolve_clnt_verify_from() implements an alternative version that can
51 /*        be used for address verification.
52 /*
53 /*        In the resolver reply, the flags member is the bit-wise OR of
54 /*        zero or more of the following:
55 /* .IP RESOLVE_FLAG_FINAL
56 /*        The recipient address resolves to a mail transport that performs
57 /*        final delivery. The destination is local or corresponds to a hosted
58 /*        domain that is handled by the local machine. This flag is currently
59 /*        not used.
60 /* .IP RESOLVE_FLAG_ROUTED
61 /*        After address resolution the recipient localpart contains further
62 /*        routing information, so the resolved next-hop destination is not
63 /*        the final destination.
64 /* .IP RESOLVE_FLAG_ERROR
65 /*        The address resolved to something that has invalid syntax.
66 /* .IP RESOLVE_FLAG_FAIL
67 /*        The request could not be completed.
68 /* .PP
69 /*        In addition, the address domain class is returned by setting
70 /*        one of the following flags (this is preliminary code awaiting
71 /*        more permanent implementation of address domain class handling):
72 /* .IP RESOLVE_CLASS_LOCAL
73 /*        The address domain matches $mydestination, $inet_interfaces
74 /*        or $proxy_interfaces.
75 /* .IP RESOLVE_CLASS_ALIAS
76 /*        The address domain matches $virtual_alias_domains (virtual
77 /*        alias domains, where each address is redirected to a real
78 /*        local or remote address).
79 /* .IP RESOLVE_CLASS_VIRTUAL
80 /*        The address domain matches $virtual_mailbox_domains (true
81 /*        virtual domains where each address can have its own mailbox).
82 /* .IP RESOLVE_CLASS_RELAY
83 /*        The address domain matches $relay_domains, i.e. this is an
84 /*        authorized mail relay destination.
85 /* .IP RESOLVE_CLASS_DEFAULT
86 /*        The address matches none of the above. Access to this domain
87 /*        should be limited to authorized senders only.
88 /* .PP
89 /*        For convenience, the constant RESOLVE_CLASS_FINAL includes all
90 /*        cases where the local machine is the final destination.
91 /* DIAGNOSTICS
92 /*        Warnings: communication failure. Fatal error: mail system is down.
93 /* SEE ALSO
94 /*        mail_proto(3h) low-level mail component glue.
95 /* LICENSE
96 /* .ad
97 /* .fi
98 /*        The Secure Mailer license must be distributed with this software.
99 /* AUTHOR(S)
100 /*        Wietse Venema
101 /*        IBM T.J. Watson Research
102 /*        P.O. Box 704
103 /*        Yorktown Heights, NY 10598, USA
104 /*
105 /*        Wietse Venema
106 /*        Google, Inc.
107 /*        111 8th Avenue
108 /*        New York, NY 10011, USA
109 /*--*/
110 
111 /* System library. */
112 
113 #include <sys_defs.h>
114 #include <unistd.h>
115 #include <string.h>
116 #include <errno.h>
117 
118 /* Utility library. */
119 
120 #include <msg.h>
121 #include <vstream.h>
122 #include <vstring.h>
123 #include <vstring_vstream.h>
124 #include <events.h>
125 #include <iostuff.h>
126 
127 /* Global library. */
128 
129 #include "mail_proto.h"
130 #include "mail_params.h"
131 #include "clnt_stream.h"
132 #include "resolve_clnt.h"
133 
134 /* Application-specific. */
135 
136  /*
137   * XXX this is shared with the rewrite client to save a file descriptor.
138   */
139 extern CLNT_STREAM *rewrite_clnt_stream;
140 
141 static time_t last_expire;
142 static VSTRING *last_class;
143 static VSTRING *last_sender;
144 static VSTRING *last_addr;
145 static RESOLVE_REPLY last_reply;
146 
147 /* resolve_clnt_init - initialize reply */
148 
resolve_clnt_init(RESOLVE_REPLY * reply)149 void    resolve_clnt_init(RESOLVE_REPLY *reply)
150 {
151     reply->transport = vstring_alloc(100);
152     reply->nexthop = vstring_alloc(100);
153     reply->recipient = vstring_alloc(100);
154     reply->flags = 0;
155 }
156 
157 /* resolve_clnt_handshake - receive server protocol announcement */
158 
resolve_clnt_handshake(VSTREAM * stream)159 static int resolve_clnt_handshake(VSTREAM *stream)
160 {
161     return (attr_scan(stream, ATTR_FLAG_STRICT,
162                       RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_TRIVIAL),
163                           ATTR_TYPE_END));
164 }
165 
166 /* resolve_clnt - resolve address to (transport, next hop, recipient) */
167 
resolve_clnt(const char * class,const char * sender,const char * addr,RESOLVE_REPLY * reply)168 void    resolve_clnt(const char *class, const char *sender,
169                                  const char *addr, RESOLVE_REPLY *reply)
170 {
171     const char *myname = "resolve_clnt";
172     VSTREAM *stream;
173     int     server_flags;
174     int     count = 0;
175 
176     /*
177      * One-entry cache.
178      */
179     if (last_addr == 0) {
180           last_class = vstring_alloc(10);
181           last_sender = vstring_alloc(10);
182           last_addr = vstring_alloc(100);
183           resolve_clnt_init(&last_reply);
184     }
185 
186     /*
187      * Sanity check. The result must not clobber the input because we may
188      * have to retransmit the request.
189      */
190 #define STR vstring_str
191 
192     if (addr == STR(reply->recipient))
193           msg_panic("%s: result clobbers input", myname);
194 
195     /*
196      * Peek at the cache.
197      */
198 #define IFSET(flag, text) ((reply->flags & (flag)) ? (text) : "")
199 
200     if (time((time_t *) 0) < last_expire
201           && *addr && strcmp(addr, STR(last_addr)) == 0
202           && strcmp(class, STR(last_class)) == 0
203           && strcmp(sender, STR(last_sender)) == 0) {
204           vstring_strcpy(reply->transport, STR(last_reply.transport));
205           vstring_strcpy(reply->nexthop, STR(last_reply.nexthop));
206           vstring_strcpy(reply->recipient, STR(last_reply.recipient));
207           reply->flags = last_reply.flags;
208           if (msg_verbose)
209               msg_info("%s: cached: `%s' -> `%s' -> transp=`%s' host=`%s' rcpt=`%s' flags=%s%s%s%s class=%s%s%s%s%s",
210                          myname, sender, addr, STR(reply->transport),
211                          STR(reply->nexthop), STR(reply->recipient),
212                          IFSET(RESOLVE_FLAG_FINAL, "final"),
213                          IFSET(RESOLVE_FLAG_ROUTED, "routed"),
214                          IFSET(RESOLVE_FLAG_ERROR, "error"),
215                          IFSET(RESOLVE_FLAG_FAIL, "fail"),
216                          IFSET(RESOLVE_CLASS_LOCAL, "local"),
217                          IFSET(RESOLVE_CLASS_ALIAS, "alias"),
218                          IFSET(RESOLVE_CLASS_VIRTUAL, "virtual"),
219                          IFSET(RESOLVE_CLASS_RELAY, "relay"),
220                          IFSET(RESOLVE_CLASS_DEFAULT, "default"));
221           return;
222     }
223 
224     /*
225      * Keep trying until we get a complete response. The resolve service is
226      * CPU bound; making the client asynchronous would just complicate the
227      * code.
228      */
229     if (rewrite_clnt_stream == 0)
230           rewrite_clnt_stream = clnt_stream_create(MAIL_CLASS_PRIVATE,
231                                                              var_rewrite_service,
232                                                              var_ipc_idle_limit,
233                                                              var_ipc_ttl_limit,
234                                                              resolve_clnt_handshake);
235 
236     for (;;) {
237           stream = clnt_stream_access(rewrite_clnt_stream);
238           errno = 0;
239           count += 1;
240           if (stream == 0
241               || attr_print(stream, ATTR_FLAG_NONE,
242                                 SEND_ATTR_STR(MAIL_ATTR_REQ, class),
243                                 SEND_ATTR_STR(MAIL_ATTR_SENDER, sender),
244                                 SEND_ATTR_STR(MAIL_ATTR_ADDR, addr),
245                                 ATTR_TYPE_END) != 0
246               || vstream_fflush(stream)
247               || attr_scan(stream, ATTR_FLAG_STRICT,
248                                RECV_ATTR_INT(MAIL_ATTR_FLAGS, &server_flags),
249                            RECV_ATTR_STR(MAIL_ATTR_TRANSPORT, reply->transport),
250                                RECV_ATTR_STR(MAIL_ATTR_NEXTHOP, reply->nexthop),
251                                RECV_ATTR_STR(MAIL_ATTR_RECIP, reply->recipient),
252                                RECV_ATTR_INT(MAIL_ATTR_FLAGS, &reply->flags),
253                                ATTR_TYPE_END) != 5) {
254               if (msg_verbose || count > 1 || (errno && errno != EPIPE && errno != ENOENT))
255                     msg_warn("problem talking to service %s: %m",
256                                var_rewrite_service);
257           } else {
258               if (msg_verbose)
259                     msg_info("%s: `%s' -> `%s' -> transp=`%s' host=`%s' rcpt=`%s' flags=%s%s%s%s class=%s%s%s%s%s",
260                                myname, sender, addr, STR(reply->transport),
261                                STR(reply->nexthop), STR(reply->recipient),
262                                IFSET(RESOLVE_FLAG_FINAL, "final"),
263                                IFSET(RESOLVE_FLAG_ROUTED, "routed"),
264                                IFSET(RESOLVE_FLAG_ERROR, "error"),
265                                IFSET(RESOLVE_FLAG_FAIL, "fail"),
266                                IFSET(RESOLVE_CLASS_LOCAL, "local"),
267                                IFSET(RESOLVE_CLASS_ALIAS, "alias"),
268                                IFSET(RESOLVE_CLASS_VIRTUAL, "virtual"),
269                                IFSET(RESOLVE_CLASS_RELAY, "relay"),
270                                IFSET(RESOLVE_CLASS_DEFAULT, "default"));
271               /* Server-requested disconnect. */
272               if (server_flags != 0)
273                     clnt_stream_recover(rewrite_clnt_stream);
274               if (STR(reply->transport)[0] == 0)
275                     msg_warn("%s: null transport result for: <%s>", myname, addr);
276               else if (STR(reply->recipient)[0] == 0 && *addr != 0)
277                     msg_warn("%s: null recipient result for: <%s>", myname, addr);
278               else
279                     break;
280           }
281           sleep(1);                               /* XXX make configurable */
282           clnt_stream_recover(rewrite_clnt_stream);
283     }
284 
285     /*
286      * Update the cache.
287      */
288     vstring_strcpy(last_class, class);
289     vstring_strcpy(last_sender, sender);
290     vstring_strcpy(last_addr, addr);
291     vstring_strcpy(last_reply.transport, STR(reply->transport));
292     vstring_strcpy(last_reply.nexthop, STR(reply->nexthop));
293     vstring_strcpy(last_reply.recipient, STR(reply->recipient));
294     last_reply.flags = reply->flags;
295     last_expire = time((time_t *) 0) + 30;        /* XXX make configurable */
296 }
297 
298 /* resolve_clnt_free - destroy reply */
299 
resolve_clnt_free(RESOLVE_REPLY * reply)300 void    resolve_clnt_free(RESOLVE_REPLY *reply)
301 {
302     reply->transport = vstring_free(reply->transport);
303     reply->nexthop = vstring_free(reply->nexthop);
304     reply->recipient = vstring_free(reply->recipient);
305 }
306 
307 #ifdef TEST
308 
309 #include <stdlib.h>
310 #include <msg_vstream.h>
311 #include <vstring_vstream.h>
312 #include <split_at.h>
313 #include <mail_conf.h>
314 
usage(char * myname)315 static NORETURN usage(char *myname)
316 {
317     msg_fatal("usage: %s [-v] [address...]", myname);
318 }
319 
resolve(char * class,char * addr,RESOLVE_REPLY * reply)320 static void resolve(char *class, char *addr, RESOLVE_REPLY *reply)
321 {
322     struct RESOLVE_FLAG_TABLE {
323           int     flag;
324           const char *name;
325     };
326     struct RESOLVE_FLAG_TABLE resolve_flag_table[] = {
327           RESOLVE_FLAG_FINAL, "FLAG_FINAL",
328           RESOLVE_FLAG_ROUTED, "FLAG_ROUTED",
329           RESOLVE_FLAG_ERROR, "FLAG_ERROR",
330           RESOLVE_FLAG_FAIL, "FLAG_FAIL",
331           RESOLVE_CLASS_LOCAL, "CLASS_LOCAL",
332           RESOLVE_CLASS_ALIAS, "CLASS_ALIAS",
333           RESOLVE_CLASS_VIRTUAL, "CLASS_VIRTUAL",
334           RESOLVE_CLASS_RELAY, "CLASS_RELAY",
335           RESOLVE_CLASS_DEFAULT, "CLASS_DEFAULT",
336           0,
337     };
338     struct RESOLVE_FLAG_TABLE *fp;
339 
340     resolve_clnt(class, RESOLVE_NULL_FROM, addr, reply);
341     if (reply->flags & RESOLVE_FLAG_FAIL) {
342           vstream_printf("request failed\n");
343     } else {
344           vstream_printf("%-10s %s\n", "class", class);
345           vstream_printf("%-10s %s\n", "address", addr);
346           vstream_printf("%-10s %s\n", "transport", STR(reply->transport));
347           vstream_printf("%-10s %s\n", "nexthop", *STR(reply->nexthop) ?
348                            STR(reply->nexthop) : "[none]");
349           vstream_printf("%-10s %s\n", "recipient", STR(reply->recipient));
350           vstream_printf("%-10s ", "flags");
351           for (fp = resolve_flag_table; fp->name; fp++) {
352               if (reply->flags & fp->flag) {
353                     vstream_printf("%s ", fp->name);
354                     reply->flags &= ~fp->flag;
355               }
356           }
357           if (reply->flags != 0)
358               vstream_printf("Unknown flag 0x%x", reply->flags);
359           vstream_printf("\n\n");
360           vstream_fflush(VSTREAM_OUT);
361     }
362 }
363 
main(int argc,char ** argv)364 int     main(int argc, char **argv)
365 {
366     RESOLVE_REPLY reply;
367     char   *addr;
368     int     ch;
369 
370     msg_vstream_init(argv[0], VSTREAM_ERR);
371 
372     mail_conf_read();
373     msg_info("using config files in %s", var_config_dir);
374     if (chdir(var_queue_dir) < 0)
375           msg_fatal("chdir %s: %m", var_queue_dir);
376 
377     while ((ch = GETOPT(argc, argv, "v")) > 0) {
378           switch (ch) {
379           case 'v':
380               msg_verbose++;
381               break;
382           default:
383               usage(argv[0]);
384           }
385     }
386     resolve_clnt_init(&reply);
387 
388     if (argc > optind) {
389           while (argv[optind] && argv[optind + 1]) {
390               resolve(argv[optind], argv[optind + 1], &reply);
391               optind += 2;
392           }
393     } else {
394           VSTRING *buffer = vstring_alloc(1);
395 
396           while (vstring_fgets_nonl(buffer, VSTREAM_IN)) {
397               addr = split_at(STR(buffer), ' ');
398               if (*STR(buffer) == 0)
399                     msg_fatal("need as input: class [address]");
400               if (addr == 0)
401                     addr = "";
402               resolve(STR(buffer), addr, &reply);
403           }
404           vstring_free(buffer);
405     }
406     resolve_clnt_free(&reply);
407     exit(0);
408 }
409 
410 #endif
411