commit 413c92ef704db1b00cc40427e41455c045690f2c
parent 954997f8407b5ed6e70a7828120cdf46215bc3ce
Author: Tomas Hlavaty <tom@logand.com>
Date: Sun, 16 Jan 2011 14:04:38 +0100
dirpop3d added
Diffstat:
M | Makefile | | | 19 | +++++++++++++------ |
A | dirpop3d.c | | | 256 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | dirpop3d.vala | | | 264 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | utils.c | | | 79 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | utils.h | | | 28 | ++++++++++++++++++++++++++++ |
M | w3mail.c | | | 88 | +++++++++++-------------------------------------------------------------------- |
D | w3maild.vala | | | 300 | ------------------------------------------------------------------------------- |
7 files changed, 652 insertions(+), 382 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,10 +1,17 @@
-all: w3mail w3maild
+all: w3mail dirpop3d
-w3mail: w3mail.c
- $(CC) -Wall -std=c99 -pedantic -D_XOPEN_SOURCE=600 -o w3mail w3mail.c
+w3mail: utils.o w3mail.c
+ $(CC) -Wall -std=c99 -pedantic -D_XOPEN_SOURCE=600 -o w3mail w3mail.c utils.o
-w3maild: w3maild.vala
- valac -g --pkg gio-2.0 --pkg gee-1.0 w3maild.vala
+#dirpop3d: dirpop3d.vala
+# valac -g --pkg gio-2.0 --pkg gee-1.0 dirpop3d.vala
+
+dirpop3d: utils.o dirpop3d.c
+# dirpop3d.vala
+ $(CC) -Wall -std=c99 -pedantic -D_XOPEN_SOURCE=600 -o dirpop3d dirpop3d.c utils.o
+
+utils.o: utils.h utils.c
+ $(CC) -Wall -std=c99 -pedantic -D_XOPEN_SOURCE=600 -c -o utils.o utils.c
clean:
- rm -f *~ w3mail w3maild
+ rm -f *~ *.o w3mail dirpop3d
diff --git a/dirpop3d.c b/dirpop3d.c
@@ -0,0 +1,256 @@
+// ???
+// Copyright (C) 2010 Tomas Hlavaty
+//
+// This program 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 3 of the License, or
+// (at your option) any later version.
+//
+// This program 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, see <http://www.gnu.org/licenses/>.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "utils.h"
+
+#define QLEN 1
+
+typedef void tcp_handler(int fd);
+
+static void tcp_server(int port, tcp_handler handler) {
+ int fds;
+ if((fds = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
+ die(1, "socket() failed");
+ int optval = 1;
+ if(setsockopt(fds, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
+ die(1, "setsockopt() failed");
+ struct sockaddr_in sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.sin_family = AF_INET;
+ sa.sin_addr.s_addr = inet_addr ("127.0.0.1"); //htonl(INADDR_ANY);
+ sa.sin_port = htons(port);
+ if(bind(fds, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+ die(1, "bind() failed");
+ if(listen(fds, QLEN) < 0)
+ die(1, "listen() failed");
+ for(;;) {
+ struct sockaddr_in ca;
+ unsigned int n = sizeof(ca);
+ int fdc;
+ if((fdc = accept(fds, (struct sockaddr *) &ca, &n)) < 0)
+ die(1, "accept() failed");
+ handler(fdc);
+ }
+}
+
+static int crlf(char *buf, int n) {
+ if(BLEN < n + 3) die(1, "crlf(): buffer too small %d", n);
+ buf[n] = '\r';
+ buf[n + 1] = '\n';
+ buf[n + 2] = 0;
+ return n + 2;
+}
+
+static void pr(int fd, char *fmt, ...) {
+ va_list v;
+ va_start(v, fmt);
+ char buf[BLEN];
+ int n = vsnprintf(buf, BLEN, fmt, v);
+ n = crlf(buf, n);
+ if(send(fd, buf, sizeof(char) * n, 0) < 0)
+ die(1, "pr(): send() failed");
+ va_end(v);
+}
+
+struct xstat {
+ int nfiles;
+ int nbytes;
+};
+
+static void stat_cb(void *udata, char *path, struct dirent *e, struct stat *s) {
+ struct xstat *x = udata;
+ if(!S_ISREG(s->st_mode)) return;
+ x->nfiles += 1;
+ x->nbytes += s->st_size;
+}
+
+struct xlist {
+ int nfiles;
+ int fd;
+};
+
+static void list_cb(void *udata, char *path, struct dirent *e, struct stat *s) {
+ struct xlist *x = udata;
+ if(!S_ISREG(s->st_mode)) return;
+ pr(x->fd, "%d %d", ++x->nfiles, s->st_size);
+}
+
+struct xretr {
+ int n;
+ int nfiles;
+ int fd;
+};
+
+static void retr_cb(void *udata, char *path, struct dirent *e, struct stat *s) {
+ struct xretr *x = udata;
+ if(!S_ISREG(s->st_mode) || (x->n != ++x->nfiles)) return;
+ pr(x->fd, "+OK %d octets", s->st_size);
+ char fname[BLEN];
+ snprintf(fname, BLEN, "%s/%s", path, e->d_name);
+ FILE *in = fopen(fname, "r");
+ if(!in) die(1, "echo(): cannot open input file '%s'", fname);
+ // Note that the served files must have lines shorter than BLEN and
+ // no single dot char on its own line. Therefore use base64 for the
+ // message body.
+ char buf[BLEN];
+ while(fgets(buf, BLEN, in)) {
+ rtrim(buf);
+ pr(x->fd, "%s", buf);
+ }
+ fclose(in);
+ pr(x->fd, ".");
+}
+
+static void dele_cb(void *udata, char *path, struct dirent *e, struct stat *s) {
+ struct xretr *x = udata;
+ if(!S_ISREG(s->st_mode) || (x->n != ++x->nfiles)) return;
+
+ char fname[BLEN];
+ snprintf(fname, BLEN, "%s/%s", path, e->d_name);
+ unlink(fname);
+
+ pr(x->fd, "+OK message %d deleted", x->n);
+}
+
+struct fmsg {
+ int deleted;
+ int nbytes;
+ char *fname;
+};
+
+struct flist {
+ int n;
+ fmsg msg[];
+};
+
+static struct flist *flist_new(int n) {
+ struct flist *x = malloc(sizeof(int) + (sizeof(int) + sizeof(char *)) * n);
+ x->n = n;
+ for(int i = 0; i < x->n; i++) {
+ x->msg[i].deleted = 0;
+ x->msg[i].fname = NULL;
+ }
+ return x;
+}
+
+static void flist_free(struct flist *x) {
+ for(int i = 0; i < x->n; i++) {
+ free(x->msg[i].fname);
+ }
+ free(x);
+}
+
+static char *rootdir;
+
+static void pop3_handler(int fd) {
+ pr(fd, "+OK dirpop3d ready");
+ enum {START, USER, PASS, LIST} state = START;
+ char cmd[BLEN], usr[BLEN], pwd[BLEN];
+ struct flist *flist = NULL;
+ char buf[BLEN];
+ for(int n; 0 < (n = recv(fd, buf, sizeof(char) * BLEN, 0));) {
+ rtrim(buf);
+ sscanf(buf, "%s", cmd);
+ if(0 == strcmp("QUIT", cmd)) {
+ pr(fd, "+OK dirpop3d bye");
+ break;
+ }
+ else if(0 == strcmp("USER", cmd)) {
+ if(state != START) pr(fd, "-ERR");
+ else {
+ sscanf(buf, "%s %s", cmd, usr);
+ pr(fd, "+OK hi %s", usr);
+ state = USER;
+ }
+ }
+ else if(0 == strcmp("PASS", cmd)) {
+ if(state != USER) pr(fd, "-ERR");
+ else {
+ sscanf(buf, "%s %s", cmd, pwd);
+ pr(fd, "+OK welcome %s", usr);
+ state = PASS;
+ }
+ }
+ /* else if(0 == strcmp("STAT", cmd)) { */
+ /* if(state != PASS && state != LIST) pr(fd, "-ERR"); */
+ /* else { */
+ /* struct xstat x = {0, 0}; */
+ /* char buf[BLEN]; */
+ /* snprintf(buf, BLEN, "%s/%s", rootdir, usr); */
+ /* dir(&x, stat_cb, buf); */
+ /* pr(fd, "+OK %d %d", x.nfiles, x.nbytes); */
+ /* state = LIST; */
+ /* } */
+ /* } */
+ else if(0 == strcmp("LIST", cmd)) {
+ if(state != PASS && state != LIST) pr(fd, "-ERR");
+ else {
+ char buf[BLEN];
+ snprintf(buf, BLEN, "%s/%s", rootdir, usr);
+ struct xstat x = {0, 0};
+ dir(&x, stat_cb, buf);
+ pr(fd, "+OK %d messages (%d octets)", x.nfiles, x.nbytes);
+ flist = flist_new(x.nfiles);
+ struct xlist y = {0, fd};
+ dir(&y, list_cb, buf);
+ pr(fd, ".");
+ state = LIST;
+ }
+ }
+ else if(0 == strcmp("RETR", cmd)) {
+ if(state != LIST) pr(fd, "-ERR");
+ else {
+ struct xretr x = {0, 0, fd};
+ sscanf(buf, "%s %d", cmd, &x.n);
+ if(0 < x.n) {
+ char buf[BLEN];
+ snprintf(buf, BLEN, "%s/%s", rootdir, usr);
+ dir(&x, retr_cb, buf);
+ }
+ else pr(fd, "-ERR");
+ }
+ }
+ else if(0 == strcmp("DELE", cmd)) {
+ if(state != LIST) pr(fd, "-ERR");
+ else {
+ struct xretr x = {0, 0, fd};
+ sscanf(buf, "%s %d", cmd, &x.n);
+ if(0 < x.n) {
+ char buf[BLEN];
+ snprintf(buf, BLEN, "%s/%s", rootdir, usr);
+ dir(&x, dele_cb, buf);
+ }
+ else pr(fd, "-ERR");
+ }
+ }
+ else pr(fd, "-ERR wrong command");
+ }
+ close(fd);
+}
+
+int main(int argc, char *argv[]) {
+ if(argc != 3) quit(1, "Usage: %s port rootdir", argv[0]);
+ rootdir = argv[2];
+ tcp_server(atoi(argv[1]), pop3_handler);
+}
diff --git a/dirpop3d.vala b/dirpop3d.vala
@@ -0,0 +1,264 @@
+// dirpop3d.vala -- Directory serving pop3 server based on glib.
+// (c) 2011 Tomas Hlavaty
+
+using Gee;
+
+abstract class Pop3Server {
+ SocketConnection c;
+ DataInputStream i;
+ DataOutputStream o;
+ State s;
+
+ enum State {
+ START,
+ USER,
+ PASS,
+ LIST
+ }
+
+ public Pop3Server (SocketConnection _c) {
+ c = _c;
+ i = new DataInputStream (c.input_stream);
+ o = new DataOutputStream (c.output_stream);
+ s = State.START;
+ }
+
+ protected void prinl (string x) throws GLib.IOError, GLib.Error {
+ o.put_string ("%s\n".printf (x));
+ //o.flush ();
+ stderr.printf ("%s\n", x);
+ //stderr.flush ();
+ }
+
+ protected void ok (string msg) throws GLib.IOError, GLib.Error {
+ o.put_string ("+OK %s\n".printf (msg));
+ //o.flush ();
+ stderr.printf ("+OK %s\n", msg);
+ //stderr.flush ();
+ }
+
+ protected void ok2 (string msg1, string msg2) throws GLib.IOError, GLib.Error {
+ o.put_string ("+OK %s %s\n".printf (msg1, msg2));
+ //o.flush ();
+ stderr.printf ("+OK %s %s\n", msg1, msg2);
+ //stderr.flush ();
+ }
+
+ protected void err (string msg) throws GLib.IOError, GLib.Error {
+ o.put_string ("-ERR error\n");
+ //o.flush ();
+ stderr.printf ("-ERR error\n");
+ //stderr.flush ();
+ }
+
+ public async void process_request_async () {
+ try {
+ ready ();
+ string line = null;
+ while ((line = yield i.read_line_async (Priority.HIGH_IDLE)) != null) {
+ stderr.printf ("%s\n", line);
+ var cmd = parse_line (line);
+ if(!process_command (cmd)) break;
+ }
+ } catch (Error e) {
+ stderr.printf ("Error: %s\n", e.message);
+ }
+ }
+
+ protected string[] parse_line (string line) {
+ return line.chomp().split(" ");
+ }
+
+ protected bool process_command (string[] cmd) throws GLib.IOError, GLib.Error {
+ switch(cmd[0]) {
+ case "QUIT":
+ return quit (cmd);
+ case "USER":
+ if(s != State.START) return false;
+ var x = user (cmd);
+ if(x) s = State.USER;
+ return x;
+ case "PASS":
+ if(s != State.USER) return false;
+ var x = pass (cmd);
+ if(x) s = State.PASS;
+ return x;
+ case "STAT":
+ if(s != State.PASS && s != State.LIST) return false;
+ var x = stat (cmd);
+ if(x) s = State.LIST;
+ return x;
+ case "LIST":
+ if(s != State.PASS && s != State.LIST) return false;
+ var x = list (cmd);
+ if(x) s = State.LIST;
+ return x;
+ case "RETR":
+ if(s != State.LIST) return false;
+ return retr (cmd);
+ case "DELE":
+ if(s != State.LIST) return false;
+ return dele (cmd);
+ default: return otherwise (cmd);
+ }
+ }
+
+ protected abstract void ready () throws GLib.IOError, GLib.Error;
+ protected abstract bool quit (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool user (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool pass (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool stat (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool list (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool retr (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool dele (string[] cmd) throws GLib.IOError, GLib.Error;
+ protected abstract bool otherwise (string[] cmd) throws GLib.IOError, GLib.Error;
+}
+
+static string root_dirname () { // TODO config
+ return Environment.get_home_dir () + "/.w3maild";
+}
+
+class Dirpop3Server : Pop3Server {
+ string usr = "";
+ string pwd = "";
+ ArrayList<FileInfo> lst = new ArrayList<FileInfo> ();
+
+ public Dirpop3Server (SocketConnection c) {
+ base (c);
+ }
+
+ protected override void ready () throws GLib.IOError, GLib.Error {
+ ok ("dirpop3d ready");
+ }
+
+ protected override bool quit (string[] cmd) throws GLib.IOError, GLib.Error {
+ ok2 (usr, "dirpop3d bye");
+ return false;
+ }
+
+ string data_dirname () {
+ return root_dirname () + "/" + usr;
+ }
+
+ File data_dir () {
+ return File.new_for_path (data_dirname ());
+ }
+
+ bool user_exists () {
+ if(usr.length < 1) return false;
+ return data_dir ().query_file_type (0) == FileType.DIRECTORY;
+ }
+
+ protected override bool user (string[] cmd) throws GLib.IOError, GLib.Error {
+ usr = cmd[1];
+ if(!user_exists ()) return false;
+ ok ("User accepted");
+ return true;
+ }
+
+ protected override bool pass (string[] cmd) throws GLib.IOError, GLib.Error {
+ pwd = cmd[1];
+ ok ("Pass accepted");
+ return true;
+ }
+
+ void update_lst () {
+ lst.clear ();
+ try {
+ File dir = data_dir ();
+ var i = dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME + "," +
+ FILE_ATTRIBUTE_STANDARD_TYPE,
+ FileQueryInfoFlags.NONE,
+ null);
+ FileInfo info;
+ while ((info = i.next_file (null)) != null)
+ if(info.get_file_type () == FileType.REGULAR)
+ lst.add (info);
+ } catch (Error e) {
+ stderr.printf ("Error: %s\n", e.message);
+ }
+ }
+
+ void collect_stat (out int cnt, out uint64 siz) {
+ siz = 0;
+ update_lst ();
+ for (cnt = 0; cnt < lst.size; cnt++)
+ siz += lst[cnt].get_size ();
+ }
+
+ protected override bool stat (string[] cmd) throws GLib.IOError, GLib.Error {
+ int cnt = 0;
+ uint64 siz = 0;
+ collect_stat (out cnt, out siz);
+ ok (cnt.to_string () + " " + siz.to_string ());
+ return true;
+ }
+
+ protected override bool list (string[] cmd) throws GLib.IOError, GLib.Error {
+ int cnt = 0;
+ uint64 siz = 0;
+ collect_stat (out cnt, out siz);
+ ok (cnt.to_string () + " messages (" + siz.to_string () + " octets)");
+ siz = 0;
+ for (int i = 0; i < lst.size; i++) {
+ prinl ((i + 1).to_string () + " " + lst[i].get_size ().to_string ());
+ }
+ prinl (".");
+ return true;
+ }
+
+ string data_filename (string name) {
+ return data_dirname () + "/" + name;
+ }
+
+ File data_file (string name) {
+ return File.new_for_path (data_filename (name));
+ }
+
+ protected override bool retr (string[] cmd) throws GLib.IOError, GLib.Error {
+ var i = cmd[1].to_int ();
+ if (lst[i] == null) return false;
+ ok (lst[i].get_size ().to_string () + " octets");
+ var file = data_file (lst[i].get_name ());
+ try {
+ var dis = new DataInputStream (file.read ());
+ string line;
+ while ((line = dis.read_line (null)) != null)
+ prinl (line == "." ? ".." : line);
+ prinl (".");
+ return true;
+ } catch (Error e) {
+ stderr.printf ("%s\n", e.message);
+ }
+ return false;
+ }
+
+ protected override bool dele (string[] cmd) throws GLib.IOError, GLib.Error {
+ var i = cmd[1].to_int ();
+ data_file (lst[i].get_name ()).delete ();
+ ok ("message " + i.to_string () + " deleted");
+ return true;
+ }
+
+ protected override bool otherwise (string[] cmd) throws GLib.IOError, GLib.Error {
+ return false;
+ }
+}
+
+static bool on_incoming_connection (SocketConnection c) {
+ var s = new Dirpop3Server (c);
+ s.process_request_async.begin ();
+ return true;
+}
+
+void main () {
+ try {
+ var srv = new SocketService ();
+ srv.add_inet_port (3333, null); // TODO config port
+ srv.incoming.connect (on_incoming_connection);
+ srv.start ();
+ new MainLoop ().run ();
+ } catch (Error e) {
+ stderr.printf ("%s\n", e.message);
+ }
+}
diff --git a/utils.c b/utils.c
@@ -0,0 +1,79 @@
+#include "utils.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+const int BLEN = 4 * 1024;
+
+void quit(int code, char *fmt, ...) {
+ va_list v;
+ va_start(v, fmt);
+ vfprintf(stderr, fmt, v);
+ exit(code);
+ va_end(v);
+}
+
+void die(int code, char *fmt, ...) {
+ va_list v;
+ va_start(v, fmt);
+ char msg[BLEN];
+ vsnprintf(msg, BLEN, fmt, v);
+ perror(msg);
+ exit(code);
+ va_end(v);
+}
+
+void rtrim(char *s) {
+ if(!s) die(1, "rtrim(): nothing to trim");
+ char *e = s + strlen(s) - 1;
+ while(s < e && isspace(*e)) *e-- = '\0';
+}
+
+void fpr(FILE *out, ...) {
+ va_list v;
+ va_start(v, out);
+ char *a;
+ while((a = va_arg(v, char *))) fprintf(out, "%s", a);
+ va_end(v);
+}
+
+void in(void *udata, in_cb cb, char *cmd) {
+ FILE *pipe = popen(cmd, "r");
+ if(!pipe) die(1, "in(): cannot open pipe to '%s'", cmd);
+ cb(udata, pipe);
+ pclose(pipe);
+}
+
+void out(void *udata, out_cb cb, char *cmd) {
+ FILE *pipe = popen(cmd, "w");
+ if(!pipe) die(1, "out(): cannot open pipe to '%s'", cmd);
+ cb(udata, pipe);
+ pclose(pipe);
+}
+
+void tmpf(void *udata, tmpf_cb cb, char *fname) {
+ int fd = mkstemp(fname);
+ if(fd == -1) die(errno, "tmpf(): cannot create temporary file '%s'", fname);
+ close(fd);
+ cb(udata, fname);
+ unlink(fname);
+}
+
+void dir(void *udata, dir_cb cb, char *path) {
+ DIR *d = opendir (path);
+ if(!d) die(1, "dir(): cannot open dir '%s'", path);
+ struct dirent *e;
+ char buf[BLEN];
+ while((e = readdir(d))) {
+ struct stat s;
+ snprintf(buf, BLEN, "%s/%s", path, e->d_name);
+ if(lstat(buf, &s) < 0)
+ die(1, "stat_cb(): lstat('%s') failed", buf);
+ cb(udata, path, e, &s);
+ }
+ closedir(d);
+}
diff --git a/utils.h b/utils.h
@@ -0,0 +1,28 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+extern const int BLEN;
+
+extern void quit(int code, char *fmt, ...);
+extern void die(int code, char *fmt, ...);
+
+extern void rtrim(char *s);
+
+extern void fpr(FILE *out, ...);
+
+typedef void (*in_cb)(void *udata, FILE* in);
+typedef void (*out_cb)(void *udata, FILE* out);
+typedef void (*tmpf_cb)(void *udata, char *fname);
+typedef void (*dir_cb)(void *udata, char *path, struct dirent *e, struct stat *s); // TODO return bool
+
+extern void in(void *udata, in_cb cb, char *cmd);
+extern void out(void *udata, out_cb cb, char *cmd);
+extern void tmpf(void *udata, tmpf_cb cb, char *fname);
+
+extern void dir(void *udata, dir_cb cb, char *path);
+
+#endif /* UTILS_H */
diff --git a/w3mail.c b/w3mail.c
@@ -14,68 +14,12 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
-#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <stdarg.h>
-#include <errno.h>
-#include <unistd.h>
#include <sys/wait.h>
+#include <unistd.h>
-const int BLEN = 4 * 1024;
-
-static void quit(int code, char *fmt, ...) {
- va_list v;
- va_start(v, fmt);
- vfprintf(stderr, fmt, v);
- exit(code);
- va_end(v);
-}
-
-static void die(int code, char *fmt, ...) {
- va_list v;
- va_start(v, fmt);
- char msg[BLEN];
- vsnprintf(msg, BLEN, fmt, v);
- perror(msg);
- exit(code);
- va_end(v);
-}
-
-typedef void (*in_cb)(void *udata, FILE* in);
-typedef void (*out_cb)(void *udata, FILE* out);
-typedef void (*tmpf_cb)(void *udata, char *fname);
-
-static void in(void *udata, in_cb cb, char *cmd) {
- FILE *pipe = popen(cmd, "r");
- if(!pipe) die(1, "in(): cannot open pipe to '%s'", cmd);
- cb(udata, pipe);
- pclose(pipe);
-}
-
-static void out(void *udata, out_cb cb, char *cmd) {
- FILE *pipe = popen(cmd, "w");
- if(!pipe) die(1, "out(): cannot open pipe to '%s'", cmd);
- cb(udata, pipe);
- pclose(pipe);
-}
-
-static void tmpf(void *udata, tmpf_cb cb, char *fname) {
- int fd = mkstemp(fname);
- if(fd == -1) die(errno, "tmpf(): cannot create temporary file '%s'", fname);
- close(fd);
- cb(udata, fname);
- unlink(fname);
-}
-
-static void fpr(FILE *out, ...) {
- va_list v;
- va_start(v, out);
- char *a;
- while((a = va_arg(v, char *))) fprintf(out, "%s", a);
- va_end(v);
-}
+#include "utils.h"
static void wget(char *url, char *fname) {
// http://www.gnu.org/software/wget/manual/html_node/Exit-Status.html
@@ -126,18 +70,14 @@ static void md5sum(char *fname, char *sum) {
in(sum, cb_md5sum, cmd);
}
-static void echo(char *fname, FILE *out) {
- FILE *in = fopen(fname, "r");
- if(!in) die(1, "echo(): cannot open input file '%s'", fname);
+static void cb_echo(void *udata, FILE* in) {
+ FILE *out = udata;
char buf[BLEN];
int n;
while(0 < (n = fread(buf, sizeof(char), BLEN, in)))
fwrite(buf, sizeof(char), n, out);
- fclose(in);
}
-#define pr(...) {fpr(out, __VA_ARGS__, NULL); fprintf(out, "\r\n");}
-
struct sendmail {
char *from;
char *to;
@@ -146,6 +86,8 @@ struct sendmail {
char *fname;
};
+#define pr(...) {fpr(out, __VA_ARGS__, NULL); fprintf(out, "\n");}
+
static void cb_sendmail(void *udata, FILE* out) {
struct sendmail *x = udata;
pr("From: ", x->from);
@@ -154,13 +96,13 @@ static void cb_sendmail(void *udata, FILE* out) {
pr("Subject: ", x->subj);
pr("User-Agent: w3mail");
pr("MIME-Version: 1.0");
- pr("Content-Type: text/html"); // TODO base64 or ; charset=utf-8
- pr("Content-Disposition: inline; filename=", x->fname);
- pr("Content-Transfer-Encoding: 8bit");
- pr("");
- echo(x->fname, out);
- pr("");
+ pr("Content-Type: text/html"); // TODO detect and ; charset=utf-8
+ pr("Content-Disposition: inline; filename=", x->fname); // TODO use better fname
+ pr("Content-Transfer-Encoding: base64");
pr("");
+ char cmd[BLEN];
+ snprintf(cmd, BLEN, "base64 %s", x->fname);
+ in(out, cb_echo, cmd);
}
struct w3mail {
@@ -182,12 +124,6 @@ static void cb_w3mail(void *udata, char *fname) {
out(&y, cb_sendmail, x->cmd);
}
-static void rtrim(char *s) {
- if(!s) die(1, "rtrim(): nothing to trim");
- char *e = s + strlen(s) - 1;
- while(s < e && isspace(*e)) *e-- = '\0';
-}
-
static void cb_config(void *udata, FILE* in) {
struct w3mail *x = udata;
rtrim(fgets(x->cmd, BLEN, in));
diff --git a/w3maild.vala b/w3maild.vala
@@ -1,300 +0,0 @@
-// w3maild.vala -- Get web pages into your inbox via pop3 server.
-// (c) 2011 Tomas Hlavaty
-
-using Gee;
-
-abstract class Pop3Server {
- SocketConnection c;
- DataInputStream i;
- DataOutputStream o;
- State s;
-
- enum State {
- START,
- USER,
- PASS,
- LIST
- }
-
- public Pop3Server (SocketConnection _c) {
- c = _c;
- i = new DataInputStream (c.input_stream);
- o = new DataOutputStream (c.output_stream);
- s = State.START;
- }
-
- protected void prinl (string x) throws GLib.IOError, GLib.Error {
- o.put_string (x);
- o.put_string ("\n");
- o.flush ();
- }
-
- protected void ok (string msg) throws GLib.IOError, GLib.Error {
- o.put_string ("+OK ");
- prinl (msg);
- }
-
- protected void ok2 (string msg1, string msg2) throws GLib.IOError, GLib.Error {
- o.put_string ("+OK ");
- o.put_string (msg1);
- o.put_string (" ");
- prinl (msg2);
- }
-
- public async void process_request_async () {
- try {
- ready ();
- string line = null;
- while ((line = yield i.read_line_async (Priority.HIGH_IDLE)) != null) {
- var cmd = parse_line (line);
- if(!process_command (cmd)) break;
- }
- } catch (Error e) {
- stderr.printf ("Error: %s\n", e.message);
- }
- }
-
- protected string[] parse_line (string line) {
- return line.chomp().split(" ");
- }
-
- protected bool process_command (string[] cmd) throws GLib.IOError, GLib.Error {
- switch(cmd[0]) {
- case "QUIT":
- return quit (cmd);
- case "USER":
- if(s != State.START) return false;
- var x = user (cmd);
- if(x) s = State.USER;
- return x;
- case "PASS":
- if(s != State.USER) return false;
- var x = pass (cmd);
- if(x) s = State.PASS;
- return x;
- case "STAT":
- if(s != State.PASS && s != State.LIST) return false;
- var x = stat (cmd);
- if(x) s = State.LIST;
- return x;
- case "LIST":
- if(s != State.PASS && s != State.LIST) return false;
- var x = list (cmd);
- if(x) s = State.LIST;
- return x;
- case "RETR":
- if(s != State.LIST) return false;
- return retr (cmd);
- case "DELE":
- if(s != State.LIST) return false;
- return dele (cmd);
- default: return otherwise (cmd);
- }
- }
-
- protected abstract void ready () throws GLib.IOError, GLib.Error;
- protected abstract bool quit (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool user (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool pass (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool stat (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool list (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool retr (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool dele (string[] cmd) throws GLib.IOError, GLib.Error;
- protected abstract bool otherwise (string[] cmd) throws GLib.IOError, GLib.Error;
-}
-
-// ~/.w3maild
-// ~/.w3maild/[tomas]
-// ~/.w3maild/[tomas]/[data]
-// ~/.w3maild/in
-// ~/.w3maild/log
-
-static string root_dirname () {
- return Environment.get_home_dir () + "/.w3maild";
-}
-
-class W3maildPop3Server : Pop3Server {
- string usr = "";
- string pwd = "";
- HashMap<int, FileInfo> map = new HashMap<int, FileInfo> ();
-
- public W3maildPop3Server (SocketConnection c) {
- base (c);
- }
-
- protected override void ready () throws GLib.IOError, GLib.Error {
- ok ("w3maild ready");
- }
-
- protected override bool quit (string[] cmd) throws GLib.IOError, GLib.Error {
- ok2 (usr, "w3maild bye");
- return false;
- }
-
- string data_dirname () {
- return root_dirname () + "/" + usr;
- }
-
- File data_dir () {
- return File.new_for_path (data_dirname ());
- }
-
- bool user_exists () {
- if(usr.length < 1) return false;
- return data_dir ().query_file_type (0) == FileType.DIRECTORY;
- }
-
- protected override bool user (string[] cmd) throws GLib.IOError, GLib.Error {
- usr = cmd[1];
- if(!user_exists ()) return false;
- ok ("User accepted");
- return true;
- }
-
- protected override bool pass (string[] cmd) throws GLib.IOError, GLib.Error {
- pwd = cmd[1];
- ok ("Pass accepted");
- return true;
- }
-
- void update_map () {
- int cnt = 0;
- map.clear ();
- try {
- File dir = data_dir ();
- var i = dir.enumerate_children (FILE_ATTRIBUTE_STANDARD_NAME + "," +
- FILE_ATTRIBUTE_STANDARD_TYPE,
- FileQueryInfoFlags.NONE,
- null);
- FileInfo info;
- while ((info = i.next_file (null)) != null)
- if(info.get_file_type () == FileType.REGULAR)
- map.set (++cnt, info);
- } catch (Error e) {
- stderr.printf ("Error: %s\n", e.message);
- }
- }
-
- void collect_stat (out int cnt, out uint64 siz) {
- cnt = 0;
- siz = 0;
- update_map ();
- foreach (int i in map.keys) {
- cnt++;
- siz += map[i].get_size ();
- }
- }
-
- protected override bool stat (string[] cmd) throws GLib.IOError, GLib.Error {
- int cnt = 0;
- uint64 siz = 0;
- collect_stat (out cnt, out siz);
- ok (cnt.to_string () + " " + siz.to_string ());
- return true;
- }
-
- protected override bool list (string[] cmd) throws GLib.IOError, GLib.Error {
- int cnt = 0;
- uint64 siz = 0;
- collect_stat (out cnt, out siz);
- ok (cnt.to_string () + " messages (" + siz.to_string () + " octets)");
- cnt = 0;
- siz = 0;
- foreach (int i in map.keys)
- prinl (i.to_string () + " " + map[i].get_size ().to_string ());
- prinl (".");
- return true;
- }
-
- string data_filename (string name) {
- return data_dirname () + "/" + name;
- }
-
- File data_file (string name) {
- return File.new_for_path (data_filename (name));
- }
-
- protected override bool retr (string[] cmd) throws GLib.IOError, GLib.Error {
- var i = cmd[1].to_int ();
- if (map[i] == null) return false;
- ok (map[i].get_size ().to_string () + " octets");
- var file = data_file (map[i].get_name ());
- try {
- var dis = new DataInputStream (file.read ());
- string line;
- while ((line = dis.read_line (null)) != null)
- prinl (line == "." ? ".." : line);
- prinl (".");
- return true;
- } catch (Error e) {
- stderr.printf ("%s\n", e.message);
- }
- return false;
- }
-
- protected override bool dele (string[] cmd) throws GLib.IOError, GLib.Error {
- var i = cmd[1].to_int ();
- data_file (map[i].get_name ()).delete ();
- ok ("message " + i.to_string () + " deleted");
- return true;
- }
-
- protected override bool otherwise (string[] cmd) throws GLib.IOError, GLib.Error {
- return false;
- }
-}
-
-static bool on_incoming_connection (SocketConnection c) {
- var s = new W3maildPop3Server (c);
- s.process_request_async.begin ();
- return true;
-}
-
-// async void url_server_read_async () {
-// var file = File.new_for_path (root_dirname () + "/in");
-// try {
-// var i = new DataInputStream (file.read ());
-// string line = null;
-// while ((line = yield i.read_line_async (Priority.HIGH_IDLE)) != null) {
-// stderr.printf ("%s\n", line);
-// }
-// stdout.printf ("finito\n");
-// } catch (Error e) {
-// stderr.printf ("%s\n", e.message);
-// }
-// //main_loop.quit ();
-// }
-
-// bool url_server_read () {
-// var file = File.new_for_path (root_dirname () + "/in");
-// try {
-// var i = new DataInputStream (file.read ());
-// string line = null;
-// while ((line = yield i.read_line_async (Priority.HIGH_IDLE)) != null) {
-// stderr.printf ("%s\n", line);
-// }
-// stdout.printf ("finito\n");
-// } catch (Error e) {
-// stderr.printf ("%s\n", e.message);
-// }
-// return true;
-// }
-
-void main () {
- try {
- // start pop3 server
- var srv = new SocketService ();
- srv.add_inet_port (3333, null);
- srv.incoming.connect (on_incoming_connection);
- srv.start ();
- // start url server
- //url_server_read_async.begin ();
- //Idle.add(url_server_read_async.callback);
- //Idle.add(url_server_read);
- // go
- new MainLoop ().run ();
- //main_loop.run ();
- } catch (Error e) {
- stderr.printf ("%s\n", e.message);
- }
-}