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