1 /* $NetBSD: dotforward.c,v 1.1.1.2 2013/01/02 18:59:00 tron Exp $ */ 2 3 /*++ 4 /* NAME 5 /* dotforward 3 6 /* SUMMARY 7 /* $HOME/.forward file expansion 8 /* SYNOPSIS 9 /* #include "local.h" 10 /* 11 /* int deliver_dotforward(state, usr_attr, statusp) 12 /* LOCAL_STATE state; 13 /* USER_ATTR usr_attr; 14 /* int *statusp; 15 /* DESCRIPTION 16 /* deliver_dotforward() delivers a message to the destinations 17 /* listed in a recipient's .forward file(s) as specified through 18 /* the forward_path configuration parameter. The result is 19 /* zero when no acceptable .forward file was found, or when 20 /* a recipient is listed in her own .forward file. Expansions 21 /* are scrutinized with the forward_expansion_filter parameter. 22 /* 23 /* Arguments: 24 /* .IP state 25 /* Message delivery attributes (sender, recipient etc.). 26 /* Attributes describing alias, include or forward expansion. 27 /* A table with the results from expanding aliases or lists. 28 /* A table with delivered-to: addresses taken from the message. 29 /* .IP usr_attr 30 /* Attributes describing user rights and environment. 31 /* .IP statusp 32 /* Message delivery status. See below. 33 /* DIAGNOSTICS 34 /* Fatal errors: out of memory. Warnings: bad $HOME/.forward 35 /* file type, permissions or ownership. The message delivery 36 /* status is non-zero when delivery should be tried again. 37 /* SEE ALSO 38 /* include(3) include file processor. 39 /* LICENSE 40 /* .ad 41 /* .fi 42 /* The Secure Mailer license must be distributed with this software. 43 /* AUTHOR(S) 44 /* Wietse Venema 45 /* IBM T.J. Watson Research 46 /* P.O. Box 704 47 /* Yorktown Heights, NY 10598, USA 48 /*--*/ 49 50 /* System library. */ 51 52 #include <sys_defs.h> 53 #include <sys/stat.h> 54 #include <unistd.h> 55 #include <errno.h> 56 #include <fcntl.h> 57 #ifdef USE_PATHS_H 58 #include <paths.h> 59 #endif 60 #include <string.h> 61 62 /* Utility library. */ 63 64 #include <msg.h> 65 #include <vstring.h> 66 #include <vstream.h> 67 #include <htable.h> 68 #include <open_as.h> 69 #include <lstat_as.h> 70 #include <iostuff.h> 71 #include <stringops.h> 72 #include <mymalloc.h> 73 #include <mac_expand.h> 74 75 /* Global library. */ 76 77 #include <mypwd.h> 78 #include <bounce.h> 79 #include <defer.h> 80 #include <been_here.h> 81 #include <mail_params.h> 82 #include <mail_conf.h> 83 #include <ext_prop.h> 84 #include <sent.h> 85 #include <dsn_mask.h> 86 #include <trace.h> 87 88 /* Application-specific. */ 89 90 #include "local.h" 91 92 #define NO 0 93 #define YES 1 94 95 /* deliver_dotforward - expand contents of .forward file */ 96 97 int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) 98 { 99 const char *myname = "deliver_dotforward"; 100 struct stat st; 101 VSTRING *path; 102 struct mypasswd *mypwd; 103 int fd; 104 VSTREAM *fp; 105 int status; 106 int forward_found = NO; 107 int lookup_status; 108 int addr_count; 109 char *saved_forward_path; 110 char *lhs; 111 char *next; 112 int expand_status; 113 int saved_notify; 114 115 /* 116 * Make verbose logging easier to understand. 117 */ 118 state.level++; 119 if (msg_verbose) 120 MSG_LOG_STATE(myname, state); 121 122 /* 123 * Skip this module if per-user forwarding is disabled. 124 */ 125 if (*var_forward_path == 0) 126 return (NO); 127 128 /* 129 * Skip non-existing users. The mailbox delivery routine will catch the 130 * error. 131 */ 132 if ((errno = mypwnam_err(state.msg_attr.user, &mypwd)) != 0) { 133 msg_warn("error looking up passwd info for %s: %m", 134 state.msg_attr.user); 135 dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); 136 *statusp = defer_append(BOUNCE_FLAGS(state.request), 137 BOUNCE_ATTR(state.msg_attr)); 138 return (YES); 139 } 140 if (mypwd == 0) 141 return (NO); 142 143 /* 144 * From here on no early returns or we have a memory leak. 145 */ 146 147 /* 148 * EXTERNAL LOOP CONTROL 149 * 150 * Set the delivered message attribute to the recipient, so that this 151 * message will list the correct forwarding address. 152 */ 153 if (var_frozen_delivered == 0) 154 state.msg_attr.delivered = state.msg_attr.rcpt.address; 155 156 /* 157 * DELIVERY RIGHTS 158 * 159 * Do not inherit rights from the .forward file owner. Instead, use the 160 * recipient's rights, and insist that the .forward file is owned by the 161 * recipient. This is a small but significant difference. Use the 162 * recipient's rights for all /file and |command deliveries, and pass on 163 * these rights to command/file destinations in included files. When 164 * these are the rights of root, the /file and |command delivery routines 165 * will use unprivileged default rights instead. Better safe than sorry. 166 */ 167 SET_USER_ATTR(usr_attr, mypwd, state.level); 168 169 /* 170 * DELIVERY POLICY 171 * 172 * Update the expansion type attribute so that we can decide if deliveries 173 * to |command and /file/name are allowed at all. 174 */ 175 state.msg_attr.exp_type = EXPAND_TYPE_FWD; 176 177 /* 178 * WHERE TO REPORT DELIVERY PROBLEMS 179 * 180 * Set the owner attribute so that 1) include files won't set the sender to 181 * be this user and 2) mail forwarded to other local users will be 182 * resubmitted as a new queue file. 183 */ 184 state.msg_attr.owner = state.msg_attr.user; 185 186 /* 187 * Search the forward_path for an existing forward file. 188 * 189 * If unmatched extensions should never be propagated, or if a forward file 190 * name includes the address extension, don't propagate the extension to 191 * the recipient addresses. 192 */ 193 status = 0; 194 path = vstring_alloc(100); 195 saved_forward_path = mystrdup(var_forward_path); 196 next = saved_forward_path; 197 lookup_status = -1; 198 199 while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) { 200 expand_status = local_expand(path, lhs, &state, &usr_attr, 201 var_fwd_exp_filter); 202 if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { 203 lookup_status = 204 lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); 205 if (msg_verbose) 206 msg_info("%s: path %s expand_status %d look_status %d", myname, 207 STR(path), expand_status, lookup_status); 208 if (lookup_status >= 0) { 209 if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 210 || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) 211 state.msg_attr.unmatched = 0; 212 break; 213 } 214 } 215 } 216 217 /* 218 * Process the forward file. 219 * 220 * Assume that usernames do not have file system meta characters. Open the 221 * .forward file as the user. Ignore files that aren't regular files, 222 * files that are owned by the wrong user, or files that have world write 223 * permission enabled. 224 * 225 * DUPLICATE/LOOP ELIMINATION 226 * 227 * If this user includes (an alias of) herself in her own .forward file, 228 * deliver to the user instead. 229 */ 230 if (lookup_status >= 0) { 231 232 /* 233 * Don't expand a verify-only request. 234 */ 235 if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { 236 dsb_simple(state.msg_attr.why, "2.0.0", 237 "forward via file: %s", STR(path)); 238 *statusp = sent(BOUNCE_FLAGS(state.request), 239 SENT_ATTR(state.msg_attr)); 240 forward_found = YES; 241 } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { 242 state.msg_attr.exp_from = state.msg_attr.local; 243 if (S_ISREG(st.st_mode) == 0) { 244 msg_warn("file %s is not a regular file", STR(path)); 245 } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { 246 msg_warn("file %s has bad owner uid %ld", 247 STR(path), (long) st.st_uid); 248 } else if (st.st_mode & 002) { 249 msg_warn("file %s is world writable", STR(path)); 250 } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { 251 msg_warn("cannot open file %s: %m", STR(path)); 252 } else { 253 254 /* 255 * XXX DSN. When delivering to an alias (i.e. the envelope 256 * sender address is not replaced) any ENVID, RET, or ORCPT 257 * parameters are propagated to all forwarding addresses 258 * associated with that alias. The NOTIFY parameter is 259 * propagated to the forwarding addresses, except that any 260 * SUCCESS keyword is removed. 261 */ 262 close_on_exec(fd, CLOSE_ON_EXEC); 263 addr_count = 0; 264 fp = vstream_fdopen(fd, O_RDONLY); 265 saved_notify = state.msg_attr.rcpt.dsn_notify; 266 state.msg_attr.rcpt.dsn_notify = 267 (saved_notify == DSN_NOTIFY_SUCCESS ? 268 DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); 269 status = deliver_token_stream(state, usr_attr, fp, &addr_count); 270 if (vstream_fclose(fp)) 271 msg_warn("close file %s: %m", STR(path)); 272 if (addr_count > 0) { 273 forward_found = YES; 274 been_here(state.dup_filter, "forward-done %s", STR(path)); 275 276 /* 277 * XXX DSN. When delivering to an alias (i.e. the 278 * envelope sender address is not replaced) and the 279 * original NOTIFY parameter for the alias contained the 280 * SUCCESS keyword, an "expanded" DSN is issued for the 281 * alias. 282 */ 283 if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { 284 state.msg_attr.rcpt.dsn_notify = saved_notify; 285 dsb_update(state.msg_attr.why, "2.0.0", "expanded", 286 DSB_SKIP_RMTA, DSB_SKIP_REPLY, 287 "alias expanded"); 288 (void) trace_append(BOUNCE_FLAG_NONE, 289 SENT_ATTR(state.msg_attr)); 290 } 291 } 292 } 293 } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) 294 forward_found = YES; /* else we're recursive */ 295 } 296 297 /* 298 * Clean up. 299 */ 300 vstring_free(path); 301 myfree(saved_forward_path); 302 mypwfree(mypwd); 303 304 *statusp = status; 305 return (forward_found); 306 } 307