1 /* $NetBSD: recipient.c,v 1.1.1.3 2011/03/02 19:32:20 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 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 100 static 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 200 int 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