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;
+}