]> git.sthu.org Git - smailq.git/commitdiff
Initial commit v1.0
authorStefan Huber <shuber@sthu.org>
Sun, 29 Dec 2013 10:59:16 +0000 (11:59 +0100)
committerStefan Huber <shuber@sthu.org>
Mon, 30 Dec 2013 13:27:01 +0000 (14:27 +0100)
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
mailq [new file with mode: 0755]
sendmail [new file with mode: 0755]
smailq [new file with mode: 0755]
smailq.conf [new file with mode: 0644]
smailq.docbook [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..65c5ca8
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..1ca9fc6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,28 @@
+PREFIX=/usr
+# Where the script binary should go
+BINPATH = $(PREFIX)/bin
+SBINPATH = $(PREFIX)/sbin
+MANPATH = $(PREFIX)/share/man
+SHAREDIR = $(PREFIX)/share/smailq
+CONFFILE = $(SHAREDIR)/smailq.conf.sample
+
+######################################################################
+
+all: manpage
+
+install: all
+       install -m 0755 smailq $(BINPATH)/smailq
+       install -m 0755 mailq $(BINPATH)/mailq
+       install -m 0755 sendmail $(SBINPATH)/sendmail
+       mkdir -p $(SHAREDIR)
+       install -m 0644 smailq.conf $(CONFFILE)
+       install -m 0644 smailq.1 $(MANPATH)/man1/smailq.1
+       bzip2 -f $(MANPATH)/man1/smailq.1
+
+manpage: smailq.1
+
+smailq.1: smailq.docbook
+       docbook2man.pl $<
+       #groff -t -e -mandoc -Tps smailq.1 > smailq.ps
+
+
diff --git a/mailq b/mailq
new file mode 100755 (executable)
index 0000000..c59062a
--- /dev/null
+++ b/mailq
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+# Copyright (c) 2013 Stefan Huber <shuber@sthu.org>
+
+set -e
+
+PROGNAME=$(basename $0)
+
+usage() {
+    cat <<EOF
+List the mails in the mail queue by calling 'smailq --list'.
+
+USAGE:
+  $0 [OPTION]
+
+OPTIONS:
+  -h, --help    Print this text
+EOF
+}
+
+
+TEMP=`getopt -o "h" --long "help" -n "$PROGNAME" -- "$@"`
+eval set - "$TEMP"
+
+while true; do
+    case "$1" in
+        -h | --help )
+            usage
+            exit $STATE_OK ;;
+        -- )
+            shift
+            break ;;
+        * )
+            break ;;
+    esac
+done
+
+smailq --list
diff --git a/sendmail b/sendmail
new file mode 100755 (executable)
index 0000000..31cffc7
--- /dev/null
+++ b/sendmail
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# Copyright (c) 2013 Stefan Huber <shuber@sthu.org>
+
+set -e
+
+CMD="$(dirname $0)/smailq"
+options=
+list="0"
+
+usage() {
+    cat << EOF
+A wrapper script for smailq that behaves like sendmail. 
+
+USAGE:
+  $0 [OPTIONS] [recipient ...]
+  $0 --help  
+
+It simply calls '$CMD --send -- [OPTIONS] [recipient ...]'. In
+particular, it passes all options as MSA options to smailq.
+EOF
+}
+
+
+options=()
+while [ $# -gt "0" ]; do
+    if [ "$1" == "-bp" ]; then
+        list="1"
+    else
+        options+=("$1")
+    fi
+    shift
+done
+
+if [ "$list" = "1" ]; then
+    smailq --list
+else
+    smailq --send -- "${options[@]}"
+fi
+
diff --git a/smailq b/smailq
new file mode 100755 (executable)
index 0000000..c42b980
--- /dev/null
+++ b/smailq
@@ -0,0 +1,472 @@
+#!/usr/bin/env python3
+"""A mail queue for lightweight SMTP clients (MSAs) like msmtp."""
+
+__author__ = "Stefan Huber"
+__copyright__ = "Copyright 2013"
+
+__license__ = "LGPL-3"
+__version__ = "1.0"
+
+
+from contextlib import contextmanager
+import configparser
+import fcntl
+import getopt
+import os
+import pickle
+import random
+import shlex
+import subprocess
+import sys
+import time
+import socket
+
+
+verbose = False
+quiet = False
+
+
+class Config:
+    """Configuration read from a config file"""
+
+    class ConfigError(RuntimeError):
+        """Error when reading config file"""
+        def __init__(self, value):
+            self.value = value
+            self.message = value
+
+    def __init__(self, conffn):
+        self.logdir = None
+        self.datadir = None
+        self.nwtesthost = None
+        self.nwtestport = None
+        self.nwtesttimeout = None
+        self.msacmd = None
+
+        self.__nwtest = None
+
+        self.__read(conffn)
+
+    def __read(self, conffn):
+        conf = configparser.RawConfigParser()
+        conf.read(conffn)
+
+        self.logdir = "~/.smailq/log"
+        self.datadir = "~/.smailq/data"
+        self.nwtesthost = "www.google.com"
+        self.nwtestport = 80
+        self.nwtesttimeout = 8
+
+        self.logdir = conf.get("general", "logdir", fallback=self.logdir)
+        self.datadir = conf.get("general", "datadir", fallback=self.datadir)
+        self.nwtesthost = conf.get("nwtest", "host", fallback=self.nwtesthost)
+        self.nwtestport = conf.getint("nwtest", "port",
+                                      fallback=self.nwtestport)
+        self.nwtesttimeout = conf.getint("nwtest", "timeout",
+                                         fallback=self.nwtesttimeout)
+
+        if not conf.has_option("msa", "cmd"):
+            raise Config.ConfigError("Section 'msa' contains no 'cmd' option.")
+        self.msacmd = conf.get("msa", "cmd")
+
+    def getdatadir(self):
+        """Returns the directory for the mail data"""
+        return os.path.expanduser(self.datadir)
+
+    def getlogdir(self):
+        """Returns the directory for the log data"""
+        return os.path.expanduser(self.logdir)
+
+    def getlockfn(self):
+        """Get a lock filename of the data directory"""
+        return self.getdatadir() + "/.lock"
+
+    def getmailfn(self, id):
+        return self.getdatadir() + "/" + id + ".eml"
+
+    def getmsaargsfn(self, id):
+        return self.getdatadir() + "/" + id + ".msaargs"
+
+    @contextmanager
+    def aquiredatalock(self):
+        """Get a lock on the data directory"""
+        fn = self.getlockfn()
+
+        # If lock file exists, wait until it disappears
+        while os.path.exists(fn):
+            time.sleep(0.05)
+
+        # Use lockf to get exclusive access to file
+        fp = open(fn, 'w')
+        fcntl.lockf(fp, fcntl.LOCK_EX)
+        try:
+            yield
+        finally:
+            fcntl.lockf(fp, fcntl.LOCK_UN)
+            fp.close()
+            os.remove(self.getlockfn())
+
+    def networktest(self):
+        """Test if we have connection to the internet."""
+
+        if self.__nwtest is None:
+            self.__nwtest = False
+            try:
+                host = (self.nwtesthost, self.nwtestport)
+                to = self.nwtesttimeout
+                with socket.create_connection(host, timeout=to):
+                    self.__nwtest = True
+            except OSError as e:
+                pass
+            except Exception as e:
+                printerr(e)
+
+        return self.__nwtest
+
+
+class MailQueue:
+
+    def __init__(self, conf):
+        self.conf = conf
+        self.__mailids = None
+
+    def get_mail_ids(self):
+        """Return a list of all mail IDs"""
+
+        # Get mail and msaargs files in datadir
+        listdir = os.listdir(self.conf.getdatadir())
+        mailfiles = [f for f in listdir if f.endswith(".eml")]
+        msaargsfiles = [f for f in listdir if f.endswith(".msaargs")]
+
+        # Strip of file endings
+        mailfiles = [f[:-4] for f in mailfiles]
+        msaargsfiles = [f[:-8] for f in msaargsfiles]
+
+        # Check if symmetric difference is zero
+        for f in set(mailfiles) - set(msaargsfiles):
+            printerr("For ID %s an eml file but no msaargs file exists." % f)
+        for f in set(msaargsfiles) - set(mailfiles):
+            printerr("For ID %s a msaargs file but no eml file exists." % f)
+
+        # Get mail IDs
+        return set(mailfiles) & set(msaargsfiles)
+
+    def getmailinfo(self, id):
+        """Get some properties of mail with given ID"""
+        assert(id in self.get_mail_ids())
+
+        mailfn = self.conf.getmailfn(id)
+
+        info = {}
+        info['ctime'] = time.ctime(os.path.getctime(mailfn))
+        info['size'] = os.path.getsize(mailfn)
+
+        with open(mailfn, "r") as f:
+            mail = f.readlines()
+
+            for l in mail:
+                if l.startswith("Subject:"):
+                    info['subject'] = l[8:].strip()
+                    break
+
+            for l in mail:
+                if l.startswith("To:"):
+                    info['to'] = l[3:].strip()
+                    break
+
+        return info
+
+    def printmailinfo(self, id):
+        """Print some info on the mail with given ID"""
+
+        print("ID %s:" % id)
+
+        if not id in self.get_mail_ids():
+            printerr("ID %s is not in the queue!" % id)
+            return
+
+        info = self.getmailinfo(id)
+
+        print("  Time: %s" % info['ctime'])
+        print("  Size: %s Bytes" % info['size'])
+        print("  To: %s" % info['to'])
+        print("  Subject: %s" % info['subject'])
+
+    def listqueue(self):
+        """Print a list of mails in the mail queue"""
+
+        ids = self.get_mail_ids()
+        print("%d mails in the queue.\n" % len(ids))
+        for id in ids:
+            self.printmailinfo(id)
+            print()
+
+    def deletemail(self, id):
+        """Attempt to deliver mail with given ID"""
+        printinfo("Removing mail with ID " + id)
+
+        if not id in self.get_mail_ids():
+            printerr("ID %s is not in the queue!" % id)
+            return
+
+        os.remove(conf.getmailfn(id))
+        os.remove(conf.getmsaargsfn(id))
+        log(conf, "Removed from queue.", id=id)
+
+    def delivermail(self, id):
+        """Attempt to deliver mail with given ID"""
+        printinfo("Deliver mail with ID " + id)
+
+        if not id in self.get_mail_ids():
+            printerr("ID %s is not in the queue!" % id)
+            return
+
+        if not self.conf.networktest():
+            printinfo("Network down. Do not deliver mail.")
+            return
+
+        info = self.getmailinfo(id)
+        log(conf, "Attempting to deliver mail. To=%s" % info['to'], id=id)
+
+        # Read the mail
+        mailfn = self.conf.getmailfn(id)
+        mailf = open(mailfn, "r")
+
+        # Read the options
+        msaargsfn = self.conf.getmsaargsfn(id)
+        msaargs = None
+        with open(msaargsfn, "rb") as f:
+            msaargs = pickle.load(f)
+
+        # Build argv for the MSA
+        msacmd = self.conf.msacmd
+        msaargv = shlex.split(msacmd)
+        msaargv += msaargs
+
+        # Call the MSA and give it the mail
+        printinfo("Calling " + " ".join([shlex.quote(m) for m in msaargv]))
+        ret = subprocess.call(msaargv, stdin=mailf)
+
+        if ret == 0:
+            log(conf, "Delivery successful.", id=id)
+            self.deletemail(id)
+        else:
+            log(conf, "Delivery failed with exit code %d." % ret, id=id)
+
+    def delivermails(self):
+        """Attempt to deliver all mails in the mail queue"""
+        printinfo("Deliver mails in the queue.")
+
+        if not self.conf.networktest():
+            printinfo("Network down. Do not deliver mails.")
+            return
+
+        for id in self.get_mail_ids():
+            self.delivermail(id)
+
+    def enqueuemail(self, mail, msaargs):
+        """Insert the given mail into the mail queue"""
+        # Creeate a new ID
+        id = None
+        while True:
+            nibbles = 8
+            id = hex(random.getrandbits(4*nibbles))[2:].upper()
+            while len(id) < nibbles:
+                id = '0' + id
+            if not os.path.exists(self.conf.getmailfn(id)):
+                break
+
+        log(conf, "Insert into queue.", id=id)
+
+        # Write the mail
+        mailfn = self.conf.getmailfn(id)
+        with open(mailfn, "w") as f:
+            f.write(mail)
+
+        # Write the options
+        msaargsfn = self.conf.getmsaargsfn(id)
+        with open(msaargsfn, "wb") as f:
+            pickle.dump(msaargs, f)
+
+        return id
+
+    def sendmail(self, mail, msaargs):
+        """Insert a mail in the mail queue, and attempt to deliver mails"""
+        self.enqueuemail(mail, msaargs)
+        self.delivermails()
+
+
+def log(conf, msg, id=None):
+    """Write message to log file"""
+    fn = conf.getlogdir() + "/smailq.log"
+
+    with open(fn, 'a') as f:
+        fcntl.lockf(f, fcntl.LOCK_EX)
+        # Prepend ID to msg
+        if id is not None:
+            msg = ("ID %s: " % id) + msg
+        # Prepend time to msg
+        msg = time.strftime("%Y-%m-%d %H:%M:%S: ", time.localtime()) + msg
+
+        # Write msg line
+        f.write(msg + "\n")
+        if not quiet:
+            print(msg)
+
+        fcntl.lockf(f, fcntl.LOCK_UN)
+
+
+def printerr(msg):
+    """Print an error message"""
+    print(msg, file=sys.stderr)
+
+
+def printinfo(msg):
+    if verbose:
+        print(msg)
+
+
+def version():
+    """Show version info"""
+
+    print("smailq " + __version__)
+    print("Copyright (C) 2013 Stefan Huber")
+
+
+def usage():
+    """Print usage text of this program"""
+
+    print("""
+smailq is a mail queue for lightweight SMTP clients (MSAs) like msmtp that do
+not provide a queue. It basically provides the functionality of sendmail and
+mailq.
+
+USAGE:
+
+  {0} --send [recipient ...] -- [MSA options ...]
+  {0} --list
+  {0} --deliver-all
+  {0} --deliver [ID ...]
+  {0} --delete [ID ...]
+  {0} --help
+  {0} --version
+
+COMMANDS:
+
+      --delete
+          Remove the mails with given IDs from the queue.
+
+      --deliver
+          Attempt to deliver the mails with given IDs only.
+
+      --deliver-all
+          Attempt to deliver all mails in the queue.
+
+  -h, --help
+          Print this usage text.
+
+      --list
+          List all mails in the queue. This is the default
+
+      --send
+          Read a mail from stdin, insert it into the queue, and attempt to
+          deliver all mails in the queue. Options after "--" are passed forward
+          to the MSA for this particular mail.
+
+  -V, --version
+          Show version info.
+
+OPTIONS:
+
+  -C, --config=FILE
+          Use the given configuration file instead of "$HOME/.smailq.conf".
+
+  -q, --quiet
+          Do not print info messages.
+
+  -v, --verbose
+          Increase output verbosity.
+""".format(sys.argv[0]))
+
+
+if __name__ == "__main__":
+
+    conffn = os.path.expanduser("~/.smailq.conf")
+    cmd = "--list"
+    nooptargs = []
+
+    try:
+
+        longopts = ["config=", "delete", "deliver-all", "deliver", "help",
+                    "list", "send", "verbose", "version", "quiet"]
+        opts, nooptargs = getopt.gnu_getopt(sys.argv[1:], "hC:vVq", longopts)
+
+        for opt, arg in opts:
+            if opt in ['-h', '--help']:
+                usage()
+                sys.exit(os.EX_OK)
+            elif opt in ['-V', '--version']:
+                version()
+                sys.exit(os.EX_OK)
+            elif opt in ['--list', '--send', '--delete', '--deliver-all',
+                         '--deliver']:
+                cmd = opt
+            elif opt in ['-C', '--config']:
+                conffn = arg
+            elif opt in ['-v', '--verbose']:
+                verbose = True
+                quiet = False
+            elif opt in ['-q', '--quiet']:
+                quiet = True
+                verbose = False
+            else:
+                assert(False)
+
+    except getopt.GetoptError as e:
+        printerr("Error parsing arguments:", e)
+        usage()
+        sys.exit(os.EX_USAGE)
+
+    # Reading config file
+    if not os.path.isfile(conffn):
+        printerr("No such config file:", conffn)
+        sys.exit(os.EX_IOERR)
+    conf = None
+    try:
+        conf = Config(conffn)
+
+        if not os.path.isdir(conf.getdatadir()):
+            printerr("Data directory does not exist: " + conf.getdatadir())
+            sys.exit(os.EX_IOERR)
+
+        if not os.path.isdir(conf.getlogdir()):
+            printerr("Log directory does not exist: " + conf.getlogdir())
+            sys.exit(os.EX_IOERR)
+
+    except Exception as e:
+        printerr("Error reading config file:", e)
+        sys.exit(os.EX_IOERR)
+
+    try:
+        with conf.aquiredatalock():
+
+            printinfo("Aquired the lock.")
+
+            mq = MailQueue(conf)
+            if cmd == "--send":
+                mail = sys.stdin.read()
+                mq.sendmail(mail, nooptargs)
+            elif cmd == "--list":
+                mq.listqueue()
+            elif cmd == "--deliver-all":
+                mq.delivermails()
+            elif cmd == "--deliver":
+                for id in nooptargs:
+                    mq.delivermail(id)
+            elif cmd == "--delete":
+                for id in nooptargs:
+                    mq.deletemail(id)
+
+    except OSError as e:
+        printerr(e)
+        sys.exit(os.EX_IOERR)
diff --git a/smailq.conf b/smailq.conf
new file mode 100644 (file)
index 0000000..1e40437
--- /dev/null
@@ -0,0 +1,21 @@
+# General settings concerning smailq
+[general]
+# Optional: The directory where the log file is written to
+logdir = ~/.smailq/log/
+# Optional: The directory where smailq saves the mail queue data
+datadir = ~/.smailq/data/
+
+# Settings for the network (TCP) connectivity test
+[nwtest]
+# Optional: The host to connect to
+host = www.google.com
+# Optional: The port to connect to
+port = 80
+# Optional: The timeout
+timeout = 8
+
+# Settings concerning the mail submission agent
+[msa]
+# This command is called when smailq attempts to deliver a mail. The
+# MSA-options passed to smailq are appended to this line.
+cmd = /usr/bin/msmtp
diff --git a/smailq.docbook b/smailq.docbook
new file mode 100644 (file)
index 0000000..1e28c0b
--- /dev/null
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook V4.4//EN"
+"file:///usr/share/xml/docbook/schema/dtd/4.4/docbookx.dtd"
+[]>
+
+<refentry>
+
+       <refentryinfo>
+        <address>
+            <email>shuber@sthu.org</email>
+        </address>
+               <author>
+                       <firstname>Stefan</firstname>
+                       <surname>Huber</surname>
+               </author>
+               <date>2013-12-29</date>
+       </refentryinfo>
+
+       <refmeta>
+               <refentrytitle>smailq</refentrytitle>
+               <manvolnum>1</manvolnum>
+        <refmiscinfo>smailq 1.0</refmiscinfo>q
+       </refmeta>
+
+       <refnamediv>
+        <refname><application>smailq</application></refname>
+               <refpurpose>A simple mail queue.</refpurpose>
+       </refnamediv>
+
+       <refsynopsisdiv>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--send</option>
+                       <arg rep="repeat"><replaceable>recipient</replaceable></arg>
+            <option>--</option>
+                       <arg rep="repeat"><replaceable>MSA options</replaceable></arg>
+               </cmdsynopsis>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--list</option>
+               </cmdsynopsis>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--deliver-all</option>
+               </cmdsynopsis>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--deliver</option>
+                       <arg rep="repeat"><replaceable>ID</replaceable></arg>
+               </cmdsynopsis>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--delete</option>
+                       <arg rep="repeat"><replaceable>ID</replaceable></arg>
+               </cmdsynopsis>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--help</option>
+               </cmdsynopsis>
+               <cmdsynopsis>
+                       <command>smailq</command>
+            <option>--version</option>
+               </cmdsynopsis>
+       </refsynopsisdiv>
+  
+       <refsect1>
+               <title>DESCRIPTION</title>
+       
+               <para>
+            <command>smailq</command> is a mail queue for lightweight SMTP
+            clients (MSAs) like msmtp that do not provide a queue. It basically
+            provides the functionality of sendmail and mailq.
+               </para>
+
+               <para>
+            When <command>smailq</command> sends a mail it first inserts the
+            mail into the message queue and then attempts to deliver all mails
+            in the queue using an external MSA such as msmtp.
+               </para>
+       
+       </refsect1>
+
+       <refsect1>
+               <title>COMMANDS</title>
+       
+               <variablelist>
+       
+               <varlistentry>
+                       <term><option>--delete</option></term>
+                       <listitem>
+                               <para>Remove the mails with given IDs from the queue.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>--deliver</option></term>
+                       <listitem>
+                               <para>Attempt to deliver the mails with given IDs only.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>--deliver-all</option></term>
+                       <listitem>
+                               <para>Attempt to deliver all mails in the queue.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>-h</option></term>
+                       <term><option>--help</option></term>
+                       <listitem>
+                               <para>Print usage text.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>--list</option></term>
+                       <listitem>
+                               <para>List all mails in the queue. This is the default.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>--send</option></term>
+                       <listitem>
+                <para>Read a mail from stdin, insert it into the queue, and
+                    attempt to deliver all mails in the queue. Options after
+                    "--" are passed forward to the MSA for this particular
+                    mail.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>-V</option></term>
+                       <term><option>--version</option></term>
+                       <listitem>
+                               <para>Show version info.</para>
+                       </listitem>
+               </varlistentry>
+
+               </variablelist>
+       </refsect1>
+  
+
+       <refsect1>
+               <title>OPTIONS</title>
+       
+               <variablelist>
+       
+               <varlistentry>
+                       <term><option>-C</option></term>
+                       <term><option>--config FILE</option></term>
+                       <listitem>
+                <para>Use the given configuration file instead of
+                    "$HOME/.smailq.conf".</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>-q</option></term>
+                       <term><option>--quiet</option></term>
+                       <listitem>
+                               <para>Do not print info messages.</para>
+                       </listitem>
+               </varlistentry>
+
+               <varlistentry>
+                       <term><option>-v</option></term>
+                       <term><option>--verbose</option></term>
+                       <listitem>
+                               <para>Increase output verbosity.</para>
+                       </listitem>
+               </varlistentry>
+
+       </variablelist>
+       </refsect1>
+
+       <refsect1>
+        <title>CONFIGURATION FILES</title>
+
+        <para>
+            By default, <command>smailq</command> reads $HOME/.smailq.conf as
+            configuration file. The syntax follows RFC 822. A sample
+            configuration file contains the following lines:
+               </para>
+
+        <programlisting>
+[general]
+# Optional: The directory where the log file is written to
+logdir = ~/.smailq/log/
+# Optional: The directory where smailq saves the mail queue data
+datadir = ~/.smailq/data/
+
+# Settings for the network (TCP) connectivity test
+[nwtest]
+# Optional: The host to connect to
+host = www.google.com
+# Optional: The port to connect to
+port = 80
+# Optional: The timeout
+timeout = 8
+
+# Settings concerning the mail submission agent
+[msa]
+# This command is called when smailq attempts to deliver a mail. The
+# MSA-options passed to smailq are appended to this line.
+cmd = /usr/bin/msmtp
+        </programlisting>
+
+       </refsect1>
+
+       <refsect1>
+        <title>BUGS</title>
+        Bug reports to Stefan Huber <email>shuber@sthu.org</email>.
+    </refsect1>
+
+       <refsect1>
+               <title>AUTHOR</title>
+       
+        <para>
+            <author>
+                <firstname>Stefan</firstname>
+                <surname>Huber</surname>
+                <contrib>Original author</contrib>
+                <email>shuber@sthu.org</email>
+            </author>
+               </para>
+       </refsect1>
+</refentry>