diff options
author | Miles Bader <miles@gnu.org> | 1996-10-15 01:33:35 +0000 |
---|---|---|
committer | Miles Bader <miles@gnu.org> | 1996-10-15 01:33:35 +0000 |
commit | e582480f32d58711d1fc6ad258c4894a26ddf395 (patch) | |
tree | ad38da2faf04a753c4e6e0081344a4f9bb6feb75 /daemons | |
parent | 1514a978724c55d2a1a126ea2053c101bef9d6f8 (diff) |
Initial checkin
Diffstat (limited to 'daemons')
-rw-r--r-- | daemons/lmail.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/daemons/lmail.c b/daemons/lmail.c new file mode 100644 index 00000000..5b6d7d63 --- /dev/null +++ b/daemons/lmail.c @@ -0,0 +1,525 @@ +/* Local mail delivery + + Copyright (C) 1996 Free Software Foundation, Inc. + + Written by Miles Bader <miles@gnu.ai.mit.edu> + + This file is part of the GNU Hurd. + + The GNU Hurd is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2, or (at + your option) any later version. + + The GNU Hurd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. */ + +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <pwd.h> +#include <syslog.h> +#include <sysexits.h> +#include <paths.h> +#include <argp.h> +#include <hurd.h> +#include <hurd/fd.h> +#include <version.h> + +#define OPT_FILE -5 + +char *argp_program_version = STANDARD_HURD_VERSION (mail.local); + +static const struct argp_option +options[] = +{ + {"from", 'f', "USER", 0, "Record sender as USER"}, + {0, 'd', 0, OPTION_ALIAS|OPTION_HIDDEN}, + {"file", OPT_FILE, "FILE", 0, "Deliver FILE instead of standard input"}, + {"remove", 'r', 0, 0, "Remove FILE after successful delivery"}, + {"mail-dir",'m', "DIR", 0, "Look for mailboxes in DIR"}, + {"use-lock-file",'l', 0, 0, + "Use a lock file instead of flock for mailboxes"}, + {0} +}; +static const char args_doc[] = "USER..."; +static const char doc[] = "Deliver mail to the local mailboxes of USER..."; + +#define HDR_PFX "From " /* Header, at the beginning of a line, + starting each msg in a mailbox. */ +#define ESC_PFX ">" /* Used to escape occurances of HDR_PFX in + the msg body. */ + +#define BMAX (64*1024) /* Chunk size for I/O. */ + +struct params +{ + char *from; /* Who the mail is from. */ + char *mail_dir; /* Mailbox directory. */ +}; + +/* Convert the system error code ERR to an appropiate exit value. This + function currently only returns three sorts: success, temporary failure, + or error, with exit codes 0, EX_TEMPFAIL, or EX_UNAVAILABLE. The table of + temporary failures is from the bsd original of this program. */ +static int +err_to_ex (error_t err) +{ + switch (err) + { + case 0: + return 0; /* Success */ + /* Temporary failures: */ +#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) + case EWOULDBLOCK: /* Operation would block. */ +#endif + case EAGAIN: /* Resource temporarily unavailable */ + case EDQUOT: /* Disc quota exceeded */ + case EBUSY: /* Device busy */ + case EPROCLIM: /* Too many processes */ + case EUSERS: /* Too many users */ + case ECONNABORTED: /* Software caused connection abort */ + case ECONNREFUSED: /* Connection refused */ + case ECONNRESET: /* Connection reset by peer */ + case EDEADLK: /* Resource deadlock avoided */ + case EFBIG: /* File too large */ + case EHOSTDOWN: /* Host is down */ + case EHOSTUNREACH: /* No route to host */ + case EMFILE: /* Too many open files */ + case ENETDOWN: /* Network is down */ + case ENETRESET: /* Network dropped connection on reset */ + case ENETUNREACH: /* Network is unreachable */ + case ENFILE: /* Too many open files in system */ + case ENOBUFS: /* No buffer space available */ + case ENOMEM: /* Cannot allocate memory */ + case ENOSPC: /* No space left on device */ + case EROFS: /* Read-only file system */ + case ESTALE: /* Stale NFS file handle */ + case ETIMEDOUT: /* Connection timed out */ + return EX_TEMPFAIL; + default: + return EX_UNAVAILABLE; + } +} + +/* Print and syslog the given error message, with the system error string for + ERRNO appended. Return an appropiate exit code for ERRNO. */ +#define SYSERR(fmt, args...) \ + ({ syslog (LOG_ERR, fmt ": %m" , ##args); err_to_ex (errno); }) + +/* Print and syslog the given error message. Return the exit code + EX_UNAVAILABLE. */ +#define ERR(fmt, args...) \ + ({ syslog (LOG_ERR, fmt , ##args); EX_UNAVAILABLE; }) + +/* Print and syslog the given error message, with the system error string for + CODE appended. Return an appropiate exit code for CODE. */ +#define SYSERRX(code, fmt, args...) \ + ({ error_t _code = (code); \ + syslog (LOG_ERR, fmt ": %s" , ##args , strerror (_code)); \ + err_to_ex (_code); }) + +/* Block I/O functions. These are structured to allow the use of a read + method that avoids copying the data locally. */ + +/* Free block allocated by bread. */ +static void +bfree (char *blk, size_t blk_len) +{ + if (blk_len > 0) + vm_deallocate (mach_task_self (), (vm_address_t)blk, blk_len); +} + +/* Read up to MAX chars from IN into BLK & BLK_LEN, which may be reused or + freed. */ +static int +bread (int in, char *in_name, size_t max, char **blk, size_t *blk_len) +{ + char *orig_blk = *blk; + size_t orig_blk_len = *blk_len; + error_t err = HURD_DPORT_USE (in, io_read (port, blk, blk_len, -1, max)); + + if (err) + return SYSERRX (err, "%s", in_name); + + if (*blk != orig_blk) + bfree (orig_blk, orig_blk_len); + + return 0; +} + +/* Write BLK & BLK_LEN to OUT. An exit status is returned. */ +static int +bwrite (int out, char *out_name, const char *blk, size_t blk_len) +{ + while (blk_len > 0) + { + ssize_t wr = write (out, blk, blk_len); + if (wr < 0) + return SYSERR ("%s", out_name); + blk += wr; + blk_len -= wr; + } + return 0; +} + +/* Copy from file descriptor IN to OUT. An exit status is returned. */ +static int +copy (int in, char *in_name, int out, char *out_name) +{ + int ex = 0; + char *blk = 0; + size_t blk_len = 0; + + do + { + ex = bread (in, in_name, BMAX, &blk, &blk_len); + if (! ex) + ex = bwrite (out, out_name, blk, blk_len); + } + while (blk_len > 0 && !ex); + + bfree (blk, blk_len); + + return ex; +} + +static int +write_header (int out, char *out_name, struct params *params) +{ + char *hdr; + size_t hdr_len; + struct timeval tv; + time_t time; + int ex = 0; + + if (gettimeofday (&tv, 0) < 0) + return SYSERR ("gettimeofday"); + + /* Note that the string returned by ctime includes a terminating newline. */ + time = tv.tv_sec; + hdr_len = asprintf (&hdr, "From %s %s", params->from, ctime (&time)); + if (! hdr) + return SYSERRX (ENOMEM, "%s", out_name); + + ex = bwrite (out, out_name, hdr, hdr_len); + + free (hdr); + + return ex; +} + +/* Copy from file descriptor IN to OUT, making any changes needed to make the + contents a valid mailbox entry. These include: + (1) Prepending a `From ...' line, and appending a blank line. + (2) Replacing any occurances of `From ' at the beginning of lines with + `>From '. + An exit status is returned. */ +static int +process (int in, char *in_name, int out, char *out_name, struct params *params) +{ + /* The block currently being processed. */ + char *blk = 0; + size_t blk_len = 0; + /* MATCH is the string we're looking for to escape, NL_MATCH is the same + string prefixed by a newline to ease searching (MATCH only matches at + the beginning of lines). */ + const char *const nl_match = "\n" HDR_PFX, *const match = nl_match + 1; + /* The portion of MATCH that matched the end of the previous block. 0 + means that there was at least a newline, so initializing MATCHED to 0 + simulates a newline at the start of IN. */ + ssize_t matched = 0; + int ex = write_header (out, out_name, params); + +#define match_len (sizeof HDR_PFX - 1) + + if (ex) + return ex; + +#define BWRITE(p, p_len) \ + ({ size_t _len = (p_len); \ + if (_len > 0 && (ex = bwrite (out, out_name, p, _len))) \ + break; }) + + do + { + char *start, *end; + + ex = bread (in, in_name, BMAX, &blk, &blk_len); + + if (matched >= 0) + /* The last block ended in a partial match, so see if we can complete + it in this block. */ + { + if (blk_len >= match_len - matched + && memcmp (blk, match + matched, match_len - matched) == 0) + /* It matched; output the escape prefix before the match. */ + BWRITE (ESC_PFX, sizeof ESC_PFX - 1); + /* Now we have to output the part of the preceding block that we + didn't do before because it was a partial match. */ + BWRITE (match, matched); + /* Ok, we're all caught up. The portion of the match that's in + this block will be output as normal text. */ + matched = -1; + } + + /* Scan through the block looking for matches. */ + for (start = end = blk; start < blk + blk_len; start = end) + { + /* Look for our match, prefixed by a newline. */ + end = memmem (start, blk + blk_len - start, nl_match, match_len + 1); + if (end) + /* We found a match, output the escape prefix before it. */ + { + end++; /* The newline should precede the escape. */ + BWRITE (start, end - start); /* part of block before match. */ + BWRITE (ESC_PFX, sizeof ESC_PFX - 1); /* escape prefix. */ + } + else + { + end = blk + blk_len; + break; + } + } + + /* Now see if there are any partial matches at the end. */ + for (matched = + end - start < match_len + 1 ? end - start - 1 : match_len; + matched >= 0; + matched--) + if (memcmp (end - matched - 1, nl_match, matched + 1) == 0) + /* There's a partial match MATCHED characters long at the end of + this block, so delay outputting it until the next block can be + examined; we do output the preceding newline here, though. */ + { + end -= matched; + break; + } + + BWRITE (start, end - start); + } + while (blk_len > 0); + + if (ex == 0) + ex = bwrite (out, out_name, "\n", 1); /* Append a blank line. */ + + bfree (blk, blk_len); + + return ex; +} + +/* Deliver flags. */ +#define D_PROCESS 0x1 /* Deliver */ +#define D_REWIND 0x2 /* Rewind MSG before using it. */ + +/* Deliver the text from the file descriptor MSG to the mailbox of the user + RCPT in MAIL_DIR. FLAGS is from the set D_* above. An exit appropiate + exit code is returned. */ +static int +deliver (int msg, char *msg_name, char *rcpt, int flags, struct params *params) +{ + char *mbox; /* Name of mailbox */ + int fd; /* Opened mailbox */ + struct stat stat; + int ex = 0; /* Exit status */ + struct passwd *pw = getpwnam (rcpt); /* Details of recipient */ + + if (! pw) + return ERR ("%s: Unknown user", rcpt); + + asprintf (&mbox, "%s/%s", params->mail_dir, rcpt); + if (! mbox) + return SYSERRX (ENOMEM, "%s", rcpt); + + do + { + /* First try to open an existing mailbox. */ + fd = open (mbox, O_WRONLY|O_APPEND|O_NOLINK|O_EXLOCK); + if (fd < 0 && errno == ENOENT) + /* There is none; try to create it. */ + { + fd = open (mbox, O_WRONLY|O_APPEND|O_CREAT|O_EXCL|O_NOLINK|O_EXLOCK, + S_IRUSR|S_IWUSR); + if (fd >= 0) + /* Made a new mailbox! Set the owner and group appropiately. */ + { + if (fchown (fd, pw->pw_uid, pw->pw_gid) < 0) + { + close (fd); + fd = -1; /* Propagate error. */ + } + } + } + } + /* EEXIST can only be returned someone else created the mailbox between the + two opens above, so if we try again, the first open should work. */ + while (fd < 0 && errno == EEXIST); + + if (fd < 0 || fstat (fd, &stat) < 0) + ex = SYSERR ("%s", mbox); + else if (S_ISLNK (stat.st_mode) || stat.st_nlink != 1) + ex = ERR ("%s: Is linked", mbox); + else + { + if (flags & D_REWIND) + { + if (lseek (msg, 0L, SEEK_SET) < 0) + ex = SYSERR ("%s", msg_name); + } + if (! ex) + if (flags & D_PROCESS) + ex = process (msg, msg_name, fd, mbox, params); + else + ex = copy (msg, msg_name, fd, mbox); + } + + if (fd >= 0) + if (close (fd) < 0 && !ex) + ex = SYSERR ("%s", mbox); + free (mbox); + + return ex; +} + +/* Process from the file descriptor IN into a temporary file, which return a + descriptor to in CACHED; once *CACHED is closed, it will go away + permanently. The file pointer of *CACHED is at an undefined location. An + exit status is returned. */ +static int +cache (int in, char *in_name, struct params *params, int *cached) +{ + int ex; + error_t err; + file_t file; /* Hurd port for temp file */ + int fd; /* File descriptor for it */ + file_t tmp_dir = file_name_lookup (_PATH_TMP, O_RDONLY, 0); + + if (tmp_dir == MACH_PORT_NULL) + return SYSERR ("%s", _PATH_TMP); + + /* Create FILE without actually putting it into TMP_DIR. */ + err = dir_mkfile (tmp_dir, O_RDWR, 0600, &file); + if (err) + return SYSERRX (err, "%s", _PATH_TMP); + + fd = _hurd_intern_fd (file, O_RDWR, 1); + if (fd < 0) + return SYSERR ("%s", _PATH_TMP); + + ex = process (in, in_name, fd, _PATH_TMP, params); + if (ex == 0) + *cached = fd; + else + close (fd); + + return ex; +} + +int +main (int argc, char **argv) +{ + int rcpt = 0; /* Index in ARGV of next recepient. */ + char *file = 0; /* File containing message. */ + int remove = 0; /* Remove file after successful delivery. */ + int in = 0; /* Raw input file descriptor. */ + int ex = 0; /* Exit status. */ + struct params params = { from: 0, mail_dir: _PATH_MAILDIR }; + + error_t parse_opt (int key, char *arg, struct argp_state *state) + { + switch (key) + { + case 'f': case 'd': + params.from = arg; break; + case OPT_FILE: + file = arg; break; + case 'r': + remove = 1; break; + case 'm': + params.mail_dir = arg; break; + case 'l': + argp_failure (state, EX_USAGE, EINVAL, "-l not supported"); + case ARGP_KEY_NO_ARGS: + argp_error (state, "No recipients"); + default: + return ARGP_ERR_UNKNOWN; + } + return 0; + } + const struct argp argp = { options, parse_opt, args_doc, doc }; + + /* Parse arguments. */ + argp_parse (&argp, argc, argv, 0, &rcpt, 0); + + /* Use syslog to log errors. */ + openlog ("mail.local", LOG_PERROR, LOG_MAIL); + + if (! params.from) + /* No from address specified, use the current user. */ + { + struct passwd *pw; + int uid = getuid (); + + if (uid == -1) + exit (ERR ("No user id")); + + pw = getpwuid (uid); + if (! pw) + exit (ERR ("%d: Unknown uid", uid)); + + params.from = strdup (pw->pw_name); + } + + if (file) + /* Use FILE as the message contents. */ + { + in = open (file, O_RDONLY); + if (in < 0) + exit (SYSERR ("%s", file)); + } + else + /* Use standard input. */ + in = 0; + + if (rcpt == argc - 1) + /* Only a single recipient. */ + ex = deliver (in, file ?: "-", argv[rcpt], D_PROCESS, ¶ms); + else + /* Multiple recipients. */ + { + int cached; /* Temporary processed input file. */ + + ex = cache (in, file ?: "-", ¶ms, &cached); + if (ex == 0) + while (rcpt < argc) + { + /* Deliver to one recipient. */ + int rex = deliver (cached, "message cache", argv[rcpt++], + D_REWIND, ¶ms); + + /* Merge the exit code for that recipient. Temporary failures + take precedence over hard failures and success, as + subsequently delivering duplicates (of the successful + messages) is preferable to not delivering the temporary + failures. */ + if (ex != EX_TEMPFAIL) + if (rex == EX_TEMPFAIL) + ex = EX_TEMPFAIL; + else if (ex == 0) + ex = rex; + } + } + + if (file && remove && ex == 0) + unlink (file); + + exit (ex); +} |