-/* $Cambridge: exim/src/src/transports/appendfile.c,v 1.6 2005/04/27 10:06:00 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2018 */
/* See the file NOTICE for conditions of use and distribution. */
#endif
-/* Encodings for mailbox formats, and their names. MBX format is actually
-supported only if SUPPORT_MBX is set. */
-
-enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
-
-static char *mailbox_formats[] = {
- "unix", "mbx", "smail", "maildir", "mailstore" };
-
-
-/* Check warn threshold only if quota size set or not a percentage threshold
- percentage check should only be done if quota > 0 */
-
-#define THRESHOLD_CHECK (ob->quota_warn_threshold_value > 0 && \
- (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
-
-
/* Options specific to the appendfile transport. They must be in alphabetic
order (note that "_" comes before the lower case letters). Some of them are
stored in the publicly visible instance block - these are flagged with the
opt_public flag. */
optionlist appendfile_transport_options[] = {
+#ifdef SUPPORT_MAILDIR
+ { "*expand_maildir_use_size_file", opt_stringptr,
+ (void *)offsetof(appendfile_transport_options_block, expand_maildir_use_size_file) },
+#endif
{ "*set_use_fcntl_lock",opt_bool | opt_hidden,
(void *)offsetof(appendfile_transport_options_block, set_use_fcntl) },
{ "*set_use_flock_lock",opt_bool | opt_hidden,
(void *)offsetof(appendfile_transport_options_block, maildir_retries) },
{ "maildir_tag", opt_stringptr,
(void *)offsetof(appendfile_transport_options_block, maildir_tag) },
- { "maildir_use_size_file", opt_bool,
+ { "maildir_use_size_file", opt_expand_bool,
(void *)offsetof(appendfile_transport_options_block, maildir_use_size_file ) } ,
+ { "maildirfolder_create_regex", opt_stringptr,
+ (void *)offsetof(appendfile_transport_options_block, maildirfolder_create_regex ) },
#endif /* SUPPORT_MAILDIR */
#ifdef SUPPORT_MAILSTORE
{ "mailstore_format", opt_bool,
int appendfile_transport_options_count =
sizeof(appendfile_transport_options)/sizeof(optionlist);
+
+#ifdef MACRO_PREDEF
+
+/* Dummy values */
+appendfile_transport_options_block appendfile_transport_option_defaults = {0};
+void appendfile_transport_init(transport_instance *tblock) {}
+BOOL appendfile_transport_entry(transport_instance *tblock, address_item *addr) {return FALSE;}
+
+#else /*!MACRO_PREDEF*/
+
/* Default private options block for the appendfile transport. */
appendfile_transport_options_block appendfile_transport_option_defaults = {
NULL, /* quota_warn_threshold */
NULL, /* mailbox_size_string */
NULL, /* mailbox_filecount_string */
+ NULL, /* expand_maildir_use_size_file */
US"^(?:cur|new|\\..*)$", /* maildir_dir_regex */
NULL, /* maildir_tag */
+ NULL, /* maildirfolder_create_regex */
NULL, /* mailstore_prefix */
NULL, /* mailstore_suffix */
NULL, /* check_string (default changed for non-bsmtp file)*/
NULL, /* escape_string (ditto) */
NULL, /* file_format */
+ 0, /* quota_value */
+ 0, /* quota_warn_threshold_value */
-1, /* mailbox_size_value */
-1, /* mailbox_filecount_value */
- 0, /* quota_value */
0, /* quota_filecount_value */
- 0, /* quota_warn_threshold_value */
APPENDFILE_MODE, /* mode */
APPENDFILE_DIRECTORY_MODE, /* dirmode */
APPENDFILE_LOCKFILE_MODE, /* lockfile_mode */
FALSE, /* mailstore_format */
FALSE, /* mbx_format */
FALSE, /* quota_warn_threshold_is_percent */
- TRUE /* quota_is_inclusive */
+ TRUE, /* quota_is_inclusive */
+ FALSE, /* quota_no_check */
+ FALSE /* quota_filecount_no_check */
};
+/* Encodings for mailbox formats, and their names. MBX format is actually
+supported only if SUPPORT_MBX is set. */
+
+enum { mbf_unix, mbf_mbx, mbf_smail, mbf_maildir, mbf_mailstore };
+
+static const char *mailbox_formats[] = {
+ "unix", "mbx", "smail", "maildir", "mailstore" };
+
+
+/* Check warn threshold only if quota size set or not a percentage threshold
+ percentage check should only be done if quota > 0 */
+
+#define THRESHOLD_CHECK (ob->quota_warn_threshold_value > 0 && \
+ (!ob->quota_warn_threshold_is_percent || ob->quota_value > 0))
+
+
/*************************************************
* Setup entry point *
tblock points to the transport instance
addrlist addresses about to be delivered (not used)
dummy not used (doesn't pass back data)
+ uid the uid that will be set (not used)
+ gid the gid that will be set (not used)
errmsg where to put an error message
Returns: OK, FAIL, or DEFER
static int
appendfile_transport_setup(transport_instance *tblock, address_item *addrlist,
- transport_feedback *dummy, uschar **errmsg)
+ transport_feedback *dummy, uid_t uid, gid_t gid, uschar **errmsg)
{
appendfile_transport_options_block *ob =
(appendfile_transport_options_block *)(tblock->options_block);
uschar *q = ob->quota;
-int *v = &(ob->quota_value);
-int default_value = 0;
-int i;
+double default_value = 0.0;
addrlist = addrlist; /* Keep picky compilers happy */
dummy = dummy;
+uid = uid;
+gid = gid;
+
+if (ob->expand_maildir_use_size_file)
+ ob->maildir_use_size_file = expand_check_condition(ob->expand_maildir_use_size_file,
+ US"`maildir_use_size_file` in transport", tblock->name);
/* Loop for quota, quota_filecount, quota_warn_threshold, mailbox_size,
mailbox_filecount */
-for (i = 0; i < 5; i++)
+for (int i = 0; i < 5; i++)
{
- if (q == NULL) *v = default_value; else
+ double d;
+ int no_check = 0;
+ uschar *which = NULL;
+
+ if (q == NULL) d = default_value;
+ else
{
- double d;
uschar *rest;
uschar *s = expand_string(q);
- if (s == NULL)
+ if (!s)
{
*errmsg = string_sprintf("Expansion of \"%s\" in %s transport failed: "
"%s", q, tblock->name, expand_string_message);
- return search_find_defer? DEFER : FAIL;
+ return f.search_find_defer ? DEFER : FAIL;
}
d = Ustrtod(s, &rest);
else if (tolower(*rest) == 'g') { d *= 1024.0*1024.0*1024.0; rest++; }
else if (*rest == '%' && i == 2)
{
- if (ob->quota_value <= 0 && !ob->maildir_use_size_file) d = 0;
+ if (ob->quota_value <= 0 && !ob->maildir_use_size_file)
+ d = 0;
else if ((int)d < 0 || (int)d > 100)
{
*errmsg = string_sprintf("Invalid quota_warn_threshold percentage (%d)"
rest++;
}
+
+ /* For quota and quota_filecount there may be options
+ appended. Currently only "no_check", so we can be lazy parsing it */
+ if (i < 2 && Ustrstr(rest, "/no_check") == rest)
+ {
+ no_check = 1;
+ rest += sizeof("/no_check") - 1;
+ }
+
while (isspace(*rest)) rest++;
if (*rest != 0)
"in %s transport", s, q, tblock->name);
return FAIL;
}
-
- *v = (int)d;
}
+ /* Set each value, checking for possible overflow. */
+
switch (i)
{
case 0:
- q = ob->quota_filecount;
- v = &(ob->quota_filecount_value);
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+ which = US"quota";
+ ob->quota_value = (off_t)d;
+ ob->quota_no_check = no_check;
+ q = ob->quota_filecount;
+ break;
case 1:
- q = ob->quota_warn_threshold;
- v = &(ob->quota_warn_threshold_value);
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0)
+ which = US"quota_filecount";
+ ob->quota_filecount_value = (int)d;
+ ob->quota_filecount_no_check = no_check;
+ q = ob->quota_warn_threshold;
+ break;
case 2:
- q = ob->mailbox_size_string;
- v = &(ob->mailbox_size_value);
- default_value = -1;
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+ which = US"quota_warn_threshold";
+ ob->quota_warn_threshold_value = (off_t)d;
+ q = ob->mailbox_size_string;
+ default_value = -1.0;
+ break;
case 3:
- q = ob->mailbox_filecount_string;
- v = &(ob->mailbox_filecount_value);
- break;
+ if (d >= 2.0*1024.0*1024.0*1024.0 && sizeof(off_t) <= 4)
+ which = US"mailbox_size";;
+ ob->mailbox_size_value = (off_t)d;
+ q = ob->mailbox_filecount_string;
+ break;
+
+ case 4:
+ if (d >= 2.0*1024.0*1024.0*1024.0)
+ which = US"mailbox_filecount";
+ ob->mailbox_filecount_value = (int)d;
+ break;
+ }
+
+ if (which)
+ {
+ *errmsg = string_sprintf("%s value %.10g is too large (overflow) in "
+ "%s transport", which, d, tblock->name);
+ return FAIL;
}
}
/* Only one of a file name or directory name must be given. */
-if (ob->filename != NULL && ob->dirname != NULL)
+if (ob->filename && ob->dirname)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n "
"only one of \"file\" or \"directory\" can be specified", tblock->name);
/* If a file name was specified, neither quota_filecount nor quota_directory
must be given. */
-if (ob->filename != NULL)
+if (ob->filename)
{
- if (ob->quota_filecount != NULL)
+ if (ob->quota_filecount)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n "
"quota_filecount must not be set without \"directory\"", tblock->name);
- if (ob->quota_directory != NULL)
+ if (ob->quota_directory)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n "
"quota_directory must not be set without \"directory\"", tblock->name);
}
#ifdef SUPPORT_MBX
if (ob->mbx_format)
- {
if (!ob->set_use_lockfile && !ob->set_use_fcntl && !ob->set_use_flock &&
!ob->set_use_mbx_lock)
{
if (!ob->set_use_flock) ob->use_flock = FALSE;
if (!ob->use_fcntl && !ob->use_flock) ob->use_fcntl = TRUE;
}
- }
#endif /* SUPPORT_MBX */
if (!ob->use_fcntl && !ob->use_flock && !ob->use_lockfile && !ob->use_mbx_lock)
specified, and if quota_filecount or quota_directory is given, quota must
be set. */
-if (ob->dirname != NULL)
+if (ob->dirname)
{
if (ob->maildir_format && ob->mailstore_format)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG_FOR, "%s transport:\n "
/* If a fixed uid field is set, then a gid field must also be set. */
-if (tblock->uid_set && !tblock->gid_set && tblock->expand_gid == NULL)
+if (tblock->uid_set && !tblock->gid_set && !tblock->expand_gid)
log_write(0, LOG_PANIC_DIE|LOG_CONFIG,
"user set without group for the %s transport", tblock->name);
/* If "create_file" is set, check that a valid option is given, and set the
integer variable. */
-if (ob->create_file_string != NULL)
+if (ob->create_file_string)
{
int value = 0;
- if (Ustrcmp(ob->create_file_string, "anywhere") == 0) value = create_anywhere;
- else if (Ustrcmp(ob->create_file_string, "belowhome") == 0) value =
- create_belowhome;
+ if (Ustrcmp(ob->create_file_string, "anywhere") == 0)
+ value = create_anywhere;
+ else if (Ustrcmp(ob->create_file_string, "belowhome") == 0)
+ value = create_belowhome;
else if (Ustrcmp(ob->create_file_string, "inhome") == 0)
value = create_inhome;
else
not be used if the actual threshold for a given delivery ends up as zero,
of if it's given as a percentage and there's no quota setting. */
-if (ob->quota_warn_threshold != NULL)
+if (ob->quota_warn_threshold)
{
- if (tblock->warn_message == NULL) tblock->warn_message = US
+ if (!tblock->warn_message) tblock->warn_message = US
"To: $local_part@$domain\n"
"Subject: Your mailbox\n\n"
"This message is automatically created by mail delivery software (Exim).\n\n"
/* If not batch SMTP, not maildir, not mailstore, and directory is not set,
insert default values for for the affixes and the check/escape strings. */
-else if (ob->dirname == NULL && !ob->maildir_format && !ob->mailstore_format)
+else if (!ob->dirname && !ob->maildir_format && !ob->mailstore_format)
{
- if (ob->message_prefix == NULL) ob->message_prefix =
+ if (!ob->message_prefix) ob->message_prefix =
US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n";
- if (ob->message_suffix == NULL) ob->message_suffix = US"\n";
- if (ob->check_string == NULL) ob->check_string = US"From ";
- if (ob->escape_string == NULL) ob->escape_string = US">From ";
+ if (!ob->message_suffix) ob->message_suffix = US"\n";
+ if (!ob->check_string) ob->check_string = US"From ";
+ if (!ob->escape_string) ob->escape_string = US">From ";
}
driver options. Only one of body_only and headers_only can be set. */
ob->options |=
- (tblock->body_only? topt_no_headers : 0) |
- (tblock->headers_only? topt_no_body : 0) |
- (tblock->return_path_add? topt_add_return_path : 0) |
- (tblock->delivery_date_add? topt_add_delivery_date : 0) |
- (tblock->envelope_to_add? topt_add_envelope_to : 0) |
- ((ob->use_crlf || ob->mbx_format)? topt_use_crlf : 0);
+ (tblock->body_only ? topt_no_headers : 0) |
+ (tblock->headers_only ? topt_no_body : 0) |
+ (tblock->return_path_add ? topt_add_return_path : 0) |
+ (tblock->delivery_date_add ? topt_add_delivery_date : 0) |
+ (tblock->envelope_to_add ? topt_add_envelope_to : 0) |
+ ((ob->use_crlf || ob->mbx_format) ? topt_use_crlf : 0);
}
*/
static void
-notify_comsat(uschar *user, int offset)
+notify_comsat(uschar *user, off_t offset)
{
struct servent *sp;
host_item host;
-host_item *h;
-uschar buffer[256];
+uschar * s;
DEBUG(D_transport) debug_printf("notify_comsat called\n");
-sprintf(CS buffer, "%.200s@%d\n", user, offset);
+s = string_sprintf("%.200s@" OFF_T_FMT "\n", user, offset);
if ((sp = getservbyname("biff", "udp")) == NULL)
{
until one succeeds. However, it appears that at least on some systems, comsat
doesn't listen on the ::1 address. So for the moment, just force the address to
be 127.0.0.1. At some future stage, when IPv6 really is superseding IPv4, this
-can be changed. */
+can be changed. (But actually, comsat is probably dying out anyway.) */
/******
-if (host_find_byname(&host, NULL, NULL, FALSE) == HOST_FIND_FAILED)
+if (host_find_byname(&host, NULL, 0, NULL, FALSE) == HOST_FIND_FAILED)
{
DEBUG(D_transport) debug_printf("\"localhost\" unknown\n");
return;
host.address = US"127.0.0.1";
-for (h = &host; h != NULL; h = h->next)
+for (host_item * h = &host; h; h = h->next)
{
int sock, rc;
- int host_af = (Ustrchr(h->address, ':') != NULL)? AF_INET6 : AF_INET;
+ int host_af = Ustrchr(h->address, ':') != NULL ? AF_INET6 : AF_INET;
DEBUG(D_transport) debug_printf("calling comsat on %s\n", h->address);
- sock = ip_socket(SOCK_DGRAM, host_af);
- if (sock < 0) continue;
+ if ((sock = ip_socket(SOCK_DGRAM, host_af)) < 0) continue;
/* Connect never fails for a UDP socket, so don't set a timeout. */
- (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0);
- rc = send(sock, buffer, Ustrlen(buffer) + 1, 0);
- close(sock);
+ (void)ip_connect(sock, host_af, h->address, ntohs(sp->s_port), 0, NULL);
+ rc = send(sock, s, Ustrlen(s) + 1, 0);
+ (void)close(sock);
if (rc >= 0) break;
DEBUG(D_transport)
transport_instance *
check_file_format(int cfd, transport_instance *tblock, address_item *addr)
{
-uschar *format =
+const uschar *format =
((appendfile_transport_options_block *)(tblock->options_block))->file_format;
uschar data[256];
int len = read(cfd, data, sizeof(data));
/* Search the formats for a match */
-while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size))!= NULL)
+while ((s = string_nextinlist(&format,&sep,big_buffer,big_buffer_size)))
{
int slen = Ustrlen(s);
BOOL match = len >= slen && Ustrncmp(data, s, slen) == 0;
uschar *tp = string_nextinlist(&format, &sep, big_buffer, big_buffer_size);
- if (match)
+
+ if (match && tp)
{
- transport_instance *tt;
- for (tt = transports; tt != NULL; tt = tt->next)
+ for (transport_instance * tt = transports; tt; tt = tt->next)
if (Ustrcmp(tp, tt->name) == 0)
{
DEBUG(D_transport)
zero if the directory cannot be opened
*/
-int
-check_dir_size(uschar *dirname, int *countptr, const pcre *regex)
+off_t
+check_dir_size(const uschar * dirname, int *countptr, const pcre *regex)
{
DIR *dir;
-int sum = 0;
+off_t sum = 0;
int count = *countptr;
struct dirent *ent;
struct stat statbuf;
-dir = opendir(CS dirname);
-if (dir == NULL) return 0;
+if (!(dir = opendir(CS dirname))) return 0;
-while ((ent = readdir(dir)) != NULL)
+while ((ent = readdir(dir)))
{
- uschar *name = US ent->d_name;
- uschar buffer[1024];
+ uschar * path, * name = US ent->d_name;
if (Ustrcmp(name, ".") == 0 || Ustrcmp(name, "..") == 0) continue;
/* If there's a regex, try to find the size using it */
- if (regex != NULL)
+ if (regex)
{
int ovector[6];
if (pcre_exec(regex, NULL, CS name, Ustrlen(name), 0, 0, ovector,6) >= 2)
{
uschar *endptr;
- int size = Ustrtol(name + ovector[2], &endptr, 10);
+ off_t size = (off_t)Ustrtod(name + ovector[2], &endptr);
if (endptr == name + ovector[3])
{
sum += size;
DEBUG(D_transport)
- debug_printf("check_dir_size: size from %s is %d\n", name, size);
+ debug_printf("check_dir_size: size from %s is " OFF_T_FMT "\n", name,
+ size);
continue;
}
}
/* No regex or no match for the regex, or captured non-digits */
- if (!string_format(buffer, sizeof(buffer), "%s/%s", dirname, name))
- {
- DEBUG(D_transport)
- debug_printf("check_dir_size: name too long: dir=%s name=%s\n", dirname,
- name);
- continue;
- }
+ path = string_sprintf("%s/%s", dirname, name);
- if (Ustat(buffer, &statbuf) < 0)
+ if (Ustat(path, &statbuf) < 0)
{
DEBUG(D_transport)
- debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, buffer,
+ debug_printf("check_dir_size: stat error %d for %s: %s\n", errno, path,
strerror(errno));
- continue;
}
-
- if ((statbuf.st_mode & S_IFMT) == S_IFREG)
- sum += statbuf.st_size;
- else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
- sum += check_dir_size(buffer, &count, regex);
+ else
+ if ((statbuf.st_mode & S_IFMT) == S_IFREG)
+ sum += statbuf.st_size / statbuf.st_nlink;
+ else if ((statbuf.st_mode & S_IFMT) == S_IFDIR)
+ sum += check_dir_size(path, &count, regex);
}
closedir(dir);
DEBUG(D_transport)
- debug_printf("check_dir_size: dir=%s sum=%d count=%d\n", dirname, sum, count);
+ debug_printf("check_dir_size: dir=%s sum=" OFF_T_FMT " count=%d\n", dirname,
+ sum, count);
+
*countptr = count;
return sum;
}
{
if (fcntltime > 0)
{
- alarm(fcntltime);
+ ALARM(fcntltime);
yield = fcntl(fd, F_SETLKW, &lock_data);
save_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
errno = save_errno;
}
else yield = fcntl(fd, F_SETLK, &lock_data);
#ifndef NO_FLOCK
if (doflock && (yield >= 0))
{
- int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
+ int flocktype = (fcntltype == F_WRLCK) ? LOCK_EX : LOCK_SH;
if (flocktime > 0)
{
- alarm(flocktime);
+ ALARM(flocktime);
yield = flock(fd, flocktype);
save_errno = errno;
- alarm(0);
+ ALARM_CLR(0);
errno = save_errno;
}
else yield = flock(fd, flocktype | LOCK_NB);
#define MBX_NUSERFLAGS 30
static int
-copy_mbx_message(int to_fd, int from_fd, int saved_size)
+copy_mbx_message(int to_fd, int from_fd, off_t saved_size)
{
-int used, size;
+int used;
+off_t size;
struct stat statbuf;
+transport_ctx tctx = { .u={.fd = to_fd}, .options = topt_not_socket };
/* If the current mailbox size is zero, write a header block */
if (saved_size == 0)
{
- int i;
uschar *s;
memset (deliver_out_buffer, '\0', MBX_HDRSIZE);
sprintf(CS(s = deliver_out_buffer), "*mbx*\015\012%08lx00000000\015\012",
(long int)time(NULL));
- for (i = 0; i < MBX_NUSERFLAGS; i++)
+ for (int i = 0; i < MBX_NUSERFLAGS; i++)
sprintf (CS(s += Ustrlen(s)), "\015\012");
- if (!transport_write_block (to_fd, deliver_out_buffer, MBX_HDRSIZE))
+ if (!transport_write_block (&tctx, deliver_out_buffer, MBX_HDRSIZE, FALSE))
return DEFER;
}
if (fstat(from_fd, &statbuf) < 0) return DEFER;
size = statbuf.st_size;
-sprintf (CS deliver_out_buffer, "%s,%lu;%08lx%04x-%08x\015\012",
- tod_stamp(tod_mbx), (long unsigned int)size, 0L, 0, 0);
+sprintf (CS deliver_out_buffer, "%s," OFF_T_FMT ";%08lx%04x-%08x\015\012",
+ tod_stamp(tod_mbx), size, 0L, 0, 0);
used = Ustrlen(deliver_out_buffer);
/* Rewind the temporary file, and copy it over in chunks. */
-lseek(from_fd, 0 , SEEK_SET);
+if (lseek(from_fd, 0 , SEEK_SET) < 0) return DEFER;
while (size > 0)
{
if (len == 0) errno = ERRNO_MBXLENGTH;
return DEFER;
}
- if (!transport_write_block(to_fd, deliver_out_buffer, used + len))
+ if (!transport_write_block(&tctx, deliver_out_buffer, used + len, FALSE))
return DEFER;
size -= len;
used = 0;
{
BOOL yield = TRUE;
-if (deliver_home != NULL && create_file != create_anywhere)
+if (deliver_home && create_file != create_anywhere)
{
int len = Ustrlen(deliver_home);
uschar *file = filename;
#ifndef NO_REALPATH
if (yield && create_file == create_belowhome)
{
- uschar *slash, *next;
+ uschar *next;
uschar *rp = NULL;
- for (slash = Ustrrchr(file, '/'); /* There is known to be one */
- rp == NULL && slash > file; /* Stop if reached beginning */
+ for (uschar * slash = Ustrrchr(file, '/'); /* There is known to be one */
+ !rp && slash > file; /* Stop if reached beginning */
slash = next)
{
*slash = 0;
contain symbolic links, so we have to "realpath" it as well, if
possible. */
- if (rp != NULL)
+ if (rp)
{
uschar hdbuffer[PATH_MAX+1];
uschar *rph = deliver_home;
int rlen = Ustrlen(big_buffer);
- rp = US realpath(CS deliver_home, CS hdbuffer);
- if (rp != NULL)
+ if ((rp = US realpath(CS deliver_home, CS hdbuffer)))
{
rph = hdbuffer;
len = Ustrlen(rph);
Open with O_WRONLY + O_EXCL + O_CREAT with configured mode, unless we know
this is via a symbolic link (only possible if allow_symlinks is set), in
- which case don't use O_EXCL, as it dosn't work.
+ which case don't use O_EXCL, as it doesn't work.
If open fails because the file already exists, go to (6f). To avoid
looping for ever in a situation where the file is continuously being
uschar *path;
struct utimbuf times;
struct timeval msg_tv;
+BOOL disable_quota = FALSE;
BOOL isdirectory = FALSE;
BOOL isfifo = FALSE;
BOOL wait_for_tick = FALSE;
uid_t uid = geteuid(); /* See note above */
gid_t gid = getegid();
int mbformat;
-int mode = (addr->mode > 0)? addr->mode : ob->mode;
-int saved_size = -1;
-int mailbox_size = ob->mailbox_size_value;
+int mode = (addr->mode > 0) ? addr->mode : ob->mode;
+off_t saved_size = -1;
+off_t mailbox_size = ob->mailbox_size_value;
int mailbox_filecount = ob->mailbox_filecount_value;
int hd = -1;
int fd = -1;
$address_file variable. Below, we update the local part in the address if it
changes by expansion, so that the final path ends up in the log. */
-if (testflag(addr, af_file) &&
- ob->filename == NULL &&
- ob->dirname == NULL)
+if (testflag(addr, af_file) && !ob->filename && !ob->dirname)
{
fdname = US"$address_file";
if (address_file[Ustrlen(address_file)-1] == '/' ||
explicitly set and (b) a non-address_file delivery, where one of "file" or
"directory" must be set; initialization ensures that they are not both set. */
-if (fdname == NULL)
+if (!fdname)
{
- fdname = ob->filename;
- if (fdname == NULL)
+ if (!(fdname = ob->filename))
{
fdname = ob->dirname;
isdirectory = TRUE;
}
- if (fdname == NULL)
+ if (!fdname)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Mandatory file or directory option "
addr->transport_return = PANIC;
addr->message = string_sprintf("mail%s_format requires \"directory\" "
"to be specified for the %s transport",
- ob->maildir_format? "dir" : "store", tblock->name);
+ ob->maildir_format ? "dir" : "store", tblock->name);
return FALSE;
}
-path = expand_string(fdname);
-
-if (path == NULL)
+if (!(path = expand_string(fdname)))
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (file or directory "
return FALSE;
}
-/* For a file delivery, make sure the local part in the address is updated to
-the true local part. */
+/* For a file delivery, make sure the local part in the address(es) is updated
+to the true local part. */
-if (testflag(addr, af_file)) addr->local_part = string_copy(path);
+if (testflag(addr, af_file))
+ for (address_item * addr2 = addr; addr2 != NULL; addr2 = addr2->next)
+ addr2->local_part = string_copy(path);
/* The available mailbox formats depend on whether it is a directory or a file
delivery. */
{
mbformat =
#ifdef SUPPORT_MAILDIR
- (ob->maildir_format)? mbf_maildir :
+ ob->maildir_format ? mbf_maildir :
#endif
#ifdef SUPPORT_MAILSTORE
- (ob->mailstore_format)? mbf_mailstore :
+ ob->mailstore_format ? mbf_mailstore :
#endif
mbf_smail;
}
{
mbformat =
#ifdef SUPPORT_MBX
- (ob->mbx_format)? mbf_mbx :
+ ob->mbx_format ? mbf_mbx :
#endif
mbf_unix;
}
DEBUG(D_transport)
{
- debug_printf("appendfile: mode=%o notify_comsat=%d quota=%d warning=%d%s\n"
+ debug_printf("appendfile: mode=%o notify_comsat=%d quota=" OFF_T_FMT
+ "%s%s"
+ " warning=" OFF_T_FMT "%s\n"
" %s=%s format=%s\n message_prefix=%s\n message_suffix=%s\n "
"maildir_use_size_file=%s\n",
- mode, ob->notify_comsat, ob->quota_value, ob->quota_warn_threshold_value,
- ob->quota_warn_threshold_is_percent? "%" : "",
- isdirectory? "directory" : "file",
+ mode, ob->notify_comsat, ob->quota_value,
+ ob->quota_no_check ? " (no_check)" : "",
+ ob->quota_filecount_no_check ? " (no_check_filecount)" : "",
+ ob->quota_warn_threshold_value,
+ ob->quota_warn_threshold_is_percent ? "%" : "",
+ isdirectory ? "directory" : "file",
path, mailbox_formats[mbformat],
- (ob->message_prefix == NULL)? US"null" : string_printing(ob->message_prefix),
- (ob->message_suffix == NULL)? US"null" : string_printing(ob->message_suffix),
- (ob->maildir_use_size_file)? "yes" : "no");
+ !ob->message_prefix ? US"null" : string_printing(ob->message_prefix),
+ !ob->message_suffix ? US"null" : string_printing(ob->message_suffix),
+ ob->maildir_use_size_file ? "yes" : "no");
if (!isdirectory) debug_printf(" locking by %s%s%s%s%s\n",
- ob->use_lockfile? "lockfile " : "",
- ob->use_mbx_lock? "mbx locking (" : "",
- ob->use_fcntl? "fcntl " : "",
- ob->use_flock? "flock" : "",
- ob->use_mbx_lock? ")" : "");
+ ob->use_lockfile ? "lockfile " : "",
+ ob->use_mbx_lock ? "mbx locking (" : "",
+ ob->use_fcntl ? "fcntl " : "",
+ ob->use_flock ? "flock" : "",
+ ob->use_mbx_lock ? ")" : "");
}
/* If the -N option is set, can't do any more. */
-if (dont_deliver)
+if (f.dont_deliver)
{
DEBUG(D_transport)
debug_printf("*** delivery by %s transport bypassed by -N option\n",
failures because if an existing file fails to open here, it will also fail
again later when O_RDWR is used. */
- if (ob->file_format != NULL)
+ if (ob->file_format)
{
int cfd = Uopen(path, O_RDONLY, 0);
if (cfd >= 0)
{
transport_instance *tt = check_file_format(cfd, tblock, addr);
- close(cfd);
+ (void)close(cfd);
/* If another transport is indicated, call it and return; if no transport
was found, just return - the error data will have been set up.*/
if (tt != tblock)
{
- if (tt != NULL)
+ if (tt)
{
set_process_info("delivering %s to %s using %s", message_id,
addr->local_part, tt->name);
if (ob->use_lockfile)
{
+ /* cf. exim_lock.c */
lockname = string_sprintf("%s.lock", filename);
hitchname = string_sprintf( "%s.%s.%08x.%08x", lockname, primary_hostname,
(unsigned int)(time(NULL)), (unsigned int)getpid());
for (i = 0; i < ob->lock_retries; sleep(ob->lock_interval), i++)
{
int rc;
- hd = Uopen(hitchname, O_WRONLY | O_CREAT | O_EXCL, ob->lockfile_mode);
+ hd = Uopen(hitchname, O_WRONLY | O_CREAT | O_EXCL, ob->lockfile_mode);
if (hd < 0)
{
addr->basic_errno = errno;
sufficiently worried. */
if ((rc = Ulink(hitchname, lockname)) != 0) fstat(hd, &statbuf);
- close(hd);
+ (void)close(hd);
Uunlink(hitchname);
if (rc != 0 && statbuf.st_nlink != 2)
{
int sleep_before_retry = TRUE;
file_opened = FALSE;
- if((use_lstat? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
+ if ((use_lstat ? Ulstat(filename, &statbuf) : Ustat(filename, &statbuf)) != 0)
{
/* Let's hope that failure to stat (other than non-existence) is a
rare event. */
get a shared lock. */
fd = Uopen(filename, O_RDWR | O_APPEND | O_CREAT |
- (use_lstat? O_EXCL : 0), mode);
+ (use_lstat ? O_EXCL : 0), mode);
if (fd < 0)
{
if (errno == EEXIST) continue;
/* We have successfully created and opened the file. Ensure that the group
and the mode are correct. */
- Uchown(filename, uid, gid);
- Uchmod(filename, mode);
+ if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+ {
+ addr->basic_errno = errno;
+ addr->message = string_sprintf("while setting perms on mailbox %s",
+ filename);
+ addr->transport_return = FAIL;
+ goto RETURN;
+ }
}
addr->basic_errno = ERRNO_BADUGID;
addr->message = string_sprintf("mailbox %s%s has wrong uid "
"(%ld != %ld)", filename,
- islink? " (symlink)" : "",
+ islink ? " (symlink)" : "",
(long int)(statbuf.st_uid), (long int)uid);
goto RETURN;
}
{
addr->basic_errno = ERRNO_BADUGID;
addr->message = string_sprintf("mailbox %s%s has wrong gid (%d != %d)",
- filename, islink? " (symlink)" : "", statbuf.st_gid, gid);
+ filename, islink ? " (symlink)" : "", statbuf.st_gid, gid);
+ goto RETURN;
+ }
+
+ /* Just in case this is a sticky-bit mail directory, we don't want
+ users to be able to create hard links to other users' files. */
+
+ if (statbuf.st_nlink != 1)
+ {
+ addr->basic_errno = ERRNO_NOTREGULAR;
+ addr->message = string_sprintf("mailbox %s%s has too many links (%d)",
+ filename, islink ? " (symlink)" : "", statbuf.st_nlink);
goto RETURN;
+
}
/* If symlinks are permitted (not recommended), the lstat() above will
{
addr->basic_errno = ERRNO_NOTREGULAR;
addr->message = string_sprintf("mailbox %s is not a regular file%s",
- filename, ob->allow_fifo? " or named pipe" : "");
+ filename, ob->allow_fifo ? " or named pipe" : "");
goto RETURN;
}
permissions are greater than the existing permissions, don't change
things when the mode is not from the address. */
- if ((oldmode = (oldmode & 07777)) != mode)
+ if ((oldmode &= 07777) != mode)
{
int diffs = oldmode ^ mode;
if (addr->mode > 0 || (diffs & oldmode) == diffs)
a FIFO is opened WRONLY + NDELAY so that it fails if there is no process
reading the pipe. */
- fd = Uopen(filename, isfifo? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
+ fd = Uopen(filename, isfifo ? (O_WRONLY|O_NDELAY) : (O_RDWR|O_APPEND),
mode);
if (fd < 0)
{
}
addr->basic_errno = errno;
if (isfifo)
- {
addr->message = string_sprintf("while opening named pipe %s "
"(could mean no process is reading it)", filename);
- }
else if (errno != EWOULDBLOCK)
- {
addr->message = string_sprintf("while opening mailbox %s", filename);
- }
goto RETURN;
}
{
addr->basic_errno = ERRNO_INODECHANGED;
addr->message = string_sprintf("opened mailbox %s inode number changed "
- "from %d to %ld", filename, inode, statbuf.st_ino);
+ "from " INO_T_FMT " to " INO_T_FMT, filename, inode, statbuf.st_ino);
addr->special_action = SPECIAL_FREEZE;
goto RETURN;
}
addr->basic_errno = ERRNO_NOTREGULAR;
addr->message =
string_sprintf("opened mailbox %s is no longer a %s", filename,
- isfifo? "named pipe" : "regular file");
+ isfifo ? "named pipe" : "regular file");
addr->special_action = SPECIAL_FREEZE;
goto RETURN;
}
/* If file_format is set, check that the format of the file has not
changed. Error data is set by the testing function. */
- if (ob->file_format != NULL &&
- check_file_format(fd, tblock, addr) != tblock)
+ if (ob->file_format && check_file_format(fd, tblock, addr) != tblock)
{
addr->message = US"open mailbox has changed format";
goto RETURN;
#ifdef SUPPORT_MBX
else if (ob->use_mbx_lock)
{
+ int mbx_tmp_oflags;
+ struct stat lstatbuf, statbuf2;
if (apply_lock(fd, F_RDLCK, ob->use_fcntl, ob->lock_fcntl_timeout,
ob->use_flock, ob->lock_flock_timeout) >= 0 &&
fstat(fd, &statbuf) >= 0)
sprintf(CS mbx_lockname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
(long)statbuf.st_ino);
+ /*
+ * 2010-05-29: SECURITY
+ * Dan Rosenberg reported the presence of a race-condition in the
+ * original code here. Beware that many systems still allow symlinks
+ * to be followed in /tmp so an attacker can create a symlink pointing
+ * elsewhere between a stat and an open, which we should avoid
+ * following.
+ *
+ * It's unfortunate that we can't just use all the heavily debugged
+ * locking from above.
+ *
+ * Also: remember to mirror changes into exim_lock.c */
+
+ /* first leave the old pre-check in place, it provides better
+ * diagnostics for common cases */
if (Ulstat(mbx_lockname, &statbuf) >= 0)
{
if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
}
}
- mbx_lockfd = Uopen(mbx_lockname, O_RDWR | O_CREAT, 0600);
+ /* If we could just declare "we must be the ones who create this
+ * file" then a hitching post in a subdir would work, since a
+ * subdir directly in /tmp/ which we create wouldn't follow links
+ * but this isn't our locking logic, so we can't safely change the
+ * file existence rules. */
+
+ /* On systems which support O_NOFOLLOW, it's the easiest and most
+ * obviously correct security fix */
+ mbx_tmp_oflags = O_RDWR | O_CREAT;
+#ifdef O_NOFOLLOW
+ mbx_tmp_oflags |= O_NOFOLLOW;
+#endif
+ mbx_lockfd = Uopen(mbx_lockname, mbx_tmp_oflags, ob->lockfile_mode);
if (mbx_lockfd < 0)
{
addr->basic_errno = ERRNO_LOCKFAILED;
goto RETURN;
}
- Uchmod(mbx_lockname, 0600);
+ if (Ulstat(mbx_lockname, &lstatbuf) < 0)
+ {
+ addr->basic_errno = ERRNO_LOCKFAILED;
+ addr->message = string_sprintf("attempting to lstat open MBX "
+ "lock file %s: %s", mbx_lockname, strerror(errno));
+ goto RETURN;
+ }
+ if (fstat(mbx_lockfd, &statbuf2) < 0)
+ {
+ addr->basic_errno = ERRNO_LOCKFAILED;
+ addr->message = string_sprintf("attempting to stat fd of open MBX "
+ "lock file %s: %s", mbx_lockname, strerror(errno));
+ goto RETURN;
+ }
+
+ /*
+ * At this point:
+ * statbuf: if exists, is file which existed prior to opening the
+ * lockfile, might have been replaced since then
+ * statbuf2: result of stat'ing the open fd, is what was actually
+ * opened
+ * lstatbuf: result of lstat'ing the filename immediately after
+ * the open but there's a race condition again between
+ * those two steps: before open, symlink to foo, after
+ * open but before lstat have one of:
+ * * was no symlink, so is the opened file
+ * (we created it, no messing possible after that point)
+ * * hardlink to foo
+ * * symlink elsewhere
+ * * hardlink elsewhere
+ * * new file/other
+ * Don't want to compare to device of /tmp because some modern systems
+ * have regressed to having /tmp be the safe actual filesystem as
+ * valuable data, so is mostly worthless, unless we assume that *only*
+ * Linux systems do this and that all Linux has O_NOFOLLOW. Something
+ * for further consideration.
+ * No point in doing a readlink on the lockfile as that will always be
+ * at a different point in time from when we open it, so tells us
+ * nothing; attempts to clean up and delete after ourselves would risk
+ * deleting a *third* filename.
+ */
+ if ((statbuf2.st_nlink > 1) ||
+ (lstatbuf.st_nlink > 1) ||
+ (!S_ISREG(lstatbuf.st_mode)) ||
+ (lstatbuf.st_dev != statbuf2.st_dev) ||
+ (lstatbuf.st_ino != statbuf2.st_ino))
+ {
+ addr->basic_errno = ERRNO_LOCKFAILED;
+ addr->message = string_sprintf("RACE CONDITION detected: "
+ "mismatch post-initial-checks between \"%s\" and opened "
+ "fd lead us to abort!", mbx_lockname);
+ goto RETURN;
+ }
+
+ (void)Uchmod(mbx_lockname, ob->lockfile_mode);
if (apply_lock(mbx_lockfd, F_WRLCK, ob->use_fcntl,
ob->lock_fcntl_timeout, ob->use_flock, ob->lock_flock_timeout) >= 0)
DEBUG(D_transport) debug_printf("failed to lock %s: %s\n", mbx_lockname,
strerror(errno));
- close(mbx_lockfd);
+ (void)close(mbx_lockfd);
mbx_lockfd = -1;
}
else
DEBUG(D_transport)
debug_printf("fcntl(), flock(), or MBX locking failed - retrying\n");
- close(fd);
+ (void)close(fd);
fd = -1;
use_lstat = TRUE; /* Reset to use lstat first */
}
#ifdef SUPPORT_MAILDIR
- /* For a maildir delivery, ensure that all the relevant directories exist */
+ /* For a maildir delivery, ensure that all the relevant directories exist,
+ and a maildirfolder file if necessary. */
if (mbformat == mbf_maildir && !maildir_ensure_directories(path, addr,
- ob->create_directory, ob->dirmode))
+ ob->create_directory, ob->dirmode, ob->maildirfolder_create_regex))
return FALSE;
#endif /* SUPPORT_MAILDIR */
const uschar *error;
int offset;
- /* Compile the regex if there is one */
+ /* Compile the regex if there is one. */
- if (ob->quota_size_regex != NULL)
+ if (ob->quota_size_regex)
{
- regex = pcre_compile(CS ob->quota_size_regex, PCRE_COPT,
- (const char **)&error, &offset, NULL);
- if (regex == NULL)
+ if (!(regex = pcre_compile(CS ob->quota_size_regex, PCRE_COPT,
+ CCSS &error, &offset, NULL)))
{
addr->message = string_sprintf("appendfile: regular expression "
"error: %s at offset %d while compiling %s", error, offset,
ob->quota_size_regex);
return FALSE;
}
- else
- {
- DEBUG(D_transport) debug_printf("using regex for file sizes: %s\n",
- ob->quota_size_regex);
- }
+ DEBUG(D_transport) debug_printf("using regex for file sizes: %s\n",
+ ob->quota_size_regex);
}
/* Use an explicitly configured directory if set */
- if (ob->quota_directory != NULL)
+ if (ob->quota_directory)
{
- check_path = expand_string(ob->quota_directory);
- if (check_path == NULL)
+ if (!(check_path = expand_string(ob->quota_directory)))
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (quota_directory "
{
*slash = 0;
check_path = new_check_path;
+ DEBUG(D_transport) debug_printf("maildirfolder file exists: "
+ "quota check directory changed to %s\n", check_path);
}
}
}
const uschar *error;
int offset;
- if (ob->maildir_dir_regex != NULL)
+ if (ob->maildir_dir_regex)
{
- dir_regex = pcre_compile(CS ob->maildir_dir_regex, PCRE_COPT,
- (const char **)&error, &offset, NULL);
- if (dir_regex == NULL)
+ int check_path_len = Ustrlen(check_path);
+
+ if (!(dir_regex = pcre_compile(CS ob->maildir_dir_regex, PCRE_COPT,
+ CCSS &error, &offset, NULL)))
{
addr->message = string_sprintf("appendfile: regular expression "
"error: %s at offset %d while compiling %s", error, offset,
ob->maildir_dir_regex);
return FALSE;
}
- else
+
+ DEBUG(D_transport)
+ debug_printf("using regex for maildir directory selection: %s\n",
+ ob->maildir_dir_regex);
+
+ /* Check to see if we are delivering into an ignored directory, that is,
+ if the delivery path starts with the quota check path, and the rest
+ of the deliver path matches the regex; if so, set a flag to disable quota
+ checking and maildirsize updating. */
+
+ if (Ustrncmp(path, check_path, check_path_len) == 0)
{
- DEBUG(D_transport)
- debug_printf("using regex for maildir directory selection: %s\n",
- ob->maildir_dir_regex);
+ uschar *s = path + check_path_len;
+ while (*s == '/') s++;
+ s = (*s == 0) ? US "new" : string_sprintf("%s/new", s);
+ if (pcre_exec(dir_regex, NULL, CS s, Ustrlen(s), 0, 0, NULL, 0) < 0)
+ {
+ disable_quota = TRUE;
+ DEBUG(D_transport) debug_printf("delivery directory does not match "
+ "maildir_quota_directory_regex: disabling quota\n");
+ }
}
}
/* if (???? || ob->quota_value > 0) */
+ if (!disable_quota)
{
- int size, filecount;
+ off_t size;
+ int filecount;
- maildirsize_fd = maildir_ensure_sizefile(check_path, ob, regex, dir_regex,
- &size, &filecount);
-
- if (maildirsize_fd == -1)
+ if ((maildirsize_fd = maildir_ensure_sizefile(check_path, ob, regex, dir_regex,
+ &size, &filecount)) == -1)
{
addr->basic_errno = errno;
addr->message = string_sprintf("while opening or reading "
"%s/maildirsize", check_path);
return FALSE;
}
+ /* can also return -2, which means that the file was removed because of
+ raciness; but in this case, the size & filecount will still have been
+ updated. */
if (mailbox_size < 0) mailbox_size = size;
if (mailbox_filecount < 0) mailbox_filecount = filecount;
}
#endif /* SUPPORT_MAILDIR */
- /* Otherwise (mailbox_size is not yet set), if we are going to do a quota
- check later on, find the current size of the mailbox. (We don't need to check
- ob->quota_filecount_value, because it can only be set if ob->quota_value is
- set.) */
+ /* Otherwise if we are going to do a quota check later on, and the mailbox
+ size is not set, find the current size of the mailbox. Ditto for the file
+ count. Note that ob->quota_filecount_value cannot be set without
+ ob->quota_value being set. */
- if ((mailbox_size < 0 || mailbox_filecount < 0) &&
- (ob->quota_value > 0 || THRESHOLD_CHECK))
+ if (!disable_quota &&
+ (ob->quota_value > 0 || THRESHOLD_CHECK) &&
+ (mailbox_size < 0 ||
+ (mailbox_filecount < 0 && ob->quota_filecount_value > 0)))
{
- int size;
+ off_t size;
int filecount = 0;
DEBUG(D_transport)
debug_printf("quota checks on directory %s\n", check_path);
return. The actual expansion for use happens again later, when
$message_size is accurately known. */
- if (nametag != NULL && expand_string(nametag) == NULL &&
- !expand_string_forcedfail)
+ if (nametag && !expand_string(nametag) && !f.expand_string_forcedfail)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (maildir_tag "
checked at the end, to make sure we don't release this process until the
clock has ticked. */
- for (i = 1;; i++)
+ for (int i = 1;; i++)
{
uschar *basename;
(void)gettimeofday(&msg_tv, NULL);
- basename = string_sprintf("%lu.H%luP%lu.%s", msg_tv.tv_sec,
- msg_tv.tv_usec, getpid(), primary_hostname);
+ basename = string_sprintf(TIME_T_FMT ".H%luP" PID_T_FMT ".%s",
+ msg_tv.tv_sec, msg_tv.tv_usec, getpid(), primary_hostname);
filename = dataname = string_sprintf("tmp/%s", basename);
newname = string_sprintf("new/%s", basename);
errno = EEXIST;
else if (errno == ENOENT)
{
- fd = Uopen(filename, O_WRONLY | O_CREAT | O_EXCL, mode);
- if (fd >= 0) break;
+ if ((fd = Uopen(filename, O_WRONLY | O_CREAT | O_EXCL, mode)) >= 0)
+ break;
DEBUG (D_transport) debug_printf ("open failed for %s: %s\n",
filename, strerror(errno));
}
if (i >= ob->maildir_retries)
{
addr->message = string_sprintf ("failed to open %s (%d tr%s)",
- filename, i, (i == 1)? "y" : "ies");
+ filename, i, (i == 1) ? "y" : "ies");
addr->basic_errno = errno;
+ if (errno == errno_quota || errno == ENOSPC)
+ addr->user_message = US"mailbox is full";
return FALSE;
}
/* Why are these here? Put in because they are present in the non-maildir
directory case above. */
- Uchown(filename, uid, gid);
- Uchmod(filename, mode);
+ if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+ {
+ addr->basic_errno = errno;
+ addr->message = string_sprintf("while setting perms on maildir %s",
+ filename);
+ return FALSE;
+ }
}
#endif /* SUPPORT_MAILDIR */
else
{
FILE *env_file;
- address_item *taddr;
mailstore_basename = string_sprintf("%s/%s-%s", path, message_id,
string_base62((long int)getpid()));
/* Why are these here? Put in because they are present in the non-maildir
directory case above. */
- Uchown(filename, uid, gid);
- Uchmod(filename, mode);
+ if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+ {
+ addr->basic_errno = errno;
+ addr->message = string_sprintf("while setting perms on file %s",
+ filename);
+ return FALSE;
+ }
/* Built a C stream from the open file descriptor. */
- if ((env_file = fdopen(fd, "wb")) == NULL)
+ if (!(env_file = fdopen(fd, "wb")))
{
addr->basic_errno = errno;
addr->transport_return = PANIC;
addr->message = string_sprintf("fdopen of %s ("
"for %s transport) failed", filename, tblock->name);
- close(fd);
+ (void)close(fd);
Uunlink(filename);
return FALSE;
}
/* Write the envelope file, then close it. */
- if (ob->mailstore_prefix != NULL)
+ if (ob->mailstore_prefix)
{
uschar *s = expand_string(ob->mailstore_prefix);
- if (s == NULL)
+ if (!s)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
"prefix for %s transport) failed: %s", ob->mailstore_prefix,
tblock->name, expand_string_message);
- fclose(env_file);
+ (void)fclose(env_file);
Uunlink(filename);
return FALSE;
}
fprintf(env_file, "%s\n", sender_address);
- for (taddr = addr; taddr!= NULL; taddr = taddr->next)
+ for (address_item * taddr = addr; taddr; taddr = taddr->next)
fprintf(env_file, "%s@%s\n", taddr->local_part, taddr->domain);
- if (ob->mailstore_suffix != NULL)
+ if (ob->mailstore_suffix)
{
uschar *s = expand_string(ob->mailstore_suffix);
- if (s == NULL)
+ if (!s)
{
- if (!expand_string_forcedfail)
+ if (!f.expand_string_forcedfail)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" (mailstore "
"suffix for %s transport) failed: %s", ob->mailstore_suffix,
tblock->name, expand_string_message);
- fclose(env_file);
+ (void)fclose(env_file);
Uunlink(filename);
return FALSE;
}
/* Now open the data file, and ensure that it has the correct ownership and
mode. */
- fd = Uopen(dataname, O_WRONLY|O_CREAT|O_EXCL, mode);
- if (fd < 0)
+ if ((fd = Uopen(dataname, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
{
addr->basic_errno = errno;
addr->message = string_sprintf("while creating file %s", dataname);
Uunlink(filename);
return FALSE;
}
- Uchown(dataname, uid, gid);
- Uchmod(dataname, mode);
+ if (exim_chown(dataname, uid, gid) || Uchmod(dataname, mode))
+ {
+ addr->basic_errno = errno;
+ addr->message = string_sprintf("while setting perms on file %s",
+ dataname);
+ return FALSE;
+ }
}
#endif /* SUPPORT_MAILSTORE */
/* In all cases of writing to a new file, ensure that the file which is
going to be renamed has the correct ownership and mode. */
- Uchown(filename, uid, gid);
- Uchmod(filename, mode);
+ if (exim_chown(filename, uid, gid) || Uchmod(filename, mode))
+ {
+ addr->basic_errno = errno;
+ addr->message = string_sprintf("while setting perms on file %s",
+ filename);
+ return FALSE;
+ }
}
is for the mailbox already being over quota (i.e. the current message is not
included in the check). */
-if (ob->quota_value > 0)
+if (!disable_quota && ob->quota_value > 0)
{
DEBUG(D_transport)
{
- debug_printf("Exim quota = %d old size = %d this message = %d "
- "(%sincluded)\n", ob->quota_value, mailbox_size, message_size,
- ob->quota_is_inclusive? "" : "not ");
+ debug_printf("Exim quota = " OFF_T_FMT " old size = " OFF_T_FMT
+ " this message = %d (%sincluded)\n",
+ ob->quota_value, mailbox_size, message_size,
+ ob->quota_is_inclusive ? "" : "not ");
debug_printf(" file count quota = %d count = %d\n",
ob->quota_filecount_value, mailbox_filecount);
}
- if (mailbox_size + (ob->quota_is_inclusive? message_size:0) > ob->quota_value)
- {
- DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
- yield = DEFER;
- errno = ERRNO_EXIMQUOTA;
- }
- else if (ob->quota_filecount_value > 0 &&
- mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
- ob->quota_filecount_value)
- {
- DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
- yield = DEFER;
- errno = ERRNO_EXIMQUOTA;
- filecount_msg = US" filecount";
- }
+
+ if (mailbox_size + (ob->quota_is_inclusive ? message_size:0) > ob->quota_value)
+ if (!ob->quota_no_check)
+ {
+ DEBUG(D_transport) debug_printf("mailbox quota exceeded\n");
+ yield = DEFER;
+ errno = ERRNO_EXIMQUOTA;
+ }
+ else
+ DEBUG(D_transport) debug_printf("mailbox quota exceeded but ignored\n");
+
+ if (ob->quota_filecount_value > 0
+ && mailbox_filecount + (ob->quota_is_inclusive ? 1:0) >
+ ob->quota_filecount_value)
+ if (!ob->quota_filecount_no_check)
+ {
+ DEBUG(D_transport) debug_printf("mailbox file count quota exceeded\n");
+ yield = DEFER;
+ errno = ERRNO_EXIMQUOTA;
+ filecount_msg = US" filecount";
+ }
+ else DEBUG(D_transport) if (ob->quota_filecount_no_check)
+ debug_printf("mailbox file count quota exceeded but ignored\n");
+
}
/* If we are writing in MBX format, what we actually do is to write the message
#ifdef SUPPORT_MBX
if (yield == OK && ob->mbx_format)
{
- temp_file = tmpfile();
- if (temp_file == NULL)
+ if (!(temp_file = tmpfile()))
{
addr->basic_errno = errno;
addr->message = US"while setting up temporary file";
functions. */
transport_count = 0;
+transport_newlines = 0;
/* Write any configured prefix text first */
-if (yield == OK && ob->message_prefix != NULL && ob->message_prefix[0] != 0)
+if (yield == OK && ob->message_prefix && *ob->message_prefix)
{
uschar *prefix = expand_string(ob->message_prefix);
- if (prefix == NULL)
+ if (!prefix)
{
errno = ERRNO_EXPANDFAIL;
addr->transport_return = PANIC;
if (yield == OK && ob->use_bsmtp)
{
transport_count = 0;
+ transport_newlines = 0;
if (ob->use_crlf) cr = US"\r";
if (!transport_write_string(fd, "MAIL FROM:<%s>%s\n", return_path, cr))
yield = DEFER;
else
{
- address_item *a;
- for (a = addr; a != NULL; a = a->next)
+ transport_newlines++;
+ for (address_item * a = addr; a; a = a->next)
{
- address_item *b = testflag(a, af_pfr)? a->parent: a;
+ address_item * b = testflag(a, af_pfr) ? a->parent : a;
if (!transport_write_string(fd, "RCPT TO:<%s>%s\n",
transport_rcpt_address(b, tblock->rcpt_include_affixes), cr))
{ yield = DEFER; break; }
+ transport_newlines++;
}
if (yield == OK && !transport_write_string(fd, "DATA%s\n", cr))
yield = DEFER;
+ else
+ transport_newlines++;
}
}
if (yield == OK)
{
- if (!transport_write_message(addr, fd, ob->options, 0, tblock->add_headers,
- tblock->remove_headers, ob->check_string, ob->escape_string,
- tblock->rewrite_rules, tblock->rewrite_existflags))
+ transport_ctx tctx = {
+ .u = {.fd=fd},
+ .tblock = tblock,
+ .addr = addr,
+ .check_string = ob->check_string,
+ .escape_string = ob->escape_string,
+ .options = ob->options | topt_not_socket
+ };
+ if (!transport_write_message(&tctx, 0))
yield = DEFER;
}
/* Now a configured suffix. */
-if (yield == OK && ob->message_suffix != NULL && ob->message_suffix[0] != 0)
+if (yield == OK && ob->message_suffix && *ob->message_suffix)
{
uschar *suffix = expand_string(ob->message_suffix);
- if (suffix == NULL)
+ if (!suffix)
{
errno = ERRNO_EXPANDFAIL;
addr->transport_return = PANIC;
/* If batch smtp, write the terminating dot. */
-if (yield == OK && ob->use_bsmtp &&
- !transport_write_string(fd, ".%s\n", cr)) yield = DEFER;
+if (yield == OK && ob->use_bsmtp)
+ if (!transport_write_string(fd, ".%s\n", cr)) yield = DEFER;
+ else transport_newlines++;
/* If MBX format is being used, all that writing was to the temporary file.
However, if there was an earlier failure (Exim quota exceeded, for example),
any messages. */
#ifdef SUPPORT_MBX
-if (temp_file != NULL && ob->mbx_format)
+if (temp_file && ob->mbx_format)
{
int mbx_save_errno;
fd = save_fd;
if (yield == OK)
{
transport_count = 0; /* Reset transport count for actual write */
+ /* No need to reset transport_newlines as we're just using a block copy
+ * routine so the number won't be affected */
yield = copy_mbx_message(fd, fileno(temp_file), saved_size);
}
else if (errno >= 0) dataname = US"temporary file";
/* Preserve errno while closing the temporary file. */
mbx_save_errno = errno;
- fclose(temp_file);
+ (void)fclose(temp_file);
errno = mbx_save_errno;
}
#endif /* SUPPORT_MBX */
/* Force out the remaining data to check for any errors; some OS don't allow
fsync() to be called for a FIFO. */
-if (yield == OK && !isfifo && fsync(fd) < 0) yield = DEFER;
+if (yield == OK && !isfifo && EXIMfsync(fd) < 0) yield = DEFER;
-/* Update message_size to the accurate count of bytes written, including
-added headers. */
+/* Update message_size and message_linecount to the accurate count of bytes
+written, including added headers. Note; we subtract 1 from message_linecount as
+this variable doesn't count the new line between the header and the body of the
+message. */
message_size = transport_count;
+message_linecount = transport_newlines - 1;
/* If using a maildir++ quota file, add this message's size to it, and
-close the file descriptor. */
+close the file descriptor, except when the quota has been disabled because we
+are delivering into an uncounted folder. */
#ifdef SUPPORT_MAILDIR
-if (yield == OK && maildirsize_fd >= 0)
- maildir_record_length(maildirsize_fd, message_size);
-
-maildir_save_errno = errno; /* Preserve errno while closing the file */
-close(maildirsize_fd);
-errno = maildir_save_errno;
+if (!disable_quota)
+ {
+ if (yield == OK && maildirsize_fd >= 0)
+ maildir_record_length(maildirsize_fd, message_size);
+ maildir_save_errno = errno; /* Preserve errno while closing the file */
+ if (maildirsize_fd >= 0)
+ (void)close(maildirsize_fd);
+ errno = maildir_save_errno;
+ }
#endif /* SUPPORT_MAILDIR */
/* If there is a quota warning threshold and we are have crossed it with this
message, set the SPECIAL_WARN flag in the address, to cause a warning message
to be sent. */
-if (THRESHOLD_CHECK)
+if (!disable_quota && THRESHOLD_CHECK)
{
- int threshold = ob->quota_warn_threshold_value;
+ off_t threshold = ob->quota_warn_threshold_value;
if (ob->quota_warn_threshold_is_percent)
- threshold = (int)(((double)ob->quota_value * threshold) / 100);
+ threshold = (off_t)(((double)ob->quota_value * threshold) / 100);
DEBUG(D_transport)
- debug_printf("quota = %d threshold = %d old size = %d message size = %d\n",
- ob->quota_value, threshold, mailbox_size, message_size);
+ debug_printf("quota = " OFF_T_FMT
+ " threshold = " OFF_T_FMT
+ " old size = " OFF_T_FMT
+ " message size = %d\n",
+ ob->quota_value, threshold, mailbox_size,
+ message_size);
if (mailbox_size <= threshold && mailbox_size + message_size > threshold)
addr->special_action = SPECIAL_WARN;
"stat error %d for \"new\": %s\n", errno, strerror(errno));
}
else /* Want a repeatable time when in test harness */
- {
- addr->more_errno = running_in_test_harness? 10 :
+ addr->more_errno = f.running_in_test_harness ? 10 :
(int)time(NULL) - statbuf.st_mtime;
- }
+
DEBUG(D_transport)
debug_printf("maildir: time since \"new\" directory modified = %s\n",
readconf_printtime(addr->more_errno));
addr->message = string_sprintf("mailbox is full "
"(quota exceeded while writing to file %s)", filename);
#else
- addr->message = string_sprintf("mailbox is full");
+ addr->message = US"mailbox is full";
#endif /* EDQUOT */
+ addr->user_message = US"mailbox is full";
DEBUG(D_transport) debug_printf("System quota exceeded for %s%s%s\n",
dataname,
- isdirectory? US"" : US": time since file read = ",
- isdirectory? US"" : readconf_printtime(addr->more_errno));
+ isdirectory ? US"" : US": time since file read = ",
+ isdirectory ? US"" : readconf_printtime(addr->more_errno));
}
/* Handle Exim's own quota-imposition */
addr->user_message = US"mailbox is full";
DEBUG(D_transport) debug_printf("Exim%s quota exceeded for %s%s%s\n",
filecount_msg, dataname,
- isdirectory? US"" : US": time since file read = ",
- isdirectory? US"" : readconf_printtime(addr->more_errno));
+ isdirectory ? US"" : US": time since file read = ",
+ isdirectory ? US"" : readconf_printtime(addr->more_errno));
}
/* Handle a process failure while writing via a filter; the return
yield = PANIC;
addr->message = string_sprintf("transport filter process failed (%d) "
"while writing to %s%s", addr->more_errno, dataname,
- (addr->more_errno == EX_EXECFAILED)? ": unable to execute command" : "");
+ (addr->more_errno == EX_EXECFAILED) ? ": unable to execute command" : "");
}
/* Handle failure to expand header changes */
/* Handle failure to complete writing of a data block */
else if (errno == ERRNO_WRITEINCOMPLETE)
- {
addr->message = string_sprintf("failed to write data block while "
"writing to %s", dataname);
- }
/* Handle length mismatch on MBX copying */
#ifdef SUPPORT_MBX
else if (errno == ERRNO_MBXLENGTH)
- {
addr->message = string_sprintf("length mismatch while copying MBX "
"temporary file to %s", dataname);
- }
#endif /* SUPPORT_MBX */
/* For other errors, a general-purpose explanation, if the message is
investigated so far have ftruncate(), whereas not all have the F_FREESP
fcntl() call (BSDI & FreeBSD do not). */
- if (!isdirectory) ftruncate(fd, saved_size);
+ if (!isdirectory && ftruncate(fd, saved_size))
+ DEBUG(D_transport) debug_printf("Error resetting file size\n");
}
/* Handle successful writing - we want the modification time to be now for
{
addr->basic_errno = errno;
addr->message = string_sprintf("close() error for %s",
- (ob->mailstore_format)? dataname : filename);
+ (ob->mailstore_format) ? dataname : filename);
yield = DEFER;
}
This makes it possible to build values that are based on the time, and
still cope with races from multiple simultaneous deliveries. */
- if (newname == NULL)
+ if (!newname)
{
- int i;
uschar *renameleaf;
uschar *old_renameleaf = US"";
- for (i = 0; ; sleep(1), i++)
+ for (int i = 0; ; sleep(1), i++)
{
deliver_inode = statbuf.st_ino;
renameleaf = expand_string(ob->dirfilename);
deliver_inode = 0;
- if (renameleaf == NULL)
+ if (!renameleaf)
{
addr->transport_return = PANIC;
addr->message = string_sprintf("Expansion of \"%s\" "
else
{
- if (nametag != NULL)
+ if (nametag)
{
uschar *iptr = expand_string(nametag);
- if (iptr != NULL)
+ if (iptr)
{
- uschar *etag = store_get(Ustrlen(iptr) + 2);
+ uschar *etag = store_get(Ustrlen(iptr) + 2, is_tainted(iptr));
uschar *optr = etag;
- while (*iptr != 0)
- {
+ for ( ; *iptr; iptr++)
if (mac_isgraph(*iptr) && *iptr != '/')
{
if (optr == etag && isalnum(*iptr)) *optr++ = ':';
*optr++ = *iptr;
}
- iptr++;
- }
*optr = 0;
renamename = string_sprintf("%s%s", newname, etag);
}
/* Notify comsat if configured to do so. It only makes sense if the configured
file is the one that the comsat daemon knows about. */
-if (ob->notify_comsat && yield == OK && deliver_localpart != NULL)
+if (ob->notify_comsat && yield == OK && deliver_localpart)
notify_comsat(deliver_localpart, saved_size);
/* Pass back the final return code in the address structure */
debug_printf("unlinking MBX lock file %s\n", mbx_lockname);
Uunlink(mbx_lockname);
}
- close(mbx_lockfd);
+ (void)close(mbx_lockfd);
}
#endif /* SUPPORT_MBX */
/* We get here with isdirectory and filename set only in error situations. */
-if (isdirectory && filename != NULL)
+if (isdirectory && filename)
{
Uunlink(filename);
if (dataname != filename) Uunlink(dataname);
return FALSE;
}
+#endif /*!MACRO_PREDEF*/
/* End of transport/appendfile.c */