1/* $NetBSD: mailbox.c,v 1.4 2022/10/08 16:12:46 christos Exp $ */ 2 3/*++ 4/* NAME 5/* mailbox 3 6/* SUMMARY 7/* mailbox delivery 8/* SYNOPSIS 9/* #include "local.h" 10/* 11/* int deliver_mailbox(state, usr_attr, statusp) 12/* LOCAL_STATE state; 13/* USER_ATTR usr_attr; 14/* int *statusp; 15/* DESCRIPTION 16/* deliver_mailbox() delivers to mailbox, with duplicate 17/* suppression. The default is direct mailbox delivery to 18/* /var/[spool/]mail/\fIuser\fR; when a \fIhome_mailbox\fR 19/* has been configured, mail is delivered to ~/$\fIhome_mailbox\fR; 20/* and when a \fImailbox_command\fR has been configured, the message 21/* is piped into the command instead. 22/* 23/* A zero result means that the named user was not found. 24/* 25/* Arguments: 26/* .IP state 27/* The attributes that specify the message, recipient and more. 28/* Attributes describing alias, include or forward expansion. 29/* A table with the results from expanding aliases or lists. 30/* .IP usr_attr 31/* Attributes describing user rights and environment. 32/* .IP statusp 33/* Delivery status: see below. 34/* DIAGNOSTICS 35/* The message delivery status is non-zero when delivery should be tried 36/* again. 37/* LICENSE 38/* .ad 39/* .fi 40/* The Secure Mailer license must be distributed with this software. 41/* AUTHOR(S) 42/* Wietse Venema 43/* IBM T.J. Watson Research 44/* P.O. Box 704 45/* Yorktown Heights, NY 10598, USA 46/* 47/* Wietse Venema 48/* Google, Inc. 49/* 111 8th Avenue 50/* New York, NY 10011, USA 51/*--*/ 52 53/* System library. */ 54 55#include <sys_defs.h> 56#include <sys/stat.h> 57#include <fcntl.h> 58#include <string.h> 59#include <unistd.h> 60#include <errno.h> 61 62/* Utility library. */ 63 64#include <msg.h> 65#include <htable.h> 66#include <vstring.h> 67#include <vstream.h> 68#include <mymalloc.h> 69#include <stringops.h> 70#include <set_eugid.h> 71#include <warn_stat.h> 72 73/* Global library. */ 74 75#include <mail_copy.h> 76#include <defer.h> 77#include <sent.h> 78#include <mypwd.h> 79#include <been_here.h> 80#include <mail_params.h> 81#include <deliver_pass.h> 82#include <mbox_open.h> 83#include <maps.h> 84#include <dsn_util.h> 85 86/* Application-specific. */ 87 88#include "local.h" 89#include "biff_notify.h" 90 91#define YES 1 92#define NO 0 93 94/* deliver_mailbox_file - deliver to recipient mailbox */ 95 96static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) 97{ 98 const char *myname = "deliver_mailbox_file"; 99 char *spool_dir; 100 char *mailbox; 101 DSN_BUF *why = state.msg_attr.why; 102 MBOX *mp; 103 int mail_copy_status; 104 int deliver_status; 105 int copy_flags; 106 VSTRING *biff; 107 off_t end; 108 struct stat st; 109 uid_t spool_uid; 110 gid_t spool_gid; 111 uid_t chown_uid; 112 gid_t chown_gid; 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 * Don't deliver trace-only requests. 123 */ 124 if (DEL_REQ_TRACE_ONLY(state.request->flags)) { 125 dsb_simple(why, "2.0.0", "delivers to mailbox"); 126 return (sent(BOUNCE_FLAGS(state.request), SENT_ATTR(state.msg_attr))); 127 } 128 129 /* 130 * Initialize. Assume the operation will fail. Set the delivered 131 * attribute to reflect the final recipient. 132 */ 133 if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) 134 msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); 135 if (var_frozen_delivered == 0) 136 state.msg_attr.delivered = state.msg_attr.rcpt.address; 137 mail_copy_status = MAIL_COPY_STAT_WRITE; 138 if (*var_home_mailbox) { 139 spool_dir = 0; 140 mailbox = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); 141 } else { 142 spool_dir = var_mail_spool_dir; 143 mailbox = concatenate(spool_dir, "/", state.msg_attr.user, (char *) 0); 144 } 145 146 /* 147 * Mailbox delivery with least privilege. As long as we do not use root 148 * privileges this code may also work over NFS. 149 * 150 * If delivering to the recipient's home directory, perform all operations 151 * (including file locking) as that user (Mike Muuss, Army Research 152 * Laboratory, USA). 153 * 154 * If delivering to the mail spool directory, and the spool directory is 155 * world-writable, deliver as the recipient; if the spool directory is 156 * group-writable, use the recipient user id and the mail spool group id. 157 * 158 * Otherwise, use root privileges and chown the mailbox if we create it. 159 */ 160 if (spool_dir == 0 161 || stat(spool_dir, &st) < 0 162 || (st.st_mode & S_IWOTH) != 0) { 163 spool_uid = usr_attr.uid; 164 spool_gid = usr_attr.gid; 165 } else if ((st.st_mode & S_IWGRP) != 0) { 166 spool_uid = usr_attr.uid; 167 spool_gid = st.st_gid; 168 } else { 169 spool_uid = 0; 170 spool_gid = 0; 171 } 172 if (spool_uid == usr_attr.uid) { 173 chown_uid = -1; 174 chown_gid = -1; 175 } else { 176 chown_uid = usr_attr.uid; 177 chown_gid = usr_attr.gid; 178 } 179 if (msg_verbose) 180 msg_info("spool_uid/gid %ld/%ld chown_uid/gid %ld/%ld", 181 (long) spool_uid, (long) spool_gid, 182 (long) chown_uid, (long) chown_gid); 183 184 /* 185 * Lock the mailbox and open/create the mailbox file. Depending on the 186 * type of locking used, we lock first or we open first. 187 * 188 * Write the file as the recipient, so that file quota work. 189 */ 190 copy_flags = MAIL_COPY_MBOX; 191 if ((local_deliver_hdr_mask & DELIVER_HDR_FILE) == 0) 192 copy_flags &= ~MAIL_COPY_DELIVERED; 193 194 set_eugid(spool_uid, spool_gid); 195 mp = mbox_open(mailbox, O_APPEND | O_WRONLY | O_CREAT, 196 S_IRUSR | S_IWUSR, &st, chown_uid, chown_gid, 197 local_mbox_lock_mask, "5.2.0", why); 198 if (mp != 0) { 199 if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) 200 set_eugid(usr_attr.uid, usr_attr.gid); 201 if (S_ISREG(st.st_mode) == 0) { 202 vstream_fclose(mp->fp); 203 dsb_simple(why, "5.2.0", 204 "destination %s is not a regular file", mailbox); 205 } else if (var_strict_mbox_owner && st.st_uid != usr_attr.uid) { 206 vstream_fclose(mp->fp); 207 dsb_simple(why, "4.2.0", 208 "destination %s is not owned by recipient", mailbox); 209 msg_warn("specify \"%s = no\" to ignore mailbox ownership mismatch", 210 VAR_STRICT_MBOX_OWNER); 211 } else { 212 if ((end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END)) < 0) 213 msg_fatal("seek mailbox file %s: %m", mailbox); 214 mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, 215 copy_flags, "\n", why); 216 } 217 if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) 218 set_eugid(spool_uid, spool_gid); 219 mbox_release(mp); 220 } 221 set_eugid(var_owner_uid, var_owner_gid); 222 223 /* 224 * As the mail system, bounce, defer delivery, or report success. 225 */ 226 if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { 227 deliver_status = DEL_STAT_DEFER; 228 } else if (mail_copy_status != 0) { 229 vstring_sprintf_prepend(why->reason, 230 "cannot update mailbox %s for user %s. ", 231 mailbox, state.msg_attr.user); 232 deliver_status = 233 (STR(why->status)[0] == '4' ? 234 defer_append : bounce_append) 235 (BOUNCE_FLAGS(state.request), BOUNCE_ATTR(state.msg_attr)); 236 } else { 237 dsb_simple(why, "2.0.0", "delivered to mailbox"); 238 deliver_status = sent(BOUNCE_FLAGS(state.request), 239 SENT_ATTR(state.msg_attr)); 240 if (var_biff) { 241 biff = vstring_alloc(100); 242 vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); 243 biff_notify(STR(biff), VSTRING_LEN(biff) + 1); 244 vstring_free(biff); 245 } 246 } 247 248 /* 249 * Clean up. 250 */ 251 myfree(mailbox); 252 return (deliver_status); 253} 254 255/* deliver_mailbox - deliver to recipient mailbox */ 256 257int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) 258{ 259 const char *myname = "deliver_mailbox"; 260 int status; 261 struct mypasswd *mbox_pwd; 262 char *path; 263 static MAPS *transp_maps; 264 const char *map_transport; 265 static MAPS *cmd_maps; 266 const char *map_command; 267 268 /* 269 * Make verbose logging easier to understand. 270 */ 271 state.level++; 272 if (msg_verbose) 273 MSG_LOG_STATE(myname, state); 274 275 /* 276 * DUPLICATE ELIMINATION 277 * 278 * Don't come here more than once, whether or not the recipient exists. 279 */ 280 if (been_here(state.dup_filter, "mailbox %s", state.msg_attr.local)) 281 return (YES); 282 283 /* 284 * Delegate mailbox delivery to another message transport. 285 */ 286 if (*var_mbox_transp_maps && transp_maps == 0) 287 transp_maps = maps_create(VAR_MBOX_TRANSP_MAPS, var_mbox_transp_maps, 288 DICT_FLAG_LOCK | DICT_FLAG_NO_REGSUB 289 | DICT_FLAG_UTF8_REQUEST); 290 /* The -1 is a hint for the down-stream deliver_completed() function. */ 291 if (transp_maps 292 && (map_transport = maps_find(transp_maps, state.msg_attr.user, 293 DICT_FLAG_NONE)) != 0) { 294 state.msg_attr.rcpt.offset = -1L; 295 *statusp = deliver_pass(MAIL_CLASS_PRIVATE, map_transport, 296 state.request, &state.msg_attr.rcpt); 297 return (YES); 298 } else if (transp_maps && transp_maps->error != 0) { 299 /* Details in the logfile. */ 300 dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); 301 *statusp = defer_append(BOUNCE_FLAGS(state.request), 302 BOUNCE_ATTR(state.msg_attr)); 303 return (YES); 304 } 305 if (*var_mailbox_transport) { 306 state.msg_attr.rcpt.offset = -1L; 307 *statusp = deliver_pass(MAIL_CLASS_PRIVATE, var_mailbox_transport, 308 state.request, &state.msg_attr.rcpt); 309 return (YES); 310 } 311 312 /* 313 * Skip delivery when this recipient does not exist. 314 */ 315 if ((errno = mypwnam_err(state.msg_attr.user, &mbox_pwd)) != 0) { 316 msg_warn("error looking up passwd info for %s: %m", 317 state.msg_attr.user); 318 dsb_simple(state.msg_attr.why, "4.0.0", "user lookup error"); 319 *statusp = defer_append(BOUNCE_FLAGS(state.request), 320 BOUNCE_ATTR(state.msg_attr)); 321 return (YES); 322 } 323 if (mbox_pwd == 0) 324 return (NO); 325 326 /* 327 * No early returns or we have a memory leak. 328 */ 329 330 /* 331 * DELIVERY RIGHTS 332 * 333 * Use the rights of the recipient user. 334 */ 335 SET_USER_ATTR(usr_attr, mbox_pwd, state.level); 336 337 /* 338 * Deliver to mailbox, maildir or to external command. 339 */ 340#define LAST_CHAR(s) (s[strlen(s) - 1]) 341 342 if (*var_mailbox_cmd_maps && cmd_maps == 0) 343 cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, 344 DICT_FLAG_LOCK | DICT_FLAG_PARANOID 345 | DICT_FLAG_UTF8_REQUEST); 346 347 if (cmd_maps && (map_command = maps_find(cmd_maps, state.msg_attr.user, 348 DICT_FLAG_NONE)) != 0) { 349 status = deliver_command(state, usr_attr, map_command); 350 } else if (cmd_maps && cmd_maps->error != 0) { 351 /* Details in the logfile. */ 352 dsb_simple(state.msg_attr.why, "4.3.0", "table lookup failure"); 353 status = defer_append(BOUNCE_FLAGS(state.request), 354 BOUNCE_ATTR(state.msg_attr)); 355 } else if (*var_mailbox_command) { 356 status = deliver_command(state, usr_attr, var_mailbox_command); 357 } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { 358 path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); 359 status = deliver_maildir(state, usr_attr, path); 360 myfree(path); 361 } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { 362 path = concatenate(var_mail_spool_dir, state.msg_attr.user, 363 "/", (char *) 0); 364 status = deliver_maildir(state, usr_attr, path); 365 myfree(path); 366 } else 367 status = deliver_mailbox_file(state, usr_attr); 368 369 /* 370 * Cleanup. 371 */ 372 mypwfree(mbox_pwd); 373 *statusp = status; 374 return (YES); 375} 376