w3mail

program to send a web page by email
git clone https://logand.com/git/w3mail.git/
Log | Files | Refs | README | LICENSE

commit 413c92ef704db1b00cc40427e41455c045690f2c
parent 954997f8407b5ed6e70a7828120cdf46215bc3ce
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun, 16 Jan 2011 14:04:38 +0100

dirpop3d added

Diffstat:
MMakefile | 19+++++++++++++------
Adirpop3d.c | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adirpop3d.vala | 264+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils.c | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils.h | 28++++++++++++++++++++++++++++
Mw3mail.c | 88+++++++++++--------------------------------------------------------------------
Dw3maild.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); - } -}