Home | History | Annotate | Line # | Download | only in trivial-rewrite
      1 /*	$NetBSD: rewrite.c,v 1.3 2020/03/18 19:05:21 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	rewrite 3
      6 /* SUMMARY
      7 /*	mail address rewriter
      8 /* SYNOPSIS
      9 /*	#include "trivial-rewrite.h"
     10 /*
     11 /*	void	rewrite_init(void)
     12 /*
     13 /*	void	rewrite_proto(stream)
     14 /*	VSTREAM	*stream;
     15 /*
     16 /*	void	rewrite_addr(context, addr, result)
     17 /*	RWR_CONTEXT *context;
     18 /*	char	*addr;
     19 /*	VSTRING *result;
     20 /*
     21 /*	void	rewrite_tree(context, tree)
     22 /*	RWR_CONTEXT *context;
     23 /*	TOK822	*tree;
     24 /*
     25 /*	RWR_CONTEXT local_context;
     26 /*	RWR_CONTEXT remote_context;
     27 /* DESCRIPTION
     28 /*	This module implements the trivial address rewriting engine.
     29 /*
     30 /*	rewrite_init() initializes data structures that are private
     31 /*	to this module. It should be called once before using the
     32 /*	actual rewriting routines.
     33 /*
     34 /*	rewrite_proto() implements the client-server protocol: read
     35 /*	one rule set name and one address in external (quoted) form,
     36 /*	reply with the rewritten address in external form.
     37 /*
     38 /*	rewrite_addr() rewrites an address string to another string.
     39 /*	Both input and output are in external (quoted) form.
     40 /*
     41 /*	rewrite_tree() rewrites a parse tree with a single address to
     42 /*	another tree.  A tree is a dummy node on top of a token list.
     43 /*
     44 /*	local_context and remote_context provide domain names for
     45 /*	completing incomplete address forms.
     46 /* STANDARDS
     47 /* DIAGNOSTICS
     48 /*	Problems and transactions are logged to \fBsyslogd\fR(8)
     49 /*	or \fBpostlogd\fR(8).
     50 /* BUGS
     51 /* SEE ALSO
     52 /* LICENSE
     53 /* .ad
     54 /* .fi
     55 /*	The Secure Mailer license must be distributed with this software.
     56 /* AUTHOR(S)
     57 /*	Wietse Venema
     58 /*	IBM T.J. Watson Research
     59 /*	P.O. Box 704
     60 /*	Yorktown Heights, NY 10598, USA
     61 /*--*/
     62 
     63 /* System library. */
     64 
     65 #include <sys_defs.h>
     66 #include <stdlib.h>
     67 #include <string.h>
     68 
     69 #ifdef STRCASECMP_IN_STRINGS_H
     70 #include <strings.h>
     71 #endif
     72 
     73 /* Utility library. */
     74 
     75 #include <msg.h>
     76 #include <vstring.h>
     77 #include <vstream.h>
     78 #include <vstring_vstream.h>
     79 #include <split_at.h>
     80 
     81 /* Global library. */
     82 
     83 #include <mail_params.h>
     84 #include <mail_proto.h>
     85 #include <resolve_local.h>
     86 #include <tok822.h>
     87 #include <mail_conf.h>
     88 
     89 /* Application-specific. */
     90 
     91 #include "trivial-rewrite.h"
     92 
     93 RWR_CONTEXT local_context = {
     94     VAR_MYORIGIN, &var_myorigin,
     95     VAR_MYDOMAIN, &var_mydomain,
     96 };
     97 
     98 RWR_CONTEXT remote_context = {
     99     VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
    100     VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain,
    101 };
    102 
    103 static VSTRING *ruleset;
    104 static VSTRING *address;
    105 static VSTRING *result;
    106 
    107 /* rewrite_tree - rewrite address according to rule set */
    108 
    109 void    rewrite_tree(RWR_CONTEXT *context, TOK822 *tree)
    110 {
    111     TOK822 *colon;
    112     TOK822 *domain;
    113     TOK822 *bang;
    114     TOK822 *local;
    115     VSTRING *vstringval;
    116 
    117     /*
    118      * XXX If you change this module, quote_822_local.c, or tok822_parse.c,
    119      * be sure to re-run the tests under "make rewrite_clnt_test" and "make
    120      * resolve_clnt_test" in the global directory.
    121      */
    122 
    123     /*
    124      * Sanity check.
    125      */
    126     if (tree->head == 0)
    127 	msg_panic("rewrite_tree: empty tree");
    128 
    129     /*
    130      * An empty address is a special case.
    131      */
    132     if (tree->head == tree->tail
    133 	&& tree->tail->type == TOK822_QSTRING
    134 	&& VSTRING_LEN(tree->tail->vstr) == 0)
    135 	return;
    136 
    137     /*
    138      * Treat a lone @ as if it were an empty address.
    139      */
    140     if (tree->head == tree->tail
    141 	&& tree->tail->type == '@') {
    142 	tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
    143 	tok822_sub_append(tree, tok822_alloc(TOK822_QSTRING, ""));
    144 	return;
    145     }
    146 
    147     /*
    148      * Strip source route.
    149      */
    150     if (tree->head->type == '@'
    151 	&& (colon = tok822_find_type(tree->head, ':')) != 0
    152 	&& colon != tree->tail)
    153 	tok822_free_tree(tok822_sub_keep_after(tree, colon));
    154 
    155     /*
    156      * Optionally, transform address forms without @.
    157      */
    158     if ((domain = tok822_rfind_type(tree->tail, '@')) == 0) {
    159 
    160 	/*
    161 	 * Swap domain!user to user@domain.
    162 	 */
    163 	if (var_swap_bangpath != 0
    164 	    && (bang = tok822_find_type(tree->head, '!')) != 0) {
    165 	    tok822_sub_keep_before(tree, bang);
    166 	    local = tok822_cut_after(bang);
    167 	    tok822_free(bang);
    168 	    tok822_sub_prepend(tree, tok822_alloc('@', (char *) 0));
    169 	    if (local)
    170 		tok822_sub_prepend(tree, local);
    171 	}
    172 
    173 	/*
    174 	 * Promote user%domain to user@domain.
    175 	 */
    176 	else if (var_percent_hack != 0
    177 		 && (domain = tok822_rfind_type(tree->tail, '%')) != 0) {
    178 	    domain->type = '@';
    179 	}
    180 
    181 	/*
    182 	 * Append missing @origin
    183 	 */
    184 	else if (var_append_at_myorigin != 0
    185 		 && REW_PARAM_VALUE(context->origin) != 0
    186 		 && REW_PARAM_VALUE(context->origin)[0] != 0) {
    187 	    domain = tok822_sub_append(tree, tok822_alloc('@', (char *) 0));
    188 	    tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->origin),
    189 						(TOK822 **) 0));
    190 	}
    191     }
    192 
    193     /*
    194      * Append missing .domain, but leave broken forms ending in @ alone. This
    195      * merely makes diagnostics more accurate by leaving bogus addresses
    196      * alone.
    197      *
    198      * Backwards-compatibility warning: warn for "user@localhost" when there is
    199      * no "localhost" in mydestination or in any other address class with an
    200      * explicit domain list.
    201      */
    202     if (var_append_dot_mydomain != 0
    203 	&& REW_PARAM_VALUE(context->domain) != 0
    204 	&& REW_PARAM_VALUE(context->domain)[0] != 0
    205 	&& (domain = tok822_rfind_type(tree->tail, '@')) != 0
    206 	&& domain != tree->tail
    207 	&& tok822_find_type(domain, TOK822_DOMLIT) == 0
    208 	&& tok822_find_type(domain, '.') == 0) {
    209 	if (warn_compat_break_app_dot_mydomain
    210 	    && (vstringval = domain->next->vstr) != 0) {
    211 	    if (strcasecmp(vstring_str(vstringval), "localhost") != 0) {
    212 		msg_info("using backwards-compatible default setting "
    213 			 VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
    214 			 "\"%s.%s\"", vstring_str(vstringval),
    215 			 vstring_str(vstringval), var_mydomain);
    216 	    } else if (resolve_class("localhost") == RESOLVE_CLASS_DEFAULT) {
    217 		msg_info("using backwards-compatible default setting "
    218 			 VAR_APP_DOT_MYDOMAIN "=yes to rewrite \"%s\" to "
    219 			 "\"%s.%s\"; please add \"localhost\" to "
    220 			 "mydestination or other address class",
    221 			 vstring_str(vstringval), vstring_str(vstringval),
    222 			 var_mydomain);
    223 	    }
    224 	}
    225 	tok822_sub_append(tree, tok822_alloc('.', (char *) 0));
    226 	tok822_sub_append(tree, tok822_scan(REW_PARAM_VALUE(context->domain),
    227 					    (TOK822 **) 0));
    228     }
    229 
    230     /*
    231      * Strip trailing dot at end of domain, but not dot-dot or @-dot. This
    232      * merely makes diagnostics more accurate by leaving bogus addresses
    233      * alone.
    234      */
    235     if (tree->tail->type == '.'
    236 	&& tree->tail->prev
    237 	&& tree->tail->prev->type != '.'
    238 	&& tree->tail->prev->type != '@')
    239 	tok822_free_tree(tok822_sub_keep_before(tree, tree->tail));
    240 }
    241 
    242 /* rewrite_proto - read request and send reply */
    243 
    244 int     rewrite_proto(VSTREAM *stream)
    245 {
    246     RWR_CONTEXT *context;
    247     TOK822 *tree;
    248 
    249     if (attr_scan(stream, ATTR_FLAG_STRICT,
    250 		  RECV_ATTR_STR(MAIL_ATTR_RULE, ruleset),
    251 		  RECV_ATTR_STR(MAIL_ATTR_ADDR, address),
    252 		  ATTR_TYPE_END) != 2)
    253 	return (-1);
    254 
    255     if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_LOCAL) == 0)
    256 	context = &local_context;
    257     else if (strcmp(vstring_str(ruleset), MAIL_ATTR_RWR_REMOTE) == 0)
    258 	context = &remote_context;
    259     else {
    260 	msg_warn("unknown context: %s", vstring_str(ruleset));
    261 	return (-1);
    262     }
    263 
    264     /*
    265      * Sanity check. An address is supposed to be in externalized form.
    266      */
    267     if (*vstring_str(address) == 0) {
    268 	msg_warn("rewrite_addr: null address");
    269 	vstring_strcpy(result, vstring_str(address));
    270     }
    271 
    272     /*
    273      * Convert the address from externalized (quoted) form to token list,
    274      * rewrite it, and convert back.
    275      */
    276     else {
    277 	tree = tok822_scan_addr(vstring_str(address));
    278 	rewrite_tree(context, tree);
    279 	tok822_externalize(result, tree, TOK822_STR_DEFL);
    280 	tok822_free_tree(tree);
    281     }
    282     if (msg_verbose)
    283 	msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset),
    284 		 vstring_str(address), vstring_str(result));
    285 
    286     attr_print(stream, ATTR_FLAG_NONE,
    287 	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, server_flags),
    288 	       SEND_ATTR_STR(MAIL_ATTR_ADDR, vstring_str(result)),
    289 	       ATTR_TYPE_END);
    290 
    291     if (vstream_fflush(stream) != 0) {
    292 	msg_warn("write rewrite reply: %m");
    293 	return (-1);
    294     }
    295     return (0);
    296 }
    297 
    298 /* rewrite_init - module initializations */
    299 
    300 void    rewrite_init(void)
    301 {
    302     ruleset = vstring_alloc(100);
    303     address = vstring_alloc(100);
    304     result = vstring_alloc(100);
    305 }
    306