2546 lines
45 KiB
C
2546 lines
45 KiB
C
#include "common.h"
|
|
#include <ctype.h>
|
|
#include <plumb.h>
|
|
#include <9pclient.h>
|
|
#include <thread.h>
|
|
|
|
#define system nedsystem
|
|
|
|
typedef struct Message Message;
|
|
typedef struct Ctype Ctype;
|
|
typedef struct Cmd Cmd;
|
|
|
|
char root[Pathlen];
|
|
char mbname[Elemlen];
|
|
int rootlen;
|
|
int didopen;
|
|
char *user;
|
|
char wd[2048];
|
|
String *mbpath;
|
|
int natural;
|
|
int doflush;
|
|
|
|
int interrupted;
|
|
|
|
struct Message {
|
|
Message *next;
|
|
Message *prev;
|
|
Message *cmd;
|
|
Message *child;
|
|
Message *parent;
|
|
String *path;
|
|
int id;
|
|
int len;
|
|
int fileno; // number of directory
|
|
String *info;
|
|
char *from;
|
|
char *to;
|
|
char *cc;
|
|
char *replyto;
|
|
char *date;
|
|
char *subject;
|
|
char *type;
|
|
char *disposition;
|
|
char *filename;
|
|
char deleted;
|
|
char stored;
|
|
};
|
|
|
|
Message top;
|
|
|
|
struct Ctype {
|
|
char *type;
|
|
char *ext;
|
|
int display;
|
|
char *plumbdest;
|
|
Ctype *next;
|
|
};
|
|
|
|
Ctype ctype[] = {
|
|
{ "text/plain", "txt", 1, 0 },
|
|
{ "text/html", "htm", 1, 0 },
|
|
{ "text/html", "html", 1, 0 },
|
|
{ "text/tab-separated-values", "tsv", 1, 0 },
|
|
{ "text/richtext", "rtx", 1, 0 },
|
|
{ "text/rtf", "rtf", 1, 0 },
|
|
{ "text", "txt", 1, 0 },
|
|
{ "message/rfc822", "msg", 0, 0 },
|
|
{ "message/delivery-status", "txt", 1, 0 },
|
|
{ "image/bmp", "bmp", 0, "image" },
|
|
{ "image/jpeg", "jpg", 0, "image" },
|
|
{ "image/gif", "gif", 0, "image" },
|
|
{ "image/png", "png", 0, "image" },
|
|
{ "application/pdf", "pdf", 0, "postscript" },
|
|
{ "application/postscript", "ps", 0, "postscript" },
|
|
{ "application/", 0, 0, 0 },
|
|
{ "image/", 0, 0, 0 },
|
|
{ "multipart/", "mul", 0, 0 },
|
|
|
|
};
|
|
|
|
Message* acmd(Cmd*, Message*);
|
|
Message* bcmd(Cmd*, Message*);
|
|
Message* dcmd(Cmd*, Message*);
|
|
Message* eqcmd(Cmd*, Message*);
|
|
Message* hcmd(Cmd*, Message*);
|
|
Message* Hcmd(Cmd*, Message*);
|
|
Message* helpcmd(Cmd*, Message*);
|
|
Message* icmd(Cmd*, Message*);
|
|
Message* pcmd(Cmd*, Message*);
|
|
Message* qcmd(Cmd*, Message*);
|
|
Message* rcmd(Cmd*, Message*);
|
|
Message* scmd(Cmd*, Message*);
|
|
Message* ucmd(Cmd*, Message*);
|
|
Message* wcmd(Cmd*, Message*);
|
|
Message* xcmd(Cmd*, Message*);
|
|
Message* ycmd(Cmd*, Message*);
|
|
Message* pipecmd(Cmd*, Message*);
|
|
Message* rpipecmd(Cmd*, Message*);
|
|
Message* bangcmd(Cmd*, Message*);
|
|
Message* Pcmd(Cmd*, Message*);
|
|
Message* mcmd(Cmd*, Message*);
|
|
Message* fcmd(Cmd*, Message*);
|
|
Message* quotecmd(Cmd*, Message*);
|
|
|
|
struct {
|
|
char *cmd;
|
|
int args;
|
|
Message* (*f)(Cmd*, Message*);
|
|
char *help;
|
|
} cmdtab[] = {
|
|
{ "a", 1, acmd, "a reply to sender and recipients" },
|
|
{ "A", 1, acmd, "A reply to sender and recipients with copy" },
|
|
{ "b", 0, bcmd, "b print the next 10 headers" },
|
|
{ "d", 0, dcmd, "d mark for deletion" },
|
|
{ "f", 0, fcmd, "f file message by from address" },
|
|
{ "h", 0, hcmd, "h print elided message summary (,h for all)" },
|
|
{ "help", 0, helpcmd, "help print this info" },
|
|
{ "H", 0, Hcmd, "H print message's MIME structure " },
|
|
{ "i", 0, icmd, "i incorporate new mail" },
|
|
{ "m", 1, mcmd, "m addr forward mail" },
|
|
{ "M", 1, mcmd, "M addr forward mail with message" },
|
|
{ "p", 0, pcmd, "p print the processed message" },
|
|
{ "P", 0, Pcmd, "P print the raw message" },
|
|
{ "\"", 0, quotecmd, "\" print a quoted version of msg" },
|
|
{ "q", 0, qcmd, "q exit and remove all deleted mail" },
|
|
{ "r", 1, rcmd, "r [addr] reply to sender plus any addrs specified" },
|
|
{ "rf", 1, rcmd, "rf [addr]file message and reply" },
|
|
{ "R", 1, rcmd, "R [addr] reply including copy of message" },
|
|
{ "Rf", 1, rcmd, "Rf [addr]file message and reply with copy" },
|
|
{ "s", 1, scmd, "s file append raw message to file" },
|
|
{ "u", 0, ucmd, "u remove deletion mark" },
|
|
{ "w", 1, wcmd, "w file store message contents as file" },
|
|
{ "x", 0, xcmd, "x exit without flushing deleted messages" },
|
|
{ "y", 0, ycmd, "y synchronize with mail box" },
|
|
{ "=", 1, eqcmd, "= print current message number" },
|
|
{ "|", 1, pipecmd, "|cmd pipe message body to a command" },
|
|
{ "||", 1, rpipecmd, "||cmd pipe raw message to a command" },
|
|
{ "!", 1, bangcmd, "!cmd run a command" },
|
|
{ nil, 0, nil, nil },
|
|
};
|
|
|
|
enum
|
|
{
|
|
NARG= 32,
|
|
};
|
|
|
|
struct Cmd {
|
|
Message *msgs;
|
|
Message *(*f)(Cmd*, Message*);
|
|
int an;
|
|
char *av[NARG];
|
|
int delete;
|
|
};
|
|
|
|
Biobuf out;
|
|
int startedfs;
|
|
int reverse;
|
|
int longestfrom = 12;
|
|
|
|
String* file2string(String*, char*);
|
|
int dir2message(Message*, int);
|
|
int filelen(String*, char*);
|
|
String* extendpath(String*, char*);
|
|
void snprintheader(char*, int, Message*);
|
|
void cracktime(char*, char*, int);
|
|
int cistrncmp(char*, char*, int);
|
|
int cistrcmp(char*, char*);
|
|
Reprog* parsesearch(char**);
|
|
char* parseaddr(char**, Message*, Message*, Message*, Message**);
|
|
char* parsecmd(char*, Cmd*, Message*, Message*);
|
|
char* readline(char*, char*, int);
|
|
void messagecount(Message*);
|
|
void system(char*, char**, int);
|
|
void mkid(String*, Message*);
|
|
int switchmb(char*, char*);
|
|
void closemb(void);
|
|
int lineize(char*, char**, int);
|
|
int rawsearch(Message*, Reprog*);
|
|
Message* dosingleton(Message*, char*);
|
|
String* rooted(String*);
|
|
int plumb(Message*, Ctype*);
|
|
String* addrecolon(char*);
|
|
void exitfs(char*);
|
|
Message* flushdeleted(Message*);
|
|
|
|
CFsys *mailfs;
|
|
|
|
void
|
|
usage(void)
|
|
{
|
|
fprint(2, "usage: %s [-nr] [-f mailfile] [-s mailfile]\n", argv0);
|
|
fprint(2, " %s -c dir\n", argv0);
|
|
threadexitsall("usage");
|
|
}
|
|
|
|
void
|
|
catchnote(void *x, char *note)
|
|
{
|
|
USED(x);
|
|
|
|
if(strstr(note, "interrupt") != nil){
|
|
interrupted = 1;
|
|
noted(NCONT);
|
|
}
|
|
noted(NDFLT);
|
|
}
|
|
|
|
char *
|
|
plural(int n)
|
|
{
|
|
if (n == 1)
|
|
return "";
|
|
|
|
return "s";
|
|
}
|
|
|
|
void
|
|
threadmain(int argc, char **argv)
|
|
{
|
|
Message *cur, *m, *x;
|
|
char cmdline[4*1024];
|
|
Cmd cmd;
|
|
Ctype *cp;
|
|
char *err;
|
|
int n, cflag;
|
|
String *prompt;
|
|
char *file, *singleton;
|
|
|
|
Binit(&out, 1, OWRITE);
|
|
|
|
file = nil;
|
|
singleton = nil;
|
|
reverse = 1;
|
|
cflag = 0;
|
|
ARGBEGIN {
|
|
case 'c':
|
|
cflag = 1;
|
|
break;
|
|
case 'f':
|
|
sysfatal("-f not available");
|
|
file = EARGF(usage());
|
|
break;
|
|
case 's':
|
|
singleton = EARGF(usage());
|
|
break;
|
|
case 'r':
|
|
reverse = 0;
|
|
break;
|
|
case 'n':
|
|
natural = 1;
|
|
reverse = 0;
|
|
break;
|
|
default:
|
|
usage();
|
|
break;
|
|
} ARGEND;
|
|
|
|
user = getlog();
|
|
if(user == nil || *user == 0)
|
|
sysfatal("can't read user name");
|
|
|
|
if(cflag){
|
|
if(argc > 0)
|
|
creatembox(user, argv[0]);
|
|
else
|
|
creatembox(user, nil);
|
|
threadexitsall(0);
|
|
}
|
|
|
|
if(argc)
|
|
usage();
|
|
if((mailfs = nsmount("mail", nil)) == nil)
|
|
sysfatal("cannot mount mail: %r");
|
|
|
|
switchmb(file, singleton);
|
|
|
|
top.path = s_copy(root);
|
|
|
|
for(cp = ctype; cp < ctype+nelem(ctype)-1; cp++)
|
|
cp->next = cp+1;
|
|
|
|
if(singleton != nil){
|
|
cur = dosingleton(&top, singleton);
|
|
if(cur == nil){
|
|
Bprint(&out, "no message\n");
|
|
exitfs(0);
|
|
}
|
|
pcmd(nil, cur);
|
|
} else {
|
|
cur = ⊤
|
|
n = dir2message(&top, reverse);
|
|
if(n < 0)
|
|
sysfatal("can't read %s", s_to_c(top.path));
|
|
Bprint(&out, "%d message%s\n", n, plural(n));
|
|
}
|
|
|
|
|
|
notify(catchnote);
|
|
prompt = s_new();
|
|
for(;;){
|
|
s_reset(prompt);
|
|
if(cur == &top)
|
|
s_append(prompt, ": ");
|
|
else {
|
|
mkid(prompt, cur);
|
|
s_append(prompt, ": ");
|
|
}
|
|
|
|
// leave space at the end of cmd line in case parsecmd needs to
|
|
// add a space after a '|' or '!'
|
|
if(readline(s_to_c(prompt), cmdline, sizeof(cmdline)-1) == nil)
|
|
break;
|
|
err = parsecmd(cmdline, &cmd, top.child, cur);
|
|
if(err != nil){
|
|
Bprint(&out, "!%s\n", err);
|
|
continue;
|
|
}
|
|
if(singleton != nil && cmd.f == icmd){
|
|
Bprint(&out, "!illegal command\n");
|
|
continue;
|
|
}
|
|
interrupted = 0;
|
|
if(cmd.msgs == nil || cmd.msgs == &top){
|
|
x = (*cmd.f)(&cmd, &top);
|
|
if(x != nil)
|
|
cur = x;
|
|
} else for(m = cmd.msgs; m != nil; m = m->cmd){
|
|
x = m;
|
|
if(cmd.delete){
|
|
dcmd(&cmd, x);
|
|
|
|
// dp acts differently than all other commands
|
|
// since its an old lesk idiom that people love.
|
|
// it deletes the current message, moves the current
|
|
// pointer ahead one and prints.
|
|
if(cmd.f == pcmd){
|
|
if(x->next == nil){
|
|
Bprint(&out, "!address\n");
|
|
cur = x;
|
|
break;
|
|
} else
|
|
x = x->next;
|
|
}
|
|
}
|
|
x = (*cmd.f)(&cmd, x);
|
|
if(x != nil)
|
|
cur = x;
|
|
if(interrupted)
|
|
break;
|
|
if(singleton != nil && (cmd.delete || cmd.f == dcmd))
|
|
qcmd(nil, nil);
|
|
}
|
|
if(doflush)
|
|
cur = flushdeleted(cur);
|
|
}
|
|
qcmd(nil, nil);
|
|
}
|
|
|
|
//
|
|
// read the message info
|
|
//
|
|
Message*
|
|
file2message(Message *parent, char *name)
|
|
{
|
|
Message *m;
|
|
String *path;
|
|
char *f[30], *s, *t;
|
|
int i, nf;
|
|
|
|
m = mallocz(sizeof(Message), 1);
|
|
if(m == nil)
|
|
return nil;
|
|
m->path = path = extendpath(parent->path, name);
|
|
m->fileno = atoi(name);
|
|
m->info = file2string(path, "info");
|
|
m->from = "";
|
|
m->to = "";
|
|
m->cc = "";
|
|
m->replyto = "";
|
|
m->date = "";
|
|
m->subject = "";
|
|
m->type = "";
|
|
m->disposition = "";
|
|
m->filename = "";
|
|
nf = lineize(s_to_c(m->info), f, nelem(f));
|
|
for(i=0; i<nf; i++){
|
|
s = f[i];
|
|
t = strchr(f[i], ' ');
|
|
if(t == nil)
|
|
continue;
|
|
*t++ = 0;
|
|
|
|
if(strcmp(s, "from") == 0)
|
|
m->from = t;
|
|
else if(strcmp(s, "to") == 0)
|
|
m->to = t;
|
|
else if(strcmp(s, "cc") == 0)
|
|
m->cc = t;
|
|
else if(strcmp(s, "replyto") == 0)
|
|
m->replyto = t;
|
|
else if(strcmp(s, "unixdate") == 0 && (t=strchr(t, ' ')) != nil)
|
|
m->date = t;
|
|
else if(strcmp(s, "subject") == 0)
|
|
m->subject = t;
|
|
else if(strcmp(s, "type") == 0)
|
|
m->type = t;
|
|
else if(strcmp(s, "disposition") == 0)
|
|
m->disposition = t;
|
|
else if(strcmp(s, "filename") == 0)
|
|
m->filename = t;
|
|
}
|
|
m->len = filelen(path, "raw");
|
|
if(strstr(m->type, "multipart") != nil || strcmp(m->type, "message/rfc822") == 0)
|
|
dir2message(m, 0);
|
|
m->parent = parent;
|
|
|
|
return m;
|
|
}
|
|
|
|
void
|
|
freemessage(Message *m)
|
|
{
|
|
Message *nm, *next;
|
|
|
|
for(nm = m->child; nm != nil; nm = next){
|
|
next = nm->next;
|
|
freemessage(nm);
|
|
}
|
|
s_free(m->path);
|
|
s_free(m->info);
|
|
free(m);
|
|
}
|
|
|
|
//
|
|
// read a directory into a list of messages
|
|
//
|
|
int
|
|
dir2message(Message *parent, int reverse)
|
|
{
|
|
int i, n, highest, newmsgs;
|
|
CFid *fd;
|
|
|
|
Dir *d;
|
|
Message *first, *last, *m;
|
|
|
|
fd = fsopen(mailfs, s_to_c(parent->path), OREAD);
|
|
if(fd == nil)
|
|
return -1;
|
|
|
|
// count current entries
|
|
first = parent->child;
|
|
highest = newmsgs = 0;
|
|
for(last = parent->child; last != nil && last->next != nil; last = last->next)
|
|
if(last->fileno > highest)
|
|
highest = last->fileno;
|
|
if(last != nil)
|
|
if(last->fileno > highest)
|
|
highest = last->fileno;
|
|
|
|
n = fsdirreadall(fd, &d);
|
|
fprint(2,"read %d messages\n", n);
|
|
for(i = 0; i < n; i++){
|
|
if((d[i].qid.type & QTDIR) == 0)
|
|
continue;
|
|
if(atoi(d[i].name) <= highest)
|
|
continue;
|
|
fprint(2,"calling file2message %d\n", i);
|
|
m = file2message(parent, d[i].name);
|
|
// fprint(2,"returned from file2message\n");
|
|
if(m == nil)
|
|
break;
|
|
newmsgs++;
|
|
if(reverse){
|
|
m->next = first;
|
|
if(first != nil)
|
|
first->prev = m;
|
|
first = m;
|
|
} else {
|
|
if(first == nil)
|
|
first = m;
|
|
else
|
|
last->next = m;
|
|
m->prev = last;
|
|
last = m;
|
|
}
|
|
}
|
|
free(d);
|
|
fsclose(fd);
|
|
parent->child = first;
|
|
|
|
// renumber and file longest from
|
|
i = 1;
|
|
longestfrom = 12;
|
|
for(m = first; m != nil; m = m->next){
|
|
m->id = natural ? m->fileno : i++;
|
|
n = strlen(m->from);
|
|
if(n > longestfrom)
|
|
longestfrom = n;
|
|
}
|
|
|
|
return newmsgs;
|
|
}
|
|
|
|
//
|
|
// point directly to a message
|
|
//
|
|
Message*
|
|
dosingleton(Message *parent, char *path)
|
|
{
|
|
char *p, *np;
|
|
Message *m;
|
|
|
|
// walk down to message and read it
|
|
if(strlen(path) < rootlen)
|
|
return nil;
|
|
if(path[rootlen] != '/')
|
|
return nil;
|
|
p = path+rootlen+1;
|
|
np = strchr(p, '/');
|
|
if(np != nil)
|
|
*np = 0;
|
|
m = file2message(parent, p);
|
|
if(m == nil)
|
|
return nil;
|
|
parent->child = m;
|
|
m->id = 1;
|
|
|
|
// walk down to requested component
|
|
while(np != nil){
|
|
*np = '/';
|
|
np = strchr(np+1, '/');
|
|
if(np != nil)
|
|
*np = 0;
|
|
for(m = m->child; m != nil; m = m->next)
|
|
if(strcmp(path, s_to_c(m->path)) == 0)
|
|
return m;
|
|
if(m == nil)
|
|
return nil;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
//
|
|
// read a file into a string
|
|
//
|
|
String*
|
|
file2string(String *dir, char *file)
|
|
{
|
|
String *s;
|
|
int n, m;
|
|
CFid *fd;
|
|
|
|
s = extendpath(dir, file);
|
|
fd = fsopen(mailfs, s_to_c(s), OREAD);
|
|
s_grow(s, 512); /* avoid multiple reads on info files */
|
|
s_reset(s);
|
|
if(fd == nil)
|
|
return s;
|
|
|
|
for(;;){
|
|
n = s->end - s->ptr;
|
|
if(n == 0){
|
|
s_grow(s, 128);
|
|
continue;
|
|
}
|
|
m = fsread(fd, s->ptr, n);
|
|
if(m <= 0)
|
|
break;
|
|
s->ptr += m;
|
|
if(m < n)
|
|
break;
|
|
}
|
|
s_terminate(s);
|
|
fsclose(fd);
|
|
|
|
return s;
|
|
}
|
|
|
|
//
|
|
// get the length of a file
|
|
//
|
|
int
|
|
filelen(String *dir, char *file)
|
|
{
|
|
String *path;
|
|
Dir *d;
|
|
int rv;
|
|
|
|
path = extendpath(dir, file);
|
|
d = fsdirstat(mailfs, s_to_c(path));
|
|
if(d == nil){
|
|
s_free(path);
|
|
return -1;
|
|
}
|
|
s_free(path);
|
|
rv = d->length;
|
|
free(d);
|
|
return rv;
|
|
}
|
|
|
|
//
|
|
// walk the path name an element
|
|
//
|
|
String*
|
|
extendpath(String *dir, char *name)
|
|
{
|
|
String *path;
|
|
|
|
if(strcmp(s_to_c(dir), ".") == 0)
|
|
path = s_new();
|
|
else {
|
|
path = s_copy(s_to_c(dir));
|
|
s_append(path, "/");
|
|
}
|
|
s_append(path, name);
|
|
return path;
|
|
}
|
|
|
|
int
|
|
cistrncmp(char *a, char *b, int n)
|
|
{
|
|
while(n-- > 0){
|
|
if(tolower(*a++) != tolower(*b++))
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
cistrcmp(char *a, char *b)
|
|
{
|
|
for(;;){
|
|
if(tolower(*a) != tolower(*b++))
|
|
return -1;
|
|
if(*a++ == 0)
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
char*
|
|
nosecs(char *t)
|
|
{
|
|
char *p;
|
|
|
|
p = strchr(t, ':');
|
|
if(p == nil)
|
|
return t;
|
|
p = strchr(p+1, ':');
|
|
if(p != nil)
|
|
*p = 0;
|
|
return t;
|
|
}
|
|
|
|
char *months[12] =
|
|
{
|
|
"jan", "feb", "mar", "apr", "may", "jun",
|
|
"jul", "aug", "sep", "oct", "nov", "dec"
|
|
};
|
|
|
|
int
|
|
month(char *m)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 12; i++)
|
|
if(cistrcmp(m, months[i]) == 0)
|
|
return i+1;
|
|
return 1;
|
|
}
|
|
|
|
enum
|
|
{
|
|
Yearsecs= 365*24*60*60
|
|
};
|
|
|
|
void
|
|
cracktime(char *d, char *out, int len)
|
|
{
|
|
char in[64];
|
|
char *f[6];
|
|
int n;
|
|
Tm tm;
|
|
long now, then;
|
|
char *dtime;
|
|
|
|
*out = 0;
|
|
if(d == nil)
|
|
return;
|
|
strncpy(in, d, sizeof(in));
|
|
in[sizeof(in)-1] = 0;
|
|
n = getfields(in, f, 6, 1, " \t\r\n");
|
|
if(n != 6){
|
|
// unknown style
|
|
snprint(out, 16, "%10.10s", d);
|
|
return;
|
|
}
|
|
now = time(0);
|
|
memset(&tm, 0, sizeof tm);
|
|
if(strchr(f[0], ',') != nil && strchr(f[4], ':') != nil){
|
|
// 822 style
|
|
tm.year = atoi(f[3])-1900;
|
|
tm.mon = month(f[2]);
|
|
tm.mday = atoi(f[1]);
|
|
dtime = nosecs(f[4]);
|
|
then = tm2sec(&tm);
|
|
} else if(strchr(f[3], ':') != nil){
|
|
// unix style
|
|
tm.year = atoi(f[5])-1900;
|
|
tm.mon = month(f[1]);
|
|
tm.mday = atoi(f[2]);
|
|
dtime = nosecs(f[3]);
|
|
then = tm2sec(&tm);
|
|
} else {
|
|
then = now;
|
|
tm = *localtime(now);
|
|
dtime = "";
|
|
}
|
|
|
|
if(now - then < Yearsecs/2)
|
|
snprint(out, len, "%2d/%2.2d %s", tm.mon, tm.mday, dtime);
|
|
else
|
|
snprint(out, len, "%2d/%2.2d %4d", tm.mon, tm.mday, tm.year+1900);
|
|
}
|
|
|
|
Ctype*
|
|
findctype(Message *m)
|
|
{
|
|
char *p;
|
|
char ftype[128];
|
|
int n, pfd[2];
|
|
Ctype *a, *cp;
|
|
static Ctype bintype = { "application/octet-stream", "bin", 0, 0 };
|
|
|
|
for(cp = ctype; cp; cp = cp->next)
|
|
if(strncmp(cp->type, m->type, strlen(cp->type)) == 0)
|
|
return cp;
|
|
|
|
if(pipe(pfd) < 0)
|
|
return &bintype;
|
|
|
|
*ftype = 0;
|
|
switch(fork()){
|
|
case -1:
|
|
break;
|
|
case 0:
|
|
close(pfd[1]);
|
|
close(0);
|
|
dup(pfd[0], 0);
|
|
close(1);
|
|
dup(pfd[0], 1);
|
|
execl(unsharp("#9/bin/file"), "file", "-m", s_to_c(extendpath(m->path, "body")), nil);
|
|
threadexits(0);
|
|
default:
|
|
close(pfd[0]);
|
|
n = read(pfd[1], ftype, sizeof(ftype));
|
|
if(n > 0)
|
|
ftype[n] = 0;
|
|
close(pfd[1]);
|
|
waitpid();
|
|
break;
|
|
}
|
|
|
|
if (*ftype=='\0' || (p = strchr(ftype, '/')) == nil)
|
|
return &bintype;
|
|
*p++ = 0;
|
|
|
|
a = mallocz(sizeof(Ctype), 1);
|
|
a->type = strdup(ftype);
|
|
a->ext = strdup(p);
|
|
a->display = 0;
|
|
a->plumbdest = strdup(ftype);
|
|
for(cp = ctype; cp->next; cp = cp->next)
|
|
continue;
|
|
cp->next = a;
|
|
a->next = nil;
|
|
return a;
|
|
}
|
|
|
|
void
|
|
mkid(String *s, Message *m)
|
|
{
|
|
char buf[32];
|
|
|
|
if(m->parent != &top){
|
|
mkid(s, m->parent);
|
|
s_append(s, ".");
|
|
}
|
|
sprint(buf, "%d", m->id);
|
|
s_append(s, buf);
|
|
}
|
|
|
|
void
|
|
snprintheader(char *buf, int len, Message *m)
|
|
{
|
|
char timebuf[32];
|
|
String *id;
|
|
char *p, *q;;
|
|
|
|
// create id
|
|
id = s_new();
|
|
mkid(id, m);
|
|
|
|
if(*m->from == 0){
|
|
// no from
|
|
snprint(buf, len, "%-3s %s %6d %s",
|
|
s_to_c(id),
|
|
m->type,
|
|
m->len,
|
|
m->filename);
|
|
} else if(*m->subject){
|
|
q = p = strdup(m->subject);
|
|
while(*p == ' ')
|
|
p++;
|
|
if(strlen(p) > 50)
|
|
p[50] = 0;
|
|
cracktime(m->date, timebuf, sizeof(timebuf));
|
|
snprint(buf, len, "%-3s %c%c%c %6d %11.11s %-*.*s %s",
|
|
s_to_c(id),
|
|
m->child ? 'H' : ' ',
|
|
m->deleted ? 'd' : ' ',
|
|
m->stored ? 's' : ' ',
|
|
m->len,
|
|
timebuf,
|
|
longestfrom, longestfrom, m->from,
|
|
p);
|
|
free(q);
|
|
} else {
|
|
cracktime(m->date, timebuf, sizeof(timebuf));
|
|
snprint(buf, len, "%-3s %c%c%c %6d %11.11s %s",
|
|
s_to_c(id),
|
|
m->child ? 'H' : ' ',
|
|
m->deleted ? 'd' : ' ',
|
|
m->stored ? 's' : ' ',
|
|
m->len,
|
|
timebuf,
|
|
m->from);
|
|
}
|
|
s_free(id);
|
|
}
|
|
|
|
char *spaces = " ";
|
|
|
|
void
|
|
snprintHeader(char *buf, int len, int indent, Message *m)
|
|
{
|
|
String *id;
|
|
char typeid[64];
|
|
char *p, *e;
|
|
|
|
// create id
|
|
id = s_new();
|
|
mkid(id, m);
|
|
|
|
e = buf + len;
|
|
|
|
snprint(typeid, sizeof typeid, "%s %s", s_to_c(id), m->type);
|
|
if(indent < 6)
|
|
p = seprint(buf, e, "%-32s %-6d ", typeid, m->len);
|
|
else
|
|
p = seprint(buf, e, "%-64s %-6d ", typeid, m->len);
|
|
if(m->filename && *m->filename)
|
|
p = seprint(p, e, "(file,%s)", m->filename);
|
|
if(m->from && *m->from)
|
|
p = seprint(p, e, "(from,%s)", m->from);
|
|
if(m->subject && *m->subject)
|
|
seprint(p, e, "(subj,%s)", m->subject);
|
|
|
|
s_free(id);
|
|
}
|
|
|
|
char sstring[256];
|
|
|
|
// cmd := range cmd ' ' arg-list ;
|
|
// range := address
|
|
// | address ',' address
|
|
// | 'g' search ;
|
|
// address := msgno
|
|
// | search ;
|
|
// msgno := number
|
|
// | number '/' msgno ;
|
|
// search := '/' string '/'
|
|
// | '%' string '%' ;
|
|
//
|
|
Reprog*
|
|
parsesearch(char **pp)
|
|
{
|
|
char *p, *np;
|
|
int c, n;
|
|
|
|
p = *pp;
|
|
c = *p++;
|
|
np = strchr(p, c);
|
|
if(np != nil){
|
|
*np++ = 0;
|
|
*pp = np;
|
|
} else {
|
|
n = strlen(p);
|
|
*pp = p + n;
|
|
}
|
|
if(*p == 0)
|
|
p = sstring;
|
|
else{
|
|
strncpy(sstring, p, sizeof(sstring));
|
|
sstring[sizeof(sstring)-1] = 0;
|
|
}
|
|
return regcomp(p);
|
|
}
|
|
|
|
char*
|
|
parseaddr(char **pp, Message *first, Message *cur, Message *unspec, Message **mp)
|
|
{
|
|
int n;
|
|
Message *m;
|
|
char *p;
|
|
Reprog *prog;
|
|
int c, sign;
|
|
char buf[256];
|
|
|
|
*mp = nil;
|
|
p = *pp;
|
|
|
|
if(*p == '+'){
|
|
sign = 1;
|
|
p++;
|
|
*pp = p;
|
|
} else if(*p == '-'){
|
|
sign = -1;
|
|
p++;
|
|
*pp = p;
|
|
} else
|
|
sign = 0;
|
|
|
|
switch(*p){
|
|
default:
|
|
if(sign){
|
|
n = 1;
|
|
goto number;
|
|
}
|
|
*mp = unspec;
|
|
break;
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
n = strtoul(p, pp, 10);
|
|
if(n == 0){
|
|
if(sign)
|
|
*mp = cur;
|
|
else
|
|
*mp = ⊤
|
|
break;
|
|
}
|
|
number:
|
|
m = nil;
|
|
switch(sign){
|
|
case 0:
|
|
for(m = first; m != nil; m = m->next)
|
|
if(m->id == n)
|
|
break;
|
|
break;
|
|
case -1:
|
|
if(cur != &top)
|
|
for(m = cur; m != nil && n > 0; n--)
|
|
m = m->prev;
|
|
break;
|
|
case 1:
|
|
if(cur == &top){
|
|
n--;
|
|
cur = first;
|
|
}
|
|
for(m = cur; m != nil && n > 0; n--)
|
|
m = m->next;
|
|
break;
|
|
}
|
|
if(m == nil)
|
|
return "address";
|
|
*mp = m;
|
|
break;
|
|
case '%':
|
|
case '/':
|
|
case '?':
|
|
c = *p;
|
|
prog = parsesearch(pp);
|
|
if(prog == nil)
|
|
return "badly formed regular expression";
|
|
m = nil;
|
|
switch(c){
|
|
case '%':
|
|
for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
|
|
if(rawsearch(m, prog))
|
|
break;
|
|
}
|
|
break;
|
|
case '/':
|
|
for(m = cur == &top ? first : cur->next; m != nil; m = m->next){
|
|
snprintheader(buf, sizeof(buf), m);
|
|
if(regexec(prog, buf, nil, 0))
|
|
break;
|
|
}
|
|
break;
|
|
case '?':
|
|
for(m = cur == &top ? nil : cur->prev; m != nil; m = m->prev){
|
|
snprintheader(buf, sizeof(buf), m);
|
|
if(regexec(prog, buf, nil, 0))
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
if(m == nil)
|
|
return "search";
|
|
*mp = m;
|
|
free(prog);
|
|
break;
|
|
case '$':
|
|
for(m = first; m != nil && m->next != nil; m = m->next)
|
|
;
|
|
*mp = m;
|
|
*pp = p+1;
|
|
break;
|
|
case '.':
|
|
*mp = cur;
|
|
*pp = p+1;
|
|
break;
|
|
case ',':
|
|
*mp = first;
|
|
*pp = p;
|
|
break;
|
|
}
|
|
|
|
if(*mp != nil && **pp == '.'){
|
|
(*pp)++;
|
|
if((*mp)->child == nil)
|
|
return "no sub parts";
|
|
return parseaddr(pp, (*mp)->child, (*mp)->child, (*mp)->child, mp);
|
|
}
|
|
if(**pp == '+' || **pp == '-' || **pp == '/' || **pp == '%')
|
|
return parseaddr(pp, first, *mp, *mp, mp);
|
|
|
|
return nil;
|
|
}
|
|
|
|
//
|
|
// search a message for a regular expression match
|
|
//
|
|
int
|
|
rawsearch(Message *m, Reprog *prog)
|
|
{
|
|
char buf[4096+1];
|
|
int i, rv;
|
|
CFid *fd;
|
|
String *path;
|
|
|
|
path = extendpath(m->path, "raw");
|
|
fd = fsopen(mailfs, s_to_c(path), OREAD);
|
|
if(fd == nil)
|
|
return 0;
|
|
|
|
// march through raw message 4096 bytes at a time
|
|
// with a 128 byte overlap to chain the re search.
|
|
rv = 0;
|
|
for(;;){
|
|
i = fsread(fd, buf, sizeof(buf)-1);
|
|
if(i <= 0)
|
|
break;
|
|
buf[i] = 0;
|
|
if(regexec(prog, buf, nil, 0)){
|
|
rv = 1;
|
|
break;
|
|
}
|
|
if(i < sizeof(buf)-1)
|
|
break;
|
|
if(fsseek(fd, -128LL, 1) < 0)
|
|
break;
|
|
}
|
|
|
|
fsclose(fd);
|
|
s_free(path);
|
|
return rv;
|
|
}
|
|
|
|
|
|
char*
|
|
parsecmd(char *p, Cmd *cmd, Message *first, Message *cur)
|
|
{
|
|
Reprog *prog;
|
|
Message *m, *s, *e, **l, *last;
|
|
char buf[256];
|
|
char *err;
|
|
int i, c;
|
|
char *q;
|
|
static char errbuf[Errlen];
|
|
|
|
cmd->delete = 0;
|
|
l = &cmd->msgs;
|
|
*l = nil;
|
|
|
|
// eat white space
|
|
while(*p == ' ')
|
|
p++;
|
|
|
|
// null command is a special case (advance and print)
|
|
if(*p == 0){
|
|
if(cur == &top){
|
|
// special case
|
|
m = first;
|
|
} else {
|
|
// walk to the next message even if we have to go up
|
|
m = cur->next;
|
|
while(m == nil && cur->parent != nil){
|
|
cur = cur->parent;
|
|
m = cur->next;
|
|
}
|
|
}
|
|
if(m == nil)
|
|
return "address";
|
|
*l = m;
|
|
m->cmd = nil;
|
|
cmd->an = 0;
|
|
cmd->f = pcmd;
|
|
return nil;
|
|
}
|
|
|
|
// global search ?
|
|
if(*p == 'g'){
|
|
p++;
|
|
|
|
// no search string means all messages
|
|
if(*p != '/' && *p != '%'){
|
|
for(m = first; m != nil; m = m->next){
|
|
*l = m;
|
|
l = &m->cmd;
|
|
*l = nil;
|
|
}
|
|
} else {
|
|
// mark all messages matching this search string
|
|
c = *p;
|
|
prog = parsesearch(&p);
|
|
if(prog == nil)
|
|
return "badly formed regular expression";
|
|
if(c == '%'){
|
|
for(m = first; m != nil; m = m->next){
|
|
if(rawsearch(m, prog)){
|
|
*l = m;
|
|
l = &m->cmd;
|
|
*l = nil;
|
|
}
|
|
}
|
|
} else {
|
|
for(m = first; m != nil; m = m->next){
|
|
snprintheader(buf, sizeof(buf), m);
|
|
if(regexec(prog, buf, nil, 0)){
|
|
*l = m;
|
|
l = &m->cmd;
|
|
*l = nil;
|
|
}
|
|
}
|
|
}
|
|
free(prog);
|
|
}
|
|
} else {
|
|
|
|
// parse an address
|
|
s = e = nil;
|
|
err = parseaddr(&p, first, cur, cur, &s);
|
|
if(err != nil)
|
|
return err;
|
|
if(*p == ','){
|
|
// this is an address range
|
|
if(s == &top)
|
|
s = first;
|
|
p++;
|
|
for(last = s; last != nil && last->next != nil; last = last->next)
|
|
;
|
|
err = parseaddr(&p, first, cur, last, &e);
|
|
if(err != nil)
|
|
return err;
|
|
|
|
// select all messages in the range
|
|
for(; s != nil; s = s->next){
|
|
*l = s;
|
|
l = &s->cmd;
|
|
*l = nil;
|
|
if(s == e)
|
|
break;
|
|
}
|
|
if(s == nil)
|
|
return "null address range";
|
|
} else {
|
|
// single address
|
|
if(s != &top){
|
|
*l = s;
|
|
s->cmd = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
// insert a space after '!'s and '|'s
|
|
for(q = p; *q; q++)
|
|
if(*q != '!' && *q != '|')
|
|
break;
|
|
if(q != p && *q != ' '){
|
|
memmove(q+1, q, strlen(q)+1);
|
|
*q = ' ';
|
|
}
|
|
|
|
cmd->an = getfields(p, cmd->av, nelem(cmd->av) - 1, 1, " \t\r\n");
|
|
if(cmd->an == 0 || *cmd->av[0] == 0)
|
|
cmd->f = pcmd;
|
|
else {
|
|
// hack to allow all messages to start with 'd'
|
|
if(*(cmd->av[0]) == 'd' && *(cmd->av[0]+1) != 0){
|
|
cmd->delete = 1;
|
|
cmd->av[0]++;
|
|
}
|
|
|
|
// search command table
|
|
for(i = 0; cmdtab[i].cmd != nil; i++)
|
|
if(strcmp(cmd->av[0], cmdtab[i].cmd) == 0)
|
|
break;
|
|
if(cmdtab[i].cmd == nil)
|
|
return "illegal command";
|
|
if(cmdtab[i].args == 0 && cmd->an > 1){
|
|
snprint(errbuf, sizeof(errbuf), "%s doesn't take an argument", cmdtab[i].cmd);
|
|
return errbuf;
|
|
}
|
|
cmd->f = cmdtab[i].f;
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
// inefficient read from standard input
|
|
char*
|
|
readline(char *prompt, char *line, int len)
|
|
{
|
|
char *p, *e;
|
|
int n;
|
|
|
|
retry:
|
|
interrupted = 0;
|
|
Bprint(&out, "%s", prompt);
|
|
Bflush(&out);
|
|
e = line + len;
|
|
for(p = line; p < e; p++){
|
|
n = read(0, p, 1);
|
|
if(n < 0){
|
|
if(interrupted)
|
|
goto retry;
|
|
return nil;
|
|
}
|
|
if(n == 0)
|
|
return nil;
|
|
if(*p == '\n')
|
|
break;
|
|
}
|
|
*p = 0;
|
|
return line;
|
|
}
|
|
|
|
void
|
|
messagecount(Message *m)
|
|
{
|
|
int i;
|
|
|
|
i = 0;
|
|
for(; m != nil; m = m->next)
|
|
i++;
|
|
Bprint(&out, "%d message%s\n", i, plural(i));
|
|
}
|
|
|
|
Message*
|
|
aichcmd(Message *m, int indent)
|
|
{
|
|
char hdr[256];
|
|
|
|
if(m == &top)
|
|
return nil;
|
|
|
|
snprintHeader(hdr, sizeof(hdr), indent, m);
|
|
Bprint(&out, "%s\n", hdr);
|
|
for(m = m->child; m != nil; m = m->next)
|
|
aichcmd(m, indent+1);
|
|
return nil;
|
|
}
|
|
|
|
Message*
|
|
Hcmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
|
|
if(m == &top)
|
|
return nil;
|
|
aichcmd(m, 0);
|
|
return nil;
|
|
}
|
|
|
|
Message*
|
|
hcmd(Cmd *x, Message *m)
|
|
{
|
|
char hdr[256];
|
|
|
|
USED(x);
|
|
if(m == &top)
|
|
return nil;
|
|
|
|
snprintheader(hdr, sizeof(hdr), m);
|
|
Bprint(&out, "%s\n", hdr);
|
|
return nil;
|
|
}
|
|
|
|
Message*
|
|
bcmd(Cmd *x, Message *m)
|
|
{
|
|
int i;
|
|
Message *om = m;
|
|
|
|
USED(x);
|
|
if(m == &top)
|
|
m = top.child;
|
|
for(i = 0; i < 10 && m != nil; i++){
|
|
hcmd(nil, m);
|
|
om = m;
|
|
m = m->next;
|
|
}
|
|
|
|
return om;
|
|
}
|
|
|
|
Message*
|
|
ncmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
if(m == &top)
|
|
return m->child;
|
|
return m->next;
|
|
}
|
|
|
|
int
|
|
printpart(String *s, char *part)
|
|
{
|
|
char buf[4096];
|
|
int n, tot;
|
|
CFid *fd;
|
|
String *path;
|
|
|
|
path = extendpath(s, part);
|
|
fd = fsopen(mailfs, s_to_c(path), OREAD);
|
|
s_free(path);
|
|
if(fd == nil){
|
|
fprint(2, "!message dissappeared\n");
|
|
return 0;
|
|
}
|
|
tot = 0;
|
|
while((n = fsread(fd, buf, sizeof(buf))) > 0){
|
|
if(interrupted)
|
|
break;
|
|
if(Bwrite(&out, buf, n) <= 0)
|
|
break;
|
|
tot += n;
|
|
}
|
|
fsclose(fd);
|
|
return tot;
|
|
}
|
|
|
|
int
|
|
printhtml(Message *m)
|
|
{
|
|
Cmd c;
|
|
|
|
c.an = 3;
|
|
c.av[1] = "htmlfmt";
|
|
c.av[2] = "-l 40 -cutf-8";
|
|
Bprint(&out, "!%s\n", c.av[1]);
|
|
Bflush(&out);
|
|
pipecmd(&c, m);
|
|
return 0;
|
|
}
|
|
|
|
Message*
|
|
Pcmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
if(m == &top)
|
|
return ⊤
|
|
if(m->parent == &top)
|
|
printpart(m->path, "unixheader");
|
|
printpart(m->path, "raw");
|
|
return m;
|
|
}
|
|
|
|
void
|
|
compress(char *p)
|
|
{
|
|
char *np;
|
|
int last;
|
|
|
|
last = ' ';
|
|
for(np = p; *p; p++){
|
|
if(*p != ' ' || last != ' '){
|
|
last = *p;
|
|
*np++ = last;
|
|
}
|
|
}
|
|
*np = 0;
|
|
}
|
|
|
|
Message*
|
|
pcmd(Cmd *x, Message *m)
|
|
{
|
|
Message *nm;
|
|
Ctype *cp;
|
|
String *s;
|
|
char buf[128];
|
|
|
|
USED(x);
|
|
if(m == &top)
|
|
return ⊤
|
|
if(m->parent == &top)
|
|
printpart(m->path, "unixheader");
|
|
if(printpart(m->path, "header") > 0)
|
|
Bprint(&out, "\n");
|
|
cp = findctype(m);
|
|
if(cp->display){
|
|
if(strcmp(m->type, "text/html") == 0)
|
|
printhtml(m);
|
|
else
|
|
printpart(m->path, "body");
|
|
} else if(strcmp(m->type, "multipart/alternative") == 0){
|
|
for(nm = m->child; nm != nil; nm = nm->next){
|
|
cp = findctype(nm);
|
|
if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
|
|
break;
|
|
}
|
|
if(nm == nil)
|
|
for(nm = m->child; nm != nil; nm = nm->next){
|
|
cp = findctype(nm);
|
|
if(cp->display)
|
|
break;
|
|
}
|
|
if(nm != nil)
|
|
pcmd(nil, nm);
|
|
else
|
|
hcmd(nil, m);
|
|
} else if(strncmp(m->type, "multipart/", 10) == 0){
|
|
nm = m->child;
|
|
if(nm != nil){
|
|
// always print first part
|
|
pcmd(nil, nm);
|
|
|
|
for(nm = nm->next; nm != nil; nm = nm->next){
|
|
s = rooted(s_clone(nm->path));
|
|
cp = findctype(nm);
|
|
snprintHeader(buf, sizeof buf, -1, nm);
|
|
compress(buf);
|
|
if(strcmp(nm->disposition, "inline") == 0){
|
|
if(cp->ext != nil)
|
|
Bprint(&out, "\n--- %s %s/body.%s\n\n",
|
|
buf, s_to_c(s), cp->ext);
|
|
else
|
|
Bprint(&out, "\n--- %s %s/body\n\n",
|
|
buf, s_to_c(s));
|
|
pcmd(nil, nm);
|
|
} else {
|
|
if(cp->ext != nil)
|
|
Bprint(&out, "\n!--- %s %s/body.%s\n",
|
|
buf, s_to_c(s), cp->ext);
|
|
else
|
|
Bprint(&out, "\n!--- %s %s/body\n",
|
|
buf, s_to_c(s));
|
|
}
|
|
s_free(s);
|
|
}
|
|
} else {
|
|
hcmd(nil, m);
|
|
}
|
|
} else if(strcmp(m->type, "message/rfc822") == 0){
|
|
pcmd(nil, m->child);
|
|
} else if(plumb(m, cp) >= 0)
|
|
Bprint(&out, "\n!--- using plumber to display message of type %s\n", m->type);
|
|
else
|
|
Bprint(&out, "\n!--- cannot display messages of type %s\n", m->type);
|
|
|
|
return m;
|
|
}
|
|
|
|
void
|
|
printpartindented(String *s, char *part, char *indent)
|
|
{
|
|
char *p;
|
|
String *path;
|
|
Biobuf *b;
|
|
|
|
fprint(2,"printpartindented: fixme\n");
|
|
path = extendpath(s, part);
|
|
b = Bopen(s_to_c(path), OREAD);
|
|
s_free(path);
|
|
if(b == nil){
|
|
fprint(2, "!message dissappeared\n");
|
|
return;
|
|
}
|
|
while((p = Brdline(b, '\n')) != nil){
|
|
if(interrupted)
|
|
break;
|
|
p[Blinelen(b)-1] = 0;
|
|
if(Bprint(&out, "%s%s\n", indent, p) < 0)
|
|
break;
|
|
}
|
|
Bprint(&out, "\n");
|
|
Bterm(b);
|
|
}
|
|
|
|
Message*
|
|
quotecmd(Cmd *x, Message *m)
|
|
{
|
|
Message *nm;
|
|
Ctype *cp;
|
|
|
|
USED(x);
|
|
if(m == &top)
|
|
return ⊤
|
|
Bprint(&out, "\n");
|
|
if(m->from != nil && *m->from)
|
|
Bprint(&out, "On %s, %s wrote:\n", m->date, m->from);
|
|
cp = findctype(m);
|
|
if(cp->display){
|
|
printpartindented(m->path, "body", "> ");
|
|
} else if(strcmp(m->type, "multipart/alternative") == 0){
|
|
for(nm = m->child; nm != nil; nm = nm->next){
|
|
cp = findctype(nm);
|
|
if(cp->ext != nil && strncmp(cp->ext, "txt", 3) == 0)
|
|
break;
|
|
}
|
|
if(nm == nil)
|
|
for(nm = m->child; nm != nil; nm = nm->next){
|
|
cp = findctype(nm);
|
|
if(cp->display)
|
|
break;
|
|
}
|
|
if(nm != nil)
|
|
quotecmd(nil, nm);
|
|
} else if(strncmp(m->type, "multipart/", 10) == 0){
|
|
nm = m->child;
|
|
if(nm != nil){
|
|
cp = findctype(nm);
|
|
if(cp->display || strncmp(m->type, "multipart/", 10) == 0)
|
|
quotecmd(nil, nm);
|
|
}
|
|
}
|
|
return m;
|
|
}
|
|
|
|
// really delete messages
|
|
Message*
|
|
flushdeleted(Message *cur)
|
|
{
|
|
Message *m, **l;
|
|
char buf[1024], *p, *e, *msg;
|
|
int deld, n;
|
|
CFid *fd;
|
|
int i;
|
|
|
|
doflush = 0;
|
|
deld = 0;
|
|
|
|
fd = fsopen(mailfs, "ctl", OWRITE);
|
|
if(fd == nil){
|
|
fprint(2, "!can't delete mail, opening /mail/fs/ctl: %r\n");
|
|
exitfs(0);
|
|
}
|
|
e = &buf[sizeof(buf)];
|
|
p = seprint(buf, e, "delete %s", mbname);
|
|
n = 0;
|
|
for(l = &top.child; *l != nil;){
|
|
m = *l;
|
|
if(!m->deleted){
|
|
l = &(*l)->next;
|
|
continue;
|
|
}
|
|
|
|
// don't return a pointer to a deleted message
|
|
if(m == cur)
|
|
cur = m->next;
|
|
|
|
deld++;
|
|
msg = strrchr(s_to_c(m->path), '/');
|
|
if(msg == nil)
|
|
msg = s_to_c(m->path);
|
|
else
|
|
msg++;
|
|
if(e-p < 10){
|
|
fswrite(fd, buf, p-buf);
|
|
n = 0;
|
|
p = seprint(buf, e, "delete %s", mbname);
|
|
}
|
|
p = seprint(p, e, " %s", msg);
|
|
n++;
|
|
|
|
// unchain and free
|
|
*l = m->next;
|
|
if(m->next)
|
|
m->next->prev = m->prev;
|
|
freemessage(m);
|
|
}
|
|
if(n)
|
|
fswrite(fd, buf, p-buf);
|
|
|
|
fsclose(fd);
|
|
|
|
if(deld)
|
|
Bprint(&out, "!%d message%s deleted\n", deld, plural(deld));
|
|
|
|
// renumber
|
|
i = 1;
|
|
for(m = top.child; m != nil; m = m->next)
|
|
m->id = natural ? m->fileno : i++;
|
|
|
|
// if we're out of messages, go back to first
|
|
// if no first, return the fake first
|
|
if(cur == nil){
|
|
if(top.child)
|
|
return top.child;
|
|
else
|
|
return ⊤
|
|
}
|
|
return cur;
|
|
}
|
|
|
|
Message*
|
|
qcmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
USED(m);
|
|
|
|
flushdeleted(nil);
|
|
|
|
if(didopen)
|
|
closemb();
|
|
Bflush(&out);
|
|
|
|
exitfs(0);
|
|
return nil; // not reached
|
|
}
|
|
|
|
Message*
|
|
ycmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
|
|
doflush = 1;
|
|
|
|
return icmd(nil, m);
|
|
}
|
|
|
|
Message*
|
|
xcmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
USED(m);
|
|
|
|
exitfs(0);
|
|
return nil; // not reached
|
|
}
|
|
|
|
Message*
|
|
eqcmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
|
|
if(m == &top)
|
|
Bprint(&out, "0\n");
|
|
else
|
|
Bprint(&out, "%d\n", m->id);
|
|
return nil;
|
|
}
|
|
|
|
Message*
|
|
dcmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
while(m->parent != &top)
|
|
m = m->parent;
|
|
m->deleted = 1;
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
ucmd(Cmd *x, Message *m)
|
|
{
|
|
USED(x);
|
|
|
|
if(m == &top)
|
|
return nil;
|
|
while(m->parent != &top)
|
|
m = m->parent;
|
|
if(m->deleted < 0)
|
|
Bprint(&out, "!can't undelete, already flushed\n");
|
|
m->deleted = 0;
|
|
return m;
|
|
}
|
|
|
|
|
|
Message*
|
|
icmd(Cmd *x, Message *m)
|
|
{
|
|
int n;
|
|
|
|
USED(x);
|
|
n = dir2message(&top, reverse);
|
|
if(n > 0)
|
|
Bprint(&out, "%d new message%s\n", n, plural(n));
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
helpcmd(Cmd *x, Message *m)
|
|
{
|
|
int i;
|
|
|
|
USED(x);
|
|
Bprint(&out, "Commands are of the form [<range>] <command> [args]\n");
|
|
Bprint(&out, "<range> := <addr> | <addr>','<addr>| 'g'<search>\n");
|
|
Bprint(&out, "<addr> := '.' | '$' | '^' | <number> | <search> | <addr>'+'<addr> | <addr>'-'<addr>\n");
|
|
Bprint(&out, "<search> := '/'<regexp>'/' | '?'<regexp>'?' | '%%'<regexp>'%%'\n");
|
|
Bprint(&out, "<command> :=\n");
|
|
for(i = 0; cmdtab[i].cmd != nil; i++)
|
|
Bprint(&out, "%s\n", cmdtab[i].help);
|
|
return m;
|
|
}
|
|
|
|
int
|
|
tomailer(char **av)
|
|
{
|
|
static char *marshal;
|
|
Waitmsg *w;
|
|
int pid, i;
|
|
|
|
if(marshal == nil)
|
|
marshal = unsharp("#9/bin/upas/marshal");
|
|
|
|
// start the mailer and get out of the way
|
|
switch(pid = fork()){
|
|
case -1:
|
|
fprint(2, "can't fork: %r\n");
|
|
return -1;
|
|
case 0:
|
|
Bprint(&out, "!%s", marshal);
|
|
for(i = 1; av[i]; i++){
|
|
if(strchr(av[i], ' ') != nil)
|
|
Bprint(&out, " '%s'", av[i]);
|
|
else
|
|
Bprint(&out, " %s", av[i]);
|
|
}
|
|
Bprint(&out, "\n");
|
|
Bflush(&out);
|
|
av[0] = "marshal";
|
|
chdir(wd);
|
|
exec(marshal, av);
|
|
fprint(2, "couldn't exec %s\n", marshal);
|
|
threadexits(0);
|
|
default:
|
|
w = wait();
|
|
if(w == nil){
|
|
if(interrupted)
|
|
postnote(PNPROC, pid, "die");
|
|
waitpid();
|
|
return -1;
|
|
}
|
|
if(w->msg[0]){
|
|
fprint(2, "mailer failed: %s\n", w->msg);
|
|
free(w);
|
|
return -1;
|
|
}
|
|
free(w);
|
|
Bprint(&out, "!\n");
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// like tokenize but obey "" quoting
|
|
//
|
|
int
|
|
tokenize822(char *str, char **args, int max)
|
|
{
|
|
int na;
|
|
int intok = 0, inquote = 0;
|
|
|
|
if(max <= 0)
|
|
return 0;
|
|
for(na=0; ;str++)
|
|
switch(*str) {
|
|
case ' ':
|
|
case '\t':
|
|
if(inquote)
|
|
goto Default;
|
|
/* fall through */
|
|
case '\n':
|
|
*str = 0;
|
|
if(!intok)
|
|
continue;
|
|
intok = 0;
|
|
if(na < max)
|
|
continue;
|
|
/* fall through */
|
|
case 0:
|
|
return na;
|
|
case '"':
|
|
inquote ^= 1;
|
|
/* fall through */
|
|
Default:
|
|
default:
|
|
if(intok)
|
|
continue;
|
|
args[na++] = str;
|
|
intok = 1;
|
|
}
|
|
return 0; /* can't get here; silence compiler */
|
|
}
|
|
|
|
Message*
|
|
rcmd(Cmd *c, Message *m)
|
|
{
|
|
char *av[128];
|
|
int i, ai = 1;
|
|
Message *nm;
|
|
char *addr;
|
|
String *path = nil;
|
|
String *rpath;
|
|
String *subject = nil;
|
|
String *from;
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
addr = nil;
|
|
for(nm = m; nm != ⊤ nm = nm->parent){
|
|
if(*nm->replyto != 0){
|
|
addr = nm->replyto;
|
|
break;
|
|
}
|
|
}
|
|
if(addr == nil){
|
|
Bprint(&out, "!no reply address\n");
|
|
return nil;
|
|
}
|
|
|
|
if(nm == &top){
|
|
print("!noone to reply to\n");
|
|
return nil;
|
|
}
|
|
|
|
for(nm = m; nm != ⊤ nm = nm->parent){
|
|
if(*nm->subject){
|
|
av[ai++] = "-s";
|
|
subject = addrecolon(nm->subject);
|
|
av[ai++] = s_to_c(subject);;
|
|
break;
|
|
}
|
|
}
|
|
|
|
av[ai++] = "-R";
|
|
rpath = rooted(s_clone(m->path));
|
|
av[ai++] = s_to_c(rpath);
|
|
|
|
if(strchr(c->av[0], 'f') != nil){
|
|
fcmd(c, m);
|
|
av[ai++] = "-F";
|
|
}
|
|
|
|
if(strchr(c->av[0], 'R') != nil){
|
|
av[ai++] = "-t";
|
|
av[ai++] = "message/rfc822";
|
|
av[ai++] = "-A";
|
|
path = rooted(extendpath(m->path, "raw"));
|
|
av[ai++] = s_to_c(path);
|
|
}
|
|
|
|
for(i = 1; i < c->an && ai < nelem(av)-1; i++)
|
|
av[ai++] = c->av[i];
|
|
from = s_copy(addr);
|
|
ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
|
|
av[ai] = 0;
|
|
if(tomailer(av) < 0)
|
|
m = nil;
|
|
s_free(path);
|
|
s_free(rpath);
|
|
s_free(subject);
|
|
s_free(from);
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
mcmd(Cmd *c, Message *m)
|
|
{
|
|
char **av;
|
|
int i, ai;
|
|
String *path;
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
if(c->an < 2){
|
|
fprint(2, "!usage: M list-of addresses\n");
|
|
return nil;
|
|
}
|
|
|
|
ai = 1;
|
|
av = malloc(sizeof(char*)*(c->an + 8));
|
|
|
|
av[ai++] = "-t";
|
|
if(m->parent == &top)
|
|
av[ai++] = "message/rfc822";
|
|
else
|
|
av[ai++] = "mime";
|
|
|
|
av[ai++] = "-A";
|
|
path = rooted(extendpath(m->path, "raw"));
|
|
av[ai++] = s_to_c(path);
|
|
|
|
if(strchr(c->av[0], 'M') == nil)
|
|
av[ai++] = "-n";
|
|
|
|
for(i = 1; i < c->an; i++)
|
|
av[ai++] = c->av[i];
|
|
av[ai] = 0;
|
|
|
|
if(tomailer(av) < 0)
|
|
m = nil;
|
|
if(path != nil)
|
|
s_free(path);
|
|
free(av);
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
acmd(Cmd *c, Message *m)
|
|
{
|
|
char *av[128];
|
|
int i, ai;
|
|
String *from, *to, *cc, *path = nil, *subject = nil;
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
ai = 1;
|
|
if(*m->subject){
|
|
av[ai++] = "-s";
|
|
subject = addrecolon(m->subject);
|
|
av[ai++] = s_to_c(subject);
|
|
}
|
|
|
|
if(strchr(c->av[0], 'A') != nil){
|
|
av[ai++] = "-t";
|
|
av[ai++] = "message/rfc822";
|
|
av[ai++] = "-A";
|
|
path = rooted(extendpath(m->path, "raw"));
|
|
av[ai++] = s_to_c(path);
|
|
}
|
|
|
|
for(i = 1; i < c->an && ai < nelem(av)-1; i++)
|
|
av[ai++] = c->av[i];
|
|
from = s_copy(m->from);
|
|
ai += tokenize822(s_to_c(from), &av[ai], nelem(av) - ai);
|
|
to = s_copy(m->to);
|
|
ai += tokenize822(s_to_c(to), &av[ai], nelem(av) - ai);
|
|
cc = s_copy(m->cc);
|
|
ai += tokenize822(s_to_c(cc), &av[ai], nelem(av) - ai);
|
|
av[ai] = 0;
|
|
if(tomailer(av) < 0)
|
|
return nil;
|
|
s_free(from);
|
|
s_free(to);
|
|
s_free(cc);
|
|
s_free(subject);
|
|
s_free(path);
|
|
return m;
|
|
}
|
|
|
|
String *
|
|
relpath(char *path, String *to)
|
|
{
|
|
if (*path=='/' || strncmp(path, "./", 2) == 0
|
|
|| strncmp(path, "../", 3) == 0) {
|
|
to = s_append(to, path);
|
|
} else if(mbpath) {
|
|
to = s_append(to, s_to_c(mbpath));
|
|
to->ptr = strrchr(to->base, '/')+1;
|
|
s_append(to, path);
|
|
}
|
|
return to;
|
|
}
|
|
|
|
int
|
|
appendtofile(Message *m, char *part, char *base, int mbox)
|
|
{
|
|
String *file, *h;
|
|
int in, out, rv;
|
|
|
|
file = extendpath(m->path, part);
|
|
in = open(s_to_c(file), OREAD);
|
|
if(in < 0){
|
|
fprint(2, "!message disappeared\n");
|
|
return -1;
|
|
}
|
|
|
|
s_reset(file);
|
|
|
|
relpath(base, file);
|
|
if(sysisdir(s_to_c(file))){
|
|
s_append(file, "/");
|
|
if(m->filename && strchr(m->filename, '/') == nil)
|
|
s_append(file, m->filename);
|
|
else {
|
|
s_append(file, "att.XXXXXXXXXXX");
|
|
mktemp(s_to_c(file));
|
|
}
|
|
}
|
|
if(mbox)
|
|
out = open(s_to_c(file), OWRITE);
|
|
else
|
|
out = open(s_to_c(file), OWRITE|OTRUNC);
|
|
if(out < 0){
|
|
out = create(s_to_c(file), OWRITE, 0666);
|
|
if(out < 0){
|
|
fprint(2, "!can't open %s: %r\n", s_to_c(file));
|
|
close(in);
|
|
s_free(file);
|
|
return -1;
|
|
}
|
|
}
|
|
if(mbox)
|
|
seek(out, 0, 2);
|
|
|
|
// put on a 'From ' line
|
|
if(mbox){
|
|
while(m->parent != &top)
|
|
m = m->parent;
|
|
h = file2string(m->path, "unixheader");
|
|
fprint(out, "%s", s_to_c(h));
|
|
s_free(h);
|
|
}
|
|
|
|
// copy the message escaping what we have to ad adding newlines if we have to
|
|
if(mbox)
|
|
rv = appendfiletombox(in, out);
|
|
else
|
|
rv = appendfiletofile(in, out);
|
|
|
|
close(in);
|
|
close(out);
|
|
|
|
if(rv >= 0)
|
|
print("!saved in %s\n", s_to_c(file));
|
|
s_free(file);
|
|
return rv;
|
|
}
|
|
|
|
Message*
|
|
scmd(Cmd *c, Message *m)
|
|
{
|
|
char *file;
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
switch(c->an){
|
|
case 1:
|
|
file = "stored";
|
|
break;
|
|
case 2:
|
|
file = c->av[1];
|
|
break;
|
|
default:
|
|
fprint(2, "!usage: s filename\n");
|
|
return nil;
|
|
}
|
|
|
|
if(appendtofile(m, "raw", file, 1) < 0)
|
|
return nil;
|
|
|
|
m->stored = 1;
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
wcmd(Cmd *c, Message *m)
|
|
{
|
|
char *file;
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
switch(c->an){
|
|
case 2:
|
|
file = c->av[1];
|
|
break;
|
|
case 1:
|
|
if(*m->filename == 0){
|
|
fprint(2, "!usage: w filename\n");
|
|
return nil;
|
|
}
|
|
file = strrchr(m->filename, '/');
|
|
if(file != nil)
|
|
file++;
|
|
else
|
|
file = m->filename;
|
|
break;
|
|
default:
|
|
fprint(2, "!usage: w filename\n");
|
|
return nil;
|
|
}
|
|
|
|
if(appendtofile(m, "body", file, 0) < 0)
|
|
return nil;
|
|
m->stored = 1;
|
|
return m;
|
|
}
|
|
|
|
char *specialfile[] =
|
|
{
|
|
"pipeto",
|
|
"pipefrom",
|
|
"L.mbox",
|
|
"forward",
|
|
"names"
|
|
};
|
|
|
|
// return 1 if this is a special file
|
|
static int
|
|
special(String *s)
|
|
{
|
|
char *p;
|
|
int i;
|
|
|
|
p = strrchr(s_to_c(s), '/');
|
|
if(p == nil)
|
|
p = s_to_c(s);
|
|
else
|
|
p++;
|
|
for(i = 0; i < nelem(specialfile); i++)
|
|
if(strcmp(p, specialfile[i]) == 0)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
// open the folder using the recipients account name
|
|
static String*
|
|
foldername(char *rcvr)
|
|
{
|
|
char *p;
|
|
int c;
|
|
String *file;
|
|
Dir *d;
|
|
int scarey;
|
|
|
|
file = s_new();
|
|
mboxpath("f", user, file, 0);
|
|
d = dirstat(s_to_c(file));
|
|
|
|
// if $mail/f exists, store there, otherwise in $mail
|
|
s_restart(file);
|
|
if(d && d->qid.type == QTDIR){
|
|
scarey = 0;
|
|
s_append(file, "f/");
|
|
} else {
|
|
scarey = 1;
|
|
}
|
|
free(d);
|
|
|
|
p = strrchr(rcvr, '!');
|
|
if(p != nil)
|
|
rcvr = p+1;
|
|
|
|
while(*rcvr && *rcvr != '@'){
|
|
c = *rcvr++;
|
|
if(c == '/')
|
|
c = '_';
|
|
s_putc(file, c);
|
|
}
|
|
s_terminate(file);
|
|
|
|
if(scarey && special(file)){
|
|
fprint(2, "!won't overwrite %s\n", s_to_c(file));
|
|
s_free(file);
|
|
return nil;
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
Message*
|
|
fcmd(Cmd *c, Message *m)
|
|
{
|
|
String *folder;
|
|
|
|
if(c->an > 1){
|
|
fprint(2, "!usage: f takes no arguments\n");
|
|
return nil;
|
|
}
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
folder = foldername(m->from);
|
|
if(folder == nil)
|
|
return nil;
|
|
|
|
if(appendtofile(m, "raw", s_to_c(folder), 1) < 0){
|
|
s_free(folder);
|
|
return nil;
|
|
}
|
|
s_free(folder);
|
|
|
|
m->stored = 1;
|
|
return m;
|
|
}
|
|
|
|
void
|
|
system(char *cmd, char **av, int in)
|
|
{
|
|
int pid;
|
|
|
|
switch(pid=fork()){
|
|
case -1:
|
|
return;
|
|
case 0:
|
|
if(strcmp(cmd, "rc") == 0)
|
|
cmd = unsharp("#9/bin/rc");
|
|
if(in >= 0){
|
|
close(0);
|
|
dup(in, 0);
|
|
close(in);
|
|
}
|
|
if(wd[0] != 0)
|
|
chdir(wd);
|
|
exec(cmd, av);
|
|
fprint(2, "!couldn't exec %s\n", cmd);
|
|
threadexits(0);
|
|
default:
|
|
if(in >= 0)
|
|
close(in);
|
|
while(waitpid() < 0){
|
|
if(!interrupted)
|
|
break;
|
|
postnote(PNPROC, pid, "die");
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
Message*
|
|
bangcmd(Cmd *c, Message *m)
|
|
{
|
|
char cmd[4*1024];
|
|
char *p, *e;
|
|
char *av[4];
|
|
int i;
|
|
|
|
cmd[0] = 0;
|
|
p = cmd;
|
|
e = cmd+sizeof(cmd);
|
|
for(i = 1; i < c->an; i++)
|
|
p = seprint(p, e, "%s ", c->av[i]);
|
|
av[0] = "rc";
|
|
av[1] = "-c";
|
|
av[2] = cmd;
|
|
av[3] = 0;
|
|
system("rc", av, -1);
|
|
Bprint(&out, "!\n");
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
xpipecmd(Cmd *c, Message *m, char *part)
|
|
{
|
|
char cmd[128];
|
|
char *p, *e;
|
|
char *av[4];
|
|
String *path;
|
|
int i, fd;
|
|
|
|
if(c->an < 2){
|
|
Bprint(&out, "!usage: | cmd\n");
|
|
return nil;
|
|
}
|
|
|
|
if(m == &top){
|
|
Bprint(&out, "!address\n");
|
|
return nil;
|
|
}
|
|
|
|
path = extendpath(m->path, part);
|
|
fd = fsopenfd(mailfs, s_to_c(path), OREAD);
|
|
s_free(path);
|
|
if(fd < 0){ // compatibility with older upas/fs
|
|
path = extendpath(m->path, "raw");
|
|
fd = fsopenfd(mailfs, s_to_c(path), OREAD);
|
|
s_free(path);
|
|
}
|
|
if(fd < 0){
|
|
fprint(2, "!message disappeared\n");
|
|
return nil;
|
|
}
|
|
|
|
p = cmd;
|
|
e = cmd+sizeof(cmd);
|
|
cmd[0] = 0;
|
|
for(i = 1; i < c->an; i++)
|
|
p = seprint(p, e, "%s ", c->av[i]);
|
|
av[0] = "rc";
|
|
av[1] = "-c";
|
|
av[2] = cmd;
|
|
av[3] = 0;
|
|
system("rc", av, fd); /* system closes fd */
|
|
Bprint(&out, "!\n");
|
|
return m;
|
|
}
|
|
|
|
Message*
|
|
pipecmd(Cmd *c, Message *m)
|
|
{
|
|
return xpipecmd(c, m, "body");
|
|
}
|
|
|
|
Message*
|
|
rpipecmd(Cmd *c, Message *m)
|
|
{
|
|
return xpipecmd(c, m, "rawunix");
|
|
}
|
|
|
|
void
|
|
closemb(void)
|
|
{
|
|
CFid *fd;
|
|
|
|
fd = fsopen(mailfs, "ctl", OWRITE);
|
|
if(fd == nil)
|
|
sysfatal("can't open ctl: %r");
|
|
|
|
// close current mailbox
|
|
if(*mbname && strcmp(mbname, "mbox") != 0)
|
|
fsprint(fd, "close %s", mbname);
|
|
|
|
fsclose(fd);
|
|
}
|
|
|
|
int
|
|
switchmb(char *file, char *singleton)
|
|
{
|
|
char *p;
|
|
int n, fd;
|
|
String *path;
|
|
char buf[256];
|
|
|
|
// if the user didn't say anything and there
|
|
// is an mbox mounted already, use that one
|
|
// so that the upas/fs -fdefault default is honored.
|
|
if(file || (singleton && fsaccess(mailfs, singleton, 0) < 0)){
|
|
/* XXX all wrong */
|
|
fprint(2, "file=%s singleton=%s\n", file, singleton);
|
|
if(file == nil)
|
|
file = "mbox";
|
|
|
|
// close current mailbox
|
|
closemb();
|
|
didopen = 1;
|
|
|
|
fd = open("/mail/fs/ctl", ORDWR);
|
|
if(fd < 0)
|
|
sysfatal("can't open /mail/fs/ctl: %r");
|
|
|
|
path = s_new();
|
|
|
|
// get an absolute path to the mail box
|
|
if(strncmp(file, "./", 2) == 0){
|
|
// resolve path here since upas/fs doesn't know
|
|
// our working directory
|
|
if(getwd(buf, sizeof(buf)-strlen(file)) == nil){
|
|
fprint(2, "!can't get working directory: %s\n", buf);
|
|
return -1;
|
|
}
|
|
s_append(path, buf);
|
|
s_append(path, file+1);
|
|
} else {
|
|
mboxpath(file, user, path, 0);
|
|
}
|
|
|
|
// make up a handle to use when talking to fs
|
|
p = strrchr(file, '/');
|
|
if(p == nil){
|
|
// if its in the mailbox directory, just use the name
|
|
strncpy(mbname, file, sizeof(mbname));
|
|
mbname[sizeof(mbname)-1] = 0;
|
|
} else {
|
|
// make up a mailbox name
|
|
p = strrchr(s_to_c(path), '/');
|
|
p++;
|
|
if(*p == 0){
|
|
fprint(2, "!bad mbox name");
|
|
return -1;
|
|
}
|
|
strncpy(mbname, p, sizeof(mbname));
|
|
mbname[sizeof(mbname)-1] = 0;
|
|
n = strlen(mbname);
|
|
if(n > Elemlen-12)
|
|
n = Elemlen-12;
|
|
sprint(mbname+n, "%ld", time(0));
|
|
}
|
|
|
|
if(fprint(fd, "open %s %s", s_to_c(path), mbname) < 0){
|
|
fprint(2, "!can't 'open %s %s': %r\n", file, mbname);
|
|
s_free(path);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
}else
|
|
if (singleton && fsaccess(mailfs, singleton, 0)==0){
|
|
if ((p = strchr(singleton, '/')) == nil){
|
|
fprint(2, "!bad mbox name");
|
|
return -1;
|
|
}
|
|
n = p-singleton;
|
|
strncpy(mbname, singleton, n);
|
|
mbname[n+1] = 0;
|
|
path = s_reset(nil);
|
|
mboxpath(mbname, user, path, 0);
|
|
}else{
|
|
path = s_reset(nil);
|
|
mboxpath("mbox", user, path, 0);
|
|
strcpy(mbname, "mbox");
|
|
}
|
|
|
|
snprint(root, sizeof root, "%s", mbname);
|
|
rootlen = strlen(root);
|
|
|
|
if(mbpath != nil)
|
|
s_free(mbpath);
|
|
mbpath = path;
|
|
return 0;
|
|
}
|
|
|
|
// like tokenize but for into lines
|
|
int
|
|
lineize(char *s, char **f, int n)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; *s && i < n; i++){
|
|
f[i] = s;
|
|
s = strchr(s, '\n');
|
|
if(s == nil)
|
|
break;
|
|
*s++ = 0;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
|
|
|
|
String*
|
|
rooted(String *s)
|
|
{
|
|
static char buf[256];
|
|
|
|
if(strcmp(root, ".") != 0)
|
|
return s;
|
|
snprint(buf, sizeof(buf), "/mail/fs/%s/%s", mbname, s_to_c(s));
|
|
s_free(s);
|
|
return s_copy(buf);
|
|
}
|
|
|
|
int
|
|
plumb(Message *m, Ctype *cp)
|
|
{
|
|
String *s;
|
|
Plumbmsg *pm;
|
|
static int fd = -2;
|
|
|
|
if(cp->plumbdest == nil)
|
|
return -1;
|
|
|
|
if(fd < -1)
|
|
fd = plumbopen("send", OWRITE);
|
|
if(fd < 0)
|
|
return -1;
|
|
|
|
pm = mallocz(sizeof(Plumbmsg), 1);
|
|
pm->src = strdup("mail");
|
|
if(*cp->plumbdest)
|
|
pm->dst = strdup(cp->plumbdest);
|
|
pm->wdir = nil;
|
|
pm->type = strdup("text");
|
|
pm->ndata = -1;
|
|
s = rooted(extendpath(m->path, "body"));
|
|
if(cp->ext != nil){
|
|
s_append(s, ".");
|
|
s_append(s, cp->ext);
|
|
}
|
|
pm->data = strdup(s_to_c(s));
|
|
s_free(s);
|
|
plumbsend(fd, pm);
|
|
plumbfree(pm);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
regerror(char *x)
|
|
{
|
|
USED(x);
|
|
}
|
|
|
|
String*
|
|
addrecolon(char *s)
|
|
{
|
|
String *str;
|
|
|
|
if(cistrncmp(s, "re:", 3) != 0){
|
|
str = s_copy("Re: ");
|
|
s_append(str, s);
|
|
} else
|
|
str = s_copy(s);
|
|
return str;
|
|
}
|
|
|
|
void
|
|
exitfs(char *rv)
|
|
{
|
|
threadexitsall(rv);
|
|
}
|