dmc

dynamic mail client
git clone git://git.suckless.org/dmc
Log | Files | Refs | README | LICENSE

commit b786fbad54ce8d965e324721e1736b40ab830009
parent 4cff070c83d2f7afa4cf151d7bf14c3bfe475bb1
Author: pancake@localhost.localdomain <unknown>
Date:   Sun,  1 Nov 2009 19:50:46 +0100

* Add config.def.h and config.mk
* Add 'dist' makefile target
* Add doc/pipeline to explain the wip refactoring code
  to embed socket stuff inside the pop3.c and imap4.c
* dmc-pop3 is now working with the new pipeline mode (-13 LOC)
* Added sock.c as an interface for socket connections
Diffstat:
Makefile | 18++++++++++++++----
config.def.h | 1+
config.mk | 6++++++
dmc | 9+++++++--
dmc-tag | 2+-
doc/design | 5++++-
doc/pipeline | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
imap4.c | 39+++++++++++++++++++++++----------------
pop3.c | 119+++++++++++++++++++++++++++++++++++--------------------------------------------
sock.c | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 280 insertions(+), 90 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,12 +1,15 @@ -CC?=gcc -PREFIX?=/usr -CFLAGS?=-Wall +include config.mk -all: dmc-smtp dmc-pop3 dmc-imap4 dmc-pack +all: config.h dmc-smtp dmc-pop3 dmc-imap4 dmc-pack + +config.h: + @echo creating $@ from config.def.h + cp config.def.h config.h dmc-smtp: smtp.o ${CC} ${LDFLAGS} smtp.o -o dmc-smtp -lresolv +# sock.c ? dmc-pop3: pop3.o ${CC} ${LDFLAGS} pop3.o -o dmc-pop3 @@ -36,5 +39,12 @@ uninstall: clean: rm -f dmc-pop3 dmc-imap4 dmc-smtp dmc-pack *.o +dist: + mkdir -p dmc-${VERSION} + cd dmc-${VERSION} && hg clone .. . + rm -rf dmc-${VERSION}/.hg + tar czvf dmc-${VERSION}.tar.gz dmc-${VERSION} + rm -rf dmc-${VERSION} + loc: sloccount . diff --git a/config.def.h b/config.def.h @@ -0,0 +1 @@ +#define HAVE_SSL 0 diff --git a/config.mk b/config.mk @@ -0,0 +1,6 @@ +VERSION = 0.1 + +PREFIX ?= /usr +CFLAGS ?= -Wall + +CC ?= gcc diff --git a/dmc b/dmc @@ -16,6 +16,7 @@ mkdir -p ~/.dmc/acc function acc_daemon { FIFO=~/.dmc/tmp/${NAME}.fifo + OUTF=~/.dmc/tmp/${NAME}.out INPUT=~/.dmc/tmp/${NAME}.input OUTPUT=~/.dmc/tmp/${NAME}.output @@ -30,11 +31,13 @@ function acc_daemon { echo "Starting $NAME account daemon..." - rm -f "${INPUT}" "${FIFO}" + rm -f "${INPUT}" "${FIFO}" "${OUTF}" mkfifo "${INPUT}" "${FIFO}" +# XXX output must be also a fifo?!? + :> ${OUTF} echo login ${USER} ${PASS} > ${INPUT} & (while : ; do cat ${INPUT} 2> /dev/null ; done) | \ - dmc-${PROTOCOL} $FIFO 2> ${OUTPUT} | $NETCMD > $FIFO + dmc-${PROTOCOL} ${FIFO} ${OUTF} 2> ${OUTPUT} | $NETCMD > $FIFO rm -f ${INPUT} } @@ -46,6 +49,8 @@ function dmc_cmd { :> ${OUT} echo "$@" > ~/.dmc/tmp/${NAME}.input # XXX ultraugly way to check for eof + cat ~/.dmc/tmp/${NAME}.output # barrier + cat ${OUT} osize=0 while : ; do size=`du -hs ${OUT}|awk '{print $1}'` diff --git a/dmc-tag b/dmc-tag @@ -90,7 +90,7 @@ case "$1" in echo "dmc-tag-0.1 (c) 2009 pancake(at)nopcode(dot)org" ;; "") - echo "Usage: dmc-tag [-uhv] [-c [file ..]] [-m dir dir] [[,-u] [file] [[tag ..]]]" + echo "Usage: dmc-tag [-uhv] [-l [tag ..]] [-c [file ..]] [-m dir dir] [file ..]" ;; "-h") $0 diff --git a/doc/design b/doc/design @@ -34,7 +34,10 @@ sync The sync process is done independently from the backend. - - get list of last 50 mails + - get list of last $LIMIT mails + - for a in list + get checksum of header + if header checksum not found in cache - TODO diff --git a/doc/pipeline b/doc/pipeline @@ -0,0 +1,95 @@ +mail protocols +============== +imap4, pop3 ... are implemented as a simple unix-like applications which +parses a simple shell-like input and convert them into protocol-specific +queries. + +The network layer is abstracted by pipes, so you can use any application +to provide a transport layer to the protocol. This is 'netcat' for plain +TCP connections and 'openssl s_client' for SSL ones. + +From this point you need a fifo input to feed the program with commands +and a fifo to get the result of the command. + +The mail handler pipeline can be described as follows: + + + protocol input + .----------------------------------. + V | + +-------+ +-------+ +---------+ | + | input | stdin | pop3 | stdout | netcat | stdout | + | fifo |------->| imap4 |--------->| openssl |---------' + +-------+ +-------+ +---------+ + | | + | | stderr +--------+ +--------+ + | `---------->| status | | parsed | + | +--------+ | output | + | secondary output | result | + `------------------------->| of cmd | + +--------+ + +The understanding of this pipe graph reveals that the protocol application +requires two input channels and three output ones. + +Three of them are just the basic stdin, stdout, stderr filedescriptors, +and the other two are used to get feedback from the communication channel +and dump the result of the command to a file. + +In a posix shell you can implement the pipeline in this way: + + $ rm -f input fifo-in fifo-out output + $ mkfifo input fifo-in fifo-out + $ :> output + $ (while : ; do cat input ; done ) | \ + dmc-pop3 fifo-in output 2> fifo-out | nc host port > fifo-in + +In this way the fifo-in fifo file is used to push commands to the daemon. + +The fifo-out is used for the parent application to get notifications +when the command has been completely executed. Timeouts are suposed to +be handled by the protocol handler and the manager application should +understand that all those protocols are not going to give you results +inmediately. + +An implementation of a manager tool for the previous daemon is: + + $ echo login user pass > fifo-in + $ cat fifo-out # lock until command is executed + $ cat output # retrieve result of command + ... + +The simplest way to test if a protocol handler is working properly is: + + $ dmc-pop3 fifo-in /dev/stderr | nc host port > fifo-in + +The pipe graph can be hardly simplified if we embed the transport +layer inside the program. + + _________________ + / ssl, host, port \ + \______, ________/ + \| + +-------+ +-------+ stdout +--------+ + | input | stdin | pop3 |------------>| status | fifo lock + | fifo |------->| imap4 |--------. +--------+ + +-------+ +-------+ stderr \ +--------+ + `->| output | truncated file + +--------+ + +In this way the *nix pipeline becomes: + + $ dmc-pop3 tcp host port + +And the daemon pipeline will look like: + + $ mkfifo input outlock + $ :> output + $ (while : ; do cat input ; done ) | dmc-pop3 host port 2> outlock > output + +PROs: + - the pipeline is simpler + - the transport can change from plain to ssl + +CONs: + - ssl cert checks needs to be done inside the application diff --git a/imap4.c b/imap4.c @@ -1,21 +1,21 @@ /* dmc :: Copyleft 2009 -- pancake (at) nopcode (dot) org */ +#include <poll.h> +#include <fcntl.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> -#include <fcntl.h> -#include <poll.h> -#include <sys/types.h> #include <sys/stat.h> +#include <sys/types.h> static char *cmd = NULL; static char word[4096]; static int ctr = 1; -static char *fifo; -static int ff; +static char *fifo, *outf; +static int ff, fo; static char *dir; /* TODO: make getword() ready() and cleanup() shared between smtp,pop,imap? */ @@ -74,7 +74,8 @@ static int waitreply() { int lock = 1; int line = 0; int ret, reply = -1; - fflush(stdout); + char result[256]; + ftruncate (fo, 0); while(lock || !ready()) { lock = 0; ret = read(ff, str, 1024); @@ -91,20 +92,23 @@ static int waitreply() { else if (!memcmp(ptr+1, "NO", 2)) reply = 0; - else // TODO: Make 'BAD' be -1 ? + else // TODO: Do 'BAD' should be -1 ? if (!memcmp(ptr+1, "BAD", 3)) reply = 0; } // XXX: Fix output, just show first line - fprintf(stderr, "### %s %d \"%s\"\n", cmd, reply, str); + snprintf(result, 254, "### %s %d \"%s\"\n", cmd, reply, str); } str = str+strlen(str); line++; + write (fo, str, strlen (str)); // fprintf(stderr, "--> %s\n", str); } - fprintf(stderr, "==> (((%s)))\n", word); - fflush(stderr); - write(2, "\x00", 1); // end of output + + //fprintf (stderr, "==> (((%s)))\n", word); + write (fo, word, strlen (word)); + //fflush (stderr); + write (2, result, strlen(result)); return reply; } @@ -195,18 +199,21 @@ static int doword(char *word) { int main(int argc, char **argv) { int ret = 0; - if (argc>1) { + if (argc>2) { signal(SIGINT, cleanup); fifo = argv[1]; - mkfifo(fifo, 0600); - ff = open(fifo, O_RDONLY); - if (ff != -1) { + outf = argv[2]; + mkfifo (fifo, 0600); + unlink (outf); + ff = open (fifo, O_RDONLY); + fo = open (outf, O_WRONLY|O_CREAT, 0600); + if (ff != -1 && fo != -1) { dir = strdup(""); waitreply(); while(doword(getword())); cleanup(0); ret = 0; } else fprintf(stderr, "Cannot open fifo file.\n"); - } else fprintf(stderr, "Usage: dmc-imap4 fifo | nc host 443 > fifo\n"); + } else fprintf(stderr, "Usage: dmc-imap4 fifo-in fifo-out | nc host 443 > fifo\n"); return ret; } diff --git a/pop3.c b/pop3.c @@ -9,51 +9,40 @@ #include <sys/stat.h> #include <fcntl.h> #include <poll.h> - +#include "sock.c" static char *cmd = NULL; static char word[1024]; -static char *fifo; -static int ff; -/* XXX : Use of stdin FILE* fails when using fifo files */ -static char *getword() { - fscanf(stdin, "%255s", word); - if (feof(stdin)) +static char *getword () { + fscanf (stdin, "%255s", word); + if (feof (stdin)) *word = '\0'; return word; } -static int ready() { - struct pollfd fds[1]; - fds[0].fd = ff; - fds[0].events = POLLIN|POLLPRI; - fds[0].revents = POLLNVAL|POLLHUP|POLLERR; - return poll((struct pollfd *)&fds, 1, 10); -} - static int waitreply() { + char result[1024]; char *ch, *str = word; int lock = 1; - int ret, reply = -1; - fflush(stdout); - while(lock || ready()) { + int reply = -1; + + ftruncate (2, 0); + while(lock || sock_ready()) { lock = 0; - ret = read(ff, word, 512); - if (ret<1) + if (sock_read (word, 512) <1) break; - word[ret] = 0; if (reply==-1) { - ch = strchr(str, '\r'); - if (!ch) ch = strchr(str, '\n'); + ch = strchr (str, '\r'); + if (!ch) ch = strchr (str, '\n'); if (ch) { - if (!memcmp(word, "+OK", 3)) + if (!memcmp (word, "+OK", 3)) reply = 1; else - if (!memcmp(word, "-ERR", 4)) + if (!memcmp (word, "-ERR", 4)) reply = 0; *ch = 0; - fprintf(stderr, "%s %d \"%s\"\n", cmd, ret, str); + snprintf (result, 1023, "### %s %d \"%s\"\n", cmd, reply, str); str = ch+((ch[1]=='\n')?2:1); } } @@ -61,76 +50,74 @@ static int waitreply() { ch = strstr(str, "\r\n."); if (ch) *ch = '\0'; - fprintf(stderr, "%s\n", str); + //fprintf(stderr, "%s\n", str); + fputs (str, stderr); } + fputs("", stderr); fflush(stderr); + fputs (result, stdout); + fflush(stdout); /* stderr lseek works on pipes :D */ - lseek(2, 0, 0); - //if (*str) - // write(2, "\x00", 1); // end of output + lseek (2, 0, 0); return reply; } -static int doword(char *word) { +static int doword (char *word) { int ret = 1; free (cmd); - cmd = strdup(word); - //if (*word == '\0' || !strcmp(word, "exit")) { + cmd = strdup (word); + if (*word == '\0') { /* Do nothing */ } else - if (!strcmp(word, "exit")) { - printf("QUIT\n"); - waitreply(); + if (!strcmp (word, "exit")) { + sock_printf ("QUIT\n"); + waitreply (); ret = 0; } else - if (!strcmp(word, "help") || !strcmp(word, "?")) { + if (!strcmp (word, "help") || !strcmp (word, "?")) { fprintf(stderr, "Use: ls cat head rm login exit\n"); } else if (!strcmp(word, "ls")) { - printf("LIST\n"); + sock_printf("LIST\n"); waitreply(); } else if (!strcmp(word, "cat")) { - printf("RETR %d\n", atoi(getword())); - waitreply(); + sock_printf ("RETR %d\n", atoi(getword())); + waitreply (); } else if (!strcmp(word, "head")) { - printf("TOP %d 50\n", atoi(getword())); - waitreply(); + sock_printf ("TOP %d 50\n", atoi(getword())); + waitreply (); } else if (!strcmp(word, "rm")) { - printf("DELE %d\n", atoi(getword())); - waitreply(); + sock_printf ("DELE %d\n", atoi(getword())); + waitreply (); } else if (!strcmp(word, "login")) { - printf("USER %s\n", getword()); - waitreply(); - printf("PASS %s\n", getword()); - waitreply(); - } else printf("NOOP\n"); + sock_printf ("USER %s\n", getword()); + waitreply (); // TODO: if user fail, do not send pass + sock_printf ("PASS %s\n", getword()); + waitreply (); + } else sock_printf ("NOOP\n"); return ret; } -static void cleanup(int foo) { - close(ff); - unlink(fifo); - exit(0); +static void cleanup (void) { + sock_close (); + exit (0); } int main(int argc, char **argv) { - int ret = 0; - if (argc>1) { - signal(SIGINT, cleanup); - fifo = argv[1]; - mkfifo(fifo, 0600); - ff = open(fifo, O_RDONLY); - if (ff != -1) { - waitreply(); - while(doword(getword())); - cleanup(0); + int ret = 1; + if (argc>2) { + if (sock_connect (argv[1], atoi (argv[2])) >= 0) { ret = 0; - } else fprintf(stderr, "Cannot open fifo file.\n"); - } else fprintf(stderr, "Usage: dmc-pop3 fifo | nc host 110 > fifo\n"); - return ret; + atexit (cleanup); + waitreply (); + while (doword (getword())); + cleanup (); + } else fprintf (stderr, "Cannot connect to %s %d\n", argv[1], atoi(argv[2])); + } else fprintf (stderr, "Usage: dmc-pop3 host port 2> body > fifo < input\n"); + return 0; } diff --git a/sock.c b/sock.c @@ -0,0 +1,76 @@ +/* dmc :: Copyleft 2009 -- pancake (at) nopcode (dot) org */ + +#include <stdio.h> +#include <string.h> +#include <poll.h> +#include <stdarg.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include "config.h" + +#if HAVE_SSL +#include <openssl/ssl.h> +static int ssl = 0; +#endif + +static int fd = -1; + +// TODO: cleanup all those fd=-1 +int sock_connect (const char *host, int port) { + struct sockaddr_in sa; + struct hostent *he; + int s = socket (AF_INET, SOCK_STREAM, 0); + fd = -1; + if (s != -1) { + fd = s; + memset (&sa, 0, sizeof (sa)); + sa.sin_family = AF_INET; + he = (struct hostent *)gethostbyname (host); + if (he != (struct hostent*)0) { + sa.sin_addr = *((struct in_addr *)he->h_addr); + sa.sin_port = htons (port); + if (connect (fd, (const struct sockaddr*)&sa, sizeof (struct sockaddr))) + fd = -1; + } else fd = -1; + if (fd == -1) + close (s); + } else fd = -1; + return fd; +} + +static int sock_ready() { + struct pollfd fds[1]; + fds[0].fd = fd; + fds[0].events = POLLIN|POLLPRI; + fds[0].revents = POLLNVAL|POLLHUP|POLLERR; + return poll((struct pollfd *)&fds, 1, 10); +} + +int sock_close () { + return close (fd); +} + +int sock_write (const char *str) { + return write (fd, str, strlen(str)); +} + +int sock_printf (const char *fmt, ...) { + va_list ap; + int ret = -1; + char buf[1024]; + va_start (ap, fmt); + vsnprintf (buf, 1023, fmt, ap); + // XXX check len + ret = sock_write (buf); + va_end (ap); + return ret; +} + +int sock_read (char *buf, int len) { + int ret = read (fd, buf, 1024); + if (ret>0) + buf[ret] = '\0'; + return ret; +}