1/* $NetBSD$ */ 2 3/*++ 4/* NAME 5/* recipient 3 6/* SUMMARY 7/* deliver to one local recipient 8/* SYNOPSIS 9/* #include "local.h" 10/* 11/* int deliver_recipient(state, usr_attr) 12/* LOCAL_STATE state; 13/* USER_ATTR *usr_attr; 14/* DESCRIPTION 15/* deliver_recipient() delivers a message to a local recipient. 16/* It is called initially when the queue manager requests 17/* delivery to a local recipient, and is called recursively 18/* when an alias or forward file expands to a local recipient. 19/* 20/* When called recursively with, for example, a result from alias 21/* or forward file expansion, aliases are expanded immediately, 22/* but mail for non-alias destinations is submitted as a new 23/* message, so that each recipient has a dedicated queue file 24/* message delivery status record (in a shared queue file). 25/* 26/* When the \fIrecipient_delimiter\fR configuration parameter 27/* is set, it is used to separate cookies off recipient names. 28/* A common setting is to have "recipient_delimiter = +" 29/* so that mail for \fIuser+foo\fR is delivered to \fIuser\fR, 30/* with a "Delivered-To: user+foo@domain" header line. 31/* 32/* Arguments: 33/* .IP state 34/* The attributes that specify the message, sender, and more. 35/* Attributes describing alias, include or forward expansion. 36/* A table with the results from expanding aliases or lists. 37/* A table with delivered-to: addresses taken from the message. 38/* .IP usr_attr 39/* Attributes describing user rights and environment. 40/* DIAGNOSTICS 41/* deliver_recipient() returns non-zero when delivery should be 42/* tried again. 43/* BUGS 44/* Mutually-recursive aliases or $HOME/.forward files aren't 45/* detected when they could be. The resulting mail forwarding loop 46/* is broken by the use of the Delivered-To: message header. 47/* SEE ALSO 48/* alias(3) delivery to aliases 49/* mailbox(3) delivery to mailbox 50/* dotforward(3) delivery to destinations in .forward file 51/* LICENSE 52/* .ad 53/* .fi 54/* The Secure Mailer license must be distributed with this software. 55/* AUTHOR(S) 56/* Wietse Venema 57/* IBM T.J. Watson Research 58/* P.O. Box 704 59/* Yorktown Heights, NY 10598, USA 60/*--*/ 61 62/* System library. */ 63 64#include <sys_defs.h> 65#include <sys/stat.h> 66#include <unistd.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 <mymalloc.h> 77#include <htable.h> 78#include <split_at.h> 79#include <stringops.h> 80#include <dict.h> 81#include <stat_as.h> 82 83/* Global library. */ 84 85#include <bounce.h> 86#include <defer.h> 87#include <mail_params.h> 88#include <split_addr.h> 89#include <strip_addr.h> 90#include <ext_prop.h> 91#include <mypwd.h> 92#include <canon_addr.h> 93 94/* Application-specific. */ 95 96#include "local.h" 97 98/* deliver_switch - branch on recipient type */ 99 100static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr) 101{ 102 const char *myname = "deliver_switch"; 103 int status = 0; 104 struct stat st; 105 struct mypasswd *mypwd; 106 107 /* 108 * Make verbose logging easier to understand. 109 */ 110 state.level++; 111 if (msg_verbose) 112 MSG_LOG_STATE(myname, state); 113 114 115 /* 116 * \user is special: it means don't do any alias or forward expansion. 117 * 118 * XXX This code currently does not work due to revision of the RFC822 119 * address parser. \user should be permitted only in locally specified 120 * aliases, includes or forward files. 121 * 122 * XXX Should test for presence of user home directory. 123 */ 124 if (state.msg_attr.rcpt.address[0] == '\\') { 125 state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++; 126 if (deliver_mailbox(state, usr_attr, &status) == 0) 127 status = deliver_unknown(state, usr_attr); 128 return (status); 129 } 130 131 /* 132 * Otherwise, alias expansion has highest precedence. First look up the 133 * full localpart, then the bare user. Obey the address extension 134 * propagation policy. 135 */ 136 state.msg_attr.unmatched = 0; 137 if (deliver_alias(state, usr_attr, state.msg_attr.local, &status)) 138 return (status); 139 if (state.msg_attr.extension != 0) { 140 if (local_ext_prop_mask & EXT_PROP_ALIAS) 141 state.msg_attr.unmatched = state.msg_attr.extension; 142 if (deliver_alias(state, usr_attr, state.msg_attr.user, &status)) 143 return (status); 144 state.msg_attr.unmatched = state.msg_attr.extension; 145 } 146 147 /* 148 * Special case for mail locally forwarded or aliased to a different 149 * local address. Resubmit the message via the cleanup service, so that 150 * each recipient gets a separate delivery queue file status record in 151 * the new queue file. The downside of this approach is that mutually 152 * recursive .forward files cause a mail forwarding loop. Fortunately, 153 * the loop can be broken by the use of the Delivered-To: message header. 154 * 155 * The code below must not trigger on mail sent to an alias that has no 156 * owner- companion, so that mail for an alias first.last->username is 157 * delivered directly, instead of going through username->first.last 158 * canonical mappings in the cleanup service. The downside of this 159 * approach is that recipients in the expansion of an alias without 160 * owner- won't have separate delivery queue file status records, because 161 * for them, the message won't be resubmitted as a new queue file. 162 * 163 * Do something sensible on systems that receive mail for multiple domains, 164 * such as primary.name and secondary.name. Don't resubmit the message 165 * when mail for `user@secondary.name' is delivered to a .forward file 166 * that lists `user' or `user@primary.name'. We already know that the 167 * recipient domain is local, so we only have to compare local parts. 168 */ 169 if (state.msg_attr.owner != 0 170 && strcasecmp(state.msg_attr.owner, state.msg_attr.user) != 0) 171 return (deliver_indirect(state)); 172 173 /* 174 * Always forward recipients in :include: files. 175 */ 176 if (state.msg_attr.exp_type == EXPAND_TYPE_INCL) 177 return (deliver_indirect(state)); 178 179 /* 180 * Delivery to local user. First try expansion of the recipient's 181 * $HOME/.forward file, then mailbox delivery. Back off when the user's 182 * home directory does not exist. 183 */ 184 if (var_stat_home_dir 185 && (mypwd = mypwnam(state.msg_attr.user)) != 0 186 && stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) { 187 dsb_simple(state.msg_attr.why, "4.3.0", 188 "cannot access home directory %s: %m", mypwd->pw_dir); 189 return (defer_append(BOUNCE_FLAGS(state.request), 190 BOUNCE_ATTR(state.msg_attr))); 191 } 192 if (deliver_dotforward(state, usr_attr, &status) == 0 193 && deliver_mailbox(state, usr_attr, &status) == 0) 194 status = deliver_unknown(state, usr_attr); 195 return (status); 196} 197 198/* deliver_recipient - deliver one local recipient */ 199 200int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) 201{ 202 const char *myname = "deliver_recipient"; 203 int rcpt_stat; 204 205 /* 206 * Make verbose logging easier to understand. 207 */ 208 state.level++; 209 if (msg_verbose) 210 MSG_LOG_STATE(myname, state); 211 212 /* 213 * Duplicate filter. 214 */ 215 if (been_here(state.dup_filter, "recipient %d %s", 216 state.level, state.msg_attr.rcpt.address)) 217 return (0); 218 219 /* 220 * With each level of recursion, detect and break external message 221 * forwarding loops. 222 * 223 * If the looping recipient address has an owner- alias, send the error 224 * report there instead. 225 * 226 * XXX A delivery agent cannot change the envelope sender address for 227 * bouncing. As a workaround we use a one-recipient bounce procedure. 228 * 229 * The proper fix would be to record in the bounce logfile an error return 230 * address for each individual recipient. This would also eliminate the 231 * need for VERP specific bouncing code, at the cost of complicating the 232 * normal bounce sending procedure, but would simplify the code below. 233 */ 234 if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) { 235 dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s", 236 state.msg_attr.rcpt.address); 237 /* Account for possible owner- sender address override. */ 238 return (bounce_workaround(state)); 239 } 240 241 /* 242 * Set up the recipient-specific attributes. If this is forwarded mail, 243 * leave the delivered attribute alone, so that the forwarded message 244 * will show the correct forwarding recipient. 245 */ 246 if (state.msg_attr.delivered == 0) 247 state.msg_attr.delivered = state.msg_attr.rcpt.address; 248 state.msg_attr.local = mystrdup(state.msg_attr.rcpt.address); 249 lowercase(state.msg_attr.local); 250 if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0) 251 msg_warn("no @ in recipient address: %s", state.msg_attr.local); 252 253 /* 254 * Address extension management. 255 * 256 * XXX Fix 20100422, finalized 20100529: it is too error-prone to 257 * distinguish between "no extension" and "no valid extension", so we 258 * drop an invalid extension from the recipient address local-part. 259 */ 260 state.msg_attr.user = mystrdup(state.msg_attr.local); 261 if (*var_rcpt_delim) { 262 state.msg_attr.extension = 263 split_addr(state.msg_attr.user, *var_rcpt_delim); 264 if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { 265 msg_warn("%s: address with illegal extension: %s", 266 state.msg_attr.queue_id, state.msg_attr.local); 267 state.msg_attr.extension = 0; 268 /* XXX Can't myfree + mystrdup, must truncate instead. */ 269 state.msg_attr.local[strlen(state.msg_attr.user)] = 0; 270 /* Truncating is safe. The code below rejects null usernames. */ 271 } 272 } else 273 state.msg_attr.extension = 0; 274 state.msg_attr.unmatched = state.msg_attr.extension; 275 276 /* 277 * Do not allow null usernames. 278 */ 279 if (state.msg_attr.user[0] == 0) { 280 dsb_simple(state.msg_attr.why, "5.1.3", 281 "null username in \"%s\"", state.msg_attr.rcpt.address); 282 return (bounce_append(BOUNCE_FLAGS(state.request), 283 BOUNCE_ATTR(state.msg_attr))); 284 } 285 286 /* 287 * Run the recipient through the delivery switch. 288 */ 289 if (msg_verbose) 290 deliver_attr_dump(&state.msg_attr); 291 rcpt_stat = deliver_switch(state, usr_attr); 292 293 /* 294 * Clean up. 295 */ 296 myfree(state.msg_attr.local); 297 myfree(state.msg_attr.user); 298 299 return (rcpt_stat); 300} 301