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 93RWR_CONTEXT local_context = { 94 VAR_MYORIGIN, &var_myorigin, 95 VAR_MYDOMAIN, &var_mydomain, 96}; 97 98RWR_CONTEXT remote_context = { 99 VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain, 100 VAR_REM_RWR_DOMAIN, &var_remote_rwr_domain, 101}; 102 103static VSTRING *ruleset; 104static VSTRING *address; 105static VSTRING *result; 106 107/* rewrite_tree - rewrite address according to rule set */ 108 109void 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 244int 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 300void rewrite_init(void) 301{ 302 ruleset = vstring_alloc(100); 303 address = vstring_alloc(100); 304 result = vstring_alloc(100); 305} 306