1 /* $NetBSD: recipient.c,v 1.2 2017/02/14 01:16:45 christos Exp $ */ 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 #include <errno.h> 69 70 /* Utility library. */ 71 72 #include <msg.h> 73 #include <mymalloc.h> 74 #include <htable.h> 75 #include <split_at.h> 76 #include <stringops.h> 77 #include <dict.h> 78 #include <stat_as.h> 79 80 /* Global library. */ 81 82 #include <bounce.h> 83 #include <defer.h> 84 #include <mail_params.h> 85 #include <split_addr.h> 86 #include <strip_addr.h> 87 #include <ext_prop.h> 88 #include <mypwd.h> 89 #include <canon_addr.h> 90 91 /* Application-specific. */ 92 93 #include "local.h" 94 95 /* deliver_switch - branch on recipient type */ 96 97 static int deliver_switch(LOCAL_STATE state, USER_ATTR usr_attr) 98 { 99 const char *myname = "deliver_switch"; 100 int status = 0; 101 struct stat st; 102 struct mypasswd *mypwd; 103 104 /* 105 * Make verbose logging easier to understand. 106 */ 107 state.level++; 108 if (msg_verbose) 109 MSG_LOG_STATE(myname, state); 110 111 112 /* 113 * \user is special: it means don't do any alias or forward expansion. 114 * 115 * XXX This code currently does not work due to revision of the RFC822 116 * address parser. \user should be permitted only in locally specified 117 * aliases, includes or forward files. 118 * 119 * XXX Should test for presence of user home directory. 120 */ 121 if (state.msg_attr.rcpt.address[0] == '\\') { 122 state.msg_attr.rcpt.address++, state.msg_attr.local++, state.msg_attr.user++; 123 if (deliver_mailbox(state, usr_attr, &status) == 0) 124 status = deliver_unknown(state, usr_attr); 125 return (status); 126 } 127 128 /* 129 * Otherwise, alias expansion has highest precedence. First look up the 130 * full localpart, then the bare user. Obey the address extension 131 * propagation policy. 132 */ 133 state.msg_attr.unmatched = 0; 134 if (deliver_alias(state, usr_attr, state.msg_attr.local, &status)) 135 return (status); 136 if (state.msg_attr.extension != 0) { 137 if (local_ext_prop_mask & EXT_PROP_ALIAS) 138 state.msg_attr.unmatched = state.msg_attr.extension; 139 if (deliver_alias(state, usr_attr, state.msg_attr.user, &status)) 140 return (status); 141 state.msg_attr.unmatched = state.msg_attr.extension; 142 } 143 144 /* 145 * Special case for mail locally forwarded or aliased to a different 146 * local address. Resubmit the message via the cleanup service, so that 147 * each recipient gets a separate delivery queue file status record in 148 * the new queue file. The downside of this approach is that mutually 149 * recursive .forward files cause a mail forwarding loop. Fortunately, 150 * the loop can be broken by the use of the Delivered-To: message header. 151 * 152 * The code below must not trigger on mail sent to an alias that has no 153 * owner- companion, so that mail for an alias first.last->username is 154 * delivered directly, instead of going through username->first.last 155 * canonical mappings in the cleanup service. The downside of this 156 * approach is that recipients in the expansion of an alias without 157 * owner- won't have separate delivery queue file status records, because 158 * for them, the message won't be resubmitted as a new queue file. 159 * 160 * Do something sensible on systems that receive mail for multiple domains, 161 * such as primary.name and secondary.name. Don't resubmit the message 162 * when mail for `user@secondary.name' is delivered to a .forward file 163 * that lists `user' or `user@primary.name'. We already know that the 164 * recipient domain is local, so we only have to compare local parts. 165 */ 166 if (state.msg_attr.owner != 0 167 && strcasecmp_utf8(state.msg_attr.owner, state.msg_attr.user) != 0) 168 return (deliver_indirect(state)); 169 170 /* 171 * Always forward recipients in :include: files. 172 */ 173 if (state.msg_attr.exp_type == EXPAND_TYPE_INCL) 174 return (deliver_indirect(state)); 175 176 /* 177 * Delivery to local user. First try expansion of the recipient's 178 * $HOME/.forward file, then mailbox delivery. Back off when the user's 179 * home directory does not exist. 180 */ 181 mypwd = 0; 182 if (var_stat_home_dir 183 && (errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { 184 msg_warn("error looking up passwd info for %s: %m", 185 state.msg_attr.user); 186 dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); 187 return (defer_append(BOUNCE_FLAGS(state.request), 188 BOUNCE_ATTR(state.msg_attr))); 189 } 190 if (mypwd != 0) { 191 if (stat_as(mypwd->pw_dir, &st, mypwd->pw_uid, mypwd->pw_gid) < 0) { 192 dsb_simple(state.msg_attr.why, "4.3.0", 193 "cannot access home directory %s: %m", mypwd->pw_dir); 194 mypwfree(mypwd); 195 return (defer_append(BOUNCE_FLAGS(state.request), 196 BOUNCE_ATTR(state.msg_attr))); 197 } 198 mypwfree(mypwd); 199 } 200 if (deliver_dotforward(state, usr_attr, &status) == 0 201 && deliver_mailbox(state, usr_attr, &status) == 0) 202 status = deliver_unknown(state, usr_attr); 203 return (status); 204 } 205 206 /* deliver_recipient - deliver one local recipient */ 207 208 int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) 209 { 210 const char *myname = "deliver_recipient"; 211 VSTRING *folded; 212 int rcpt_stat; 213 214 /* 215 * Make verbose logging easier to understand. 216 */ 217 state.level++; 218 if (msg_verbose) 219 MSG_LOG_STATE(myname, state); 220 221 /* 222 * Duplicate filter. 223 */ 224 if (been_here(state.dup_filter, "recipient %d %s", 225 state.level, state.msg_attr.rcpt.address)) 226 return (0); 227 228 /* 229 * With each level of recursion, detect and break external message 230 * forwarding loops. 231 * 232 * If the looping recipient address has an owner- alias, send the error 233 * report there instead. 234 * 235 * XXX A delivery agent cannot change the envelope sender address for 236 * bouncing. As a workaround we use a one-recipient bounce procedure. 237 * 238 * The proper fix would be to record in the bounce logfile an error return 239 * address for each individual recipient. This would also eliminate the 240 * need for VERP specific bouncing code, at the cost of complicating the 241 * normal bounce sending procedure, but would simplify the code below. 242 */ 243 if (delivered_hdr_find(state.loop_info, state.msg_attr.rcpt.address)) { 244 dsb_simple(state.msg_attr.why, "5.4.6", "mail forwarding loop for %s", 245 state.msg_attr.rcpt.address); 246 /* Account for possible owner- sender address override. */ 247 return (bounce_workaround(state)); 248 } 249 250 /* 251 * Set up the recipient-specific attributes. If this is forwarded mail, 252 * leave the delivered attribute alone, so that the forwarded message 253 * will show the correct forwarding recipient. 254 */ 255 if (state.msg_attr.delivered == 0) 256 state.msg_attr.delivered = state.msg_attr.rcpt.address; 257 folded = vstring_alloc(100); 258 state.msg_attr.local = casefold(folded, state.msg_attr.rcpt.address); 259 if ((state.msg_attr.domain = split_at_right(state.msg_attr.local, '@')) == 0) 260 msg_warn("no @ in recipient address: %s", state.msg_attr.local); 261 262 /* 263 * Address extension management. 264 * 265 * XXX Fix 20100422, finalized 20100529: it is too error-prone to 266 * distinguish between "no extension" and "no valid extension", so we 267 * drop an invalid extension from the recipient address local-part. 268 */ 269 state.msg_attr.user = mystrdup(state.msg_attr.local); 270 if (*var_rcpt_delim) { 271 state.msg_attr.extension = 272 split_addr(state.msg_attr.user, var_rcpt_delim); 273 if (state.msg_attr.extension && strchr(state.msg_attr.extension, '/')) { 274 msg_warn("%s: address with illegal extension: %s", 275 state.msg_attr.queue_id, state.msg_attr.local); 276 state.msg_attr.extension = 0; 277 /* XXX Can't myfree + mystrdup, must truncate instead. */ 278 state.msg_attr.local[strlen(state.msg_attr.user)] = 0; 279 /* Truncating is safe. The code below rejects null usernames. */ 280 } 281 } else 282 state.msg_attr.extension = 0; 283 state.msg_attr.unmatched = state.msg_attr.extension; 284 285 /* 286 * Do not allow null usernames. 287 */ 288 if (state.msg_attr.user[0] == 0) { 289 dsb_simple(state.msg_attr.why, "5.1.3", 290 "null username in \"%s\"", state.msg_attr.rcpt.address); 291 return (bounce_append(BOUNCE_FLAGS(state.request), 292 BOUNCE_ATTR(state.msg_attr))); 293 } 294 295 /* 296 * Run the recipient through the delivery switch. 297 */ 298 if (msg_verbose) 299 deliver_attr_dump(&state.msg_attr); 300 rcpt_stat = deliver_switch(state, usr_attr); 301 302 /* 303 * Clean up. 304 */ 305 vstring_free(folded); 306 myfree(state.msg_attr.user); 307 308 return (rcpt_stat); 309 } 310