1 /* $NetBSD: dotforward.c,v 1.1.1.1 2009/06/23 10:08:48 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 <been_here.h> 80 #include <mail_params.h> 81 #include <mail_conf.h> 82 #include <ext_prop.h> 83 #include <sent.h> 84 #include <dsn_mask.h> 85 #include <trace.h> 86 87 /* Application-specific. */ 88 89 #include "local.h" 90 91 #define NO 0 92 #define YES 1 93 94 /* deliver_dotforward - expand contents of .forward file */ 95 96 int deliver_dotforward(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) 97 { 98 const char *myname = "deliver_dotforward"; 99 struct stat st; 100 VSTRING *path; 101 struct mypasswd *mypwd; 102 int fd; 103 VSTREAM *fp; 104 int status; 105 int forward_found = NO; 106 int lookup_status; 107 int addr_count; 108 char *saved_forward_path; 109 char *lhs; 110 char *next; 111 int expand_status; 112 int saved_notify; 113 114 /* 115 * Make verbose logging easier to understand. 116 */ 117 state.level++; 118 if (msg_verbose) 119 MSG_LOG_STATE(myname, state); 120 121 /* 122 * Skip this module if per-user forwarding is disabled. 123 */ 124 if (*var_forward_path == 0) 125 return (NO); 126 127 /* 128 * Skip non-existing users. The mailbox delivery routine will catch the 129 * error. 130 */ 131 if ((mypwd = mypwnam(state.msg_attr.user)) == 0) 132 return (NO); 133 134 /* 135 * From here on no early returns or we have a memory leak. 136 */ 137 138 /* 139 * EXTERNAL LOOP CONTROL 140 * 141 * Set the delivered message attribute to the recipient, so that this 142 * message will list the correct forwarding address. 143 */ 144 if (var_frozen_delivered == 0) 145 state.msg_attr.delivered = state.msg_attr.rcpt.address; 146 147 /* 148 * DELIVERY RIGHTS 149 * 150 * Do not inherit rights from the .forward file owner. Instead, use the 151 * recipient's rights, and insist that the .forward file is owned by the 152 * recipient. This is a small but significant difference. Use the 153 * recipient's rights for all /file and |command deliveries, and pass on 154 * these rights to command/file destinations in included files. When 155 * these are the rights of root, the /file and |command delivery routines 156 * will use unprivileged default rights instead. Better safe than sorry. 157 */ 158 SET_USER_ATTR(usr_attr, mypwd, state.level); 159 160 /* 161 * DELIVERY POLICY 162 * 163 * Update the expansion type attribute so that we can decide if deliveries 164 * to |command and /file/name are allowed at all. 165 */ 166 state.msg_attr.exp_type = EXPAND_TYPE_FWD; 167 168 /* 169 * WHERE TO REPORT DELIVERY PROBLEMS 170 * 171 * Set the owner attribute so that 1) include files won't set the sender to 172 * be this user and 2) mail forwarded to other local users will be 173 * resubmitted as a new queue file. 174 */ 175 state.msg_attr.owner = state.msg_attr.user; 176 177 /* 178 * Search the forward_path for an existing forward file. 179 * 180 * If unmatched extensions should never be propagated, or if a forward file 181 * name includes the address extension, don't propagate the extension to 182 * the recipient addresses. 183 */ 184 status = 0; 185 path = vstring_alloc(100); 186 saved_forward_path = mystrdup(var_forward_path); 187 next = saved_forward_path; 188 lookup_status = -1; 189 190 while ((lhs = mystrtok(&next, ", \t\r\n")) != 0) { 191 expand_status = local_expand(path, lhs, &state, &usr_attr, 192 var_fwd_exp_filter); 193 if ((expand_status & (MAC_PARSE_ERROR | MAC_PARSE_UNDEF)) == 0) { 194 lookup_status = 195 lstat_as(STR(path), &st, usr_attr.uid, usr_attr.gid); 196 if (msg_verbose) 197 msg_info("%s: path %s expand_status %d look_status %d", myname, 198 STR(path), expand_status, lookup_status); 199 if (lookup_status >= 0) { 200 if ((expand_status & LOCAL_EXP_EXTENSION_MATCHED) != 0 201 || (local_ext_prop_mask & EXT_PROP_FORWARD) == 0) 202 state.msg_attr.unmatched = 0; 203 break; 204 } 205 } 206 } 207 208 /* 209 * Process the forward file. 210 * 211 * Assume that usernames do not have file system meta characters. Open the 212 * .forward file as the user. Ignore files that aren't regular files, 213 * files that are owned by the wrong user, or files that have world write 214 * permission enabled. 215 * 216 * DUPLICATE/LOOP ELIMINATION 217 * 218 * If this user includes (an alias of) herself in her own .forward file, 219 * deliver to the user instead. 220 */ 221 if (lookup_status >= 0) { 222 223 /* 224 * Don't expand a verify-only request. 225 */ 226 if (state.request->flags & DEL_REQ_FLAG_MTA_VRFY) { 227 dsb_simple(state.msg_attr.why, "2.0.0", 228 "forward via file: %s", STR(path)); 229 *statusp = sent(BOUNCE_FLAGS(state.request), 230 SENT_ATTR(state.msg_attr)); 231 forward_found = YES; 232 } else if (been_here(state.dup_filter, "forward %s", STR(path)) == 0) { 233 state.msg_attr.exp_from = state.msg_attr.local; 234 if (S_ISREG(st.st_mode) == 0) { 235 msg_warn("file %s is not a regular file", STR(path)); 236 } else if (st.st_uid != 0 && st.st_uid != usr_attr.uid) { 237 msg_warn("file %s has bad owner uid %ld", 238 STR(path), (long) st.st_uid); 239 } else if (st.st_mode & 002) { 240 msg_warn("file %s is world writable", STR(path)); 241 } else if ((fd = open_as(STR(path), O_RDONLY, 0, usr_attr.uid, usr_attr.gid)) < 0) { 242 msg_warn("cannot open file %s: %m", STR(path)); 243 } else { 244 245 /* 246 * XXX DSN. When delivering to an alias (i.e. the envelope 247 * sender address is not replaced) any ENVID, RET, or ORCPT 248 * parameters are propagated to all forwarding addresses 249 * associated with that alias. The NOTIFY parameter is 250 * propagated to the forwarding addresses, except that any 251 * SUCCESS keyword is removed. 252 */ 253 close_on_exec(fd, CLOSE_ON_EXEC); 254 addr_count = 0; 255 fp = vstream_fdopen(fd, O_RDONLY); 256 saved_notify = state.msg_attr.rcpt.dsn_notify; 257 state.msg_attr.rcpt.dsn_notify = 258 (saved_notify == DSN_NOTIFY_SUCCESS ? 259 DSN_NOTIFY_NEVER : saved_notify & ~DSN_NOTIFY_SUCCESS); 260 status = deliver_token_stream(state, usr_attr, fp, &addr_count); 261 if (vstream_fclose(fp)) 262 msg_warn("close file %s: %m", STR(path)); 263 if (addr_count > 0) { 264 forward_found = YES; 265 been_here(state.dup_filter, "forward-done %s", STR(path)); 266 267 /* 268 * XXX DSN. When delivering to an alias (i.e. the 269 * envelope sender address is not replaced) and the 270 * original NOTIFY parameter for the alias contained the 271 * SUCCESS keyword, an "expanded" DSN is issued for the 272 * alias. 273 */ 274 if (status == 0 && (saved_notify & DSN_NOTIFY_SUCCESS)) { 275 state.msg_attr.rcpt.dsn_notify = saved_notify; 276 dsb_update(state.msg_attr.why, "2.0.0", "expanded", 277 DSB_SKIP_RMTA, DSB_SKIP_REPLY, 278 "alias expanded"); 279 (void) trace_append(BOUNCE_FLAG_NONE, 280 SENT_ATTR(state.msg_attr)); 281 } 282 } 283 } 284 } else if (been_here_check(state.dup_filter, "forward-done %s", STR(path)) != 0) 285 forward_found = YES; /* else we're recursive */ 286 } 287 288 /* 289 * Clean up. 290 */ 291 vstring_free(path); 292 myfree(saved_forward_path); 293 mypwfree(mypwd); 294 295 *statusp = status; 296 return (forward_found); 297 } 298