summaryrefslogtreecommitdiff
path: root/daemons
diff options
context:
space:
mode:
authorMiles Bader <miles@gnu.org>1996-10-15 01:33:35 +0000
committerMiles Bader <miles@gnu.org>1996-10-15 01:33:35 +0000
commite582480f32d58711d1fc6ad258c4894a26ddf395 (patch)
treead38da2faf04a753c4e6e0081344a4f9bb6feb75 /daemons
parent1514a978724c55d2a1a126ea2053c101bef9d6f8 (diff)
Initial checkin
Diffstat (limited to 'daemons')
-rw-r--r--daemons/lmail.c525
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, &params);
+ else
+ /* Multiple recipients. */
+ {
+ int cached; /* Temporary processed input file. */
+
+ ex = cache (in, file ?: "-", &params, &cached);
+ if (ex == 0)
+ while (rcpt < argc)
+ {
+ /* Deliver to one recipient. */
+ int rex = deliver (cached, "message cache", argv[rcpt++],
+ D_REWIND, &params);
+
+ /* 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);
+}