Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#include "common.h"
#include "send.h"

#include "../smtp/smtp.h"
#include "../smtp/y.tab.h"

/* global to this file */
static Reprog *rfprog;
static Reprog *fprog;

#define VMLIMIT (64*1024)
#define MSGLIMIT (128*1024*1024)

int received;   /* from rfc822.y */

static String*  getstring(Node *p);
static String*  getaddr(Node *p);

extern int
default_from(message *mp)
{
        char *cp, *lp;

        cp = getenv("upasname");
        lp = getlog();
        if(lp == nil)
                return -1;

        if(cp && *cp)
                s_append(mp->sender, cp);
        else
                s_append(mp->sender, lp);
        s_append(mp->date, thedate());
        return 0;
}

extern message *
m_new(void)
{
        message *mp;

        mp = (message *)mallocz(sizeof(message), 1);
        if (mp == 0) {
                perror("message:");
                exit(1);
        }
        mp->sender = s_new();
        mp->replyaddr = s_new();
        mp->date = s_new();
        mp->body = s_new();
        mp->size = 0;
        mp->fd = -1;
        return mp;
}

extern void
m_free(message *mp)
{
        if(mp->fd >= 0){
                close(mp->fd);
                sysremove(s_to_c(mp->tmp));
                s_free(mp->tmp);
        }
        s_free(mp->sender);
        s_free(mp->date);
        s_free(mp->body);
        s_free(mp->havefrom);
        s_free(mp->havesender);
        s_free(mp->havereplyto);
        s_free(mp->havesubject);
        free((char *)mp);
}

/* read a message into a temp file, return an open fd to it in mp->fd */
static int
m_read_to_file(Biobuf *fp, message *mp)
{
        int fd;
        int n;
        String *file;
        char buf[4*1024];

        file = s_new();
        /*
         *  create temp file to be removed on close
         */
        abspath("mtXXXXXX", UPASTMP, file);
        mktemp(s_to_c(file));
        if((fd = syscreate(s_to_c(file), ORDWR|ORCLOSE, 0600))<0){
                s_free(file);
                return -1;
        }
        mp->tmp = file;

        /*
         *  read the rest into the temp file
         */
        while((n = Bread(fp, buf, sizeof(buf))) > 0){
                if(write(fd, buf, n) != n){
                        close(fd);
                        return -1;
                }
                mp->size += n;
                if(mp->size > MSGLIMIT){
                        mp->size = -1;
                        break;
                }
        }

        mp->fd = fd;
        return 0;
}

/* get the first address from a node */
static String*
getaddr(Node *p)
{
        for(; p; p = p->next)
                if(p->s && p->addr)
                        return s_copy(s_to_c(p->s));
        return nil;
}

/* get the text of a header line minus the field name */
static String*
getstring(Node *p)
{
        String *s;

        s = s_new();
        if(p == nil)
                return s;

        for(p = p->next; p; p = p->next){
                if(p->s){
                        s_append(s, s_to_c(p->s));
                }else{
                        s_putc(s, p->c);
                        s_terminate(s);
                }
                if(p->white)
                        s_append(s, s_to_c(p->white));
        }
        return s;
}

static char *fieldname[] =
{
[WORD-WORD]     "WORD",
[DATE-WORD]     "DATE",
[RESENT_DATE-WORD]      "RESENT_DATE",
[RETURN_PATH-WORD]      "RETURN_PATH",
[FROM-WORD]     "FROM",
[SENDER-WORD]   "SENDER",
[REPLY_TO-WORD] "REPLY_TO",
[RESENT_FROM-WORD]      "RESENT_FROM",
[RESENT_SENDER-WORD]    "RESENT_SENDER",
[RESENT_REPLY_TO-WORD]  "RESENT_REPLY_TO",
[SUBJECT-WORD]  "SUBJECT",
[TO-WORD]       "TO",
[CC-WORD]       "CC",
[BCC-WORD]      "BCC",
[RESENT_TO-WORD]        "RESENT_TO",
[RESENT_CC-WORD]        "RESENT_CC",
[RESENT_BCC-WORD]       "RESENT_BCC",
[REMOTE-WORD]   "REMOTE",
[PRECEDENCE-WORD]       "PRECEDENCE",
[MIMEVERSION-WORD]      "MIMEVERSION",
[CONTENTTYPE-WORD]      "CONTENTTYPE",
[MESSAGEID-WORD]        "MESSAGEID",
[RECEIVED-WORD] "RECEIVED",
[MAILER-WORD]   "MAILER",
[BADTOKEN-WORD] "BADTOKEN",
};

/* fix 822 addresses */
static void
rfc822cruft(message *mp)
{
        Field *f;
        Node *p;
        String *body, *s;
        char *cp;

        /*
         *  parse headers in in-core part
         */
        yyinit(s_to_c(mp->body), s_len(mp->body));
        mp->rfc822headers = 0;
        yyparse();
        mp->rfc822headers = 1;
        mp->received = received;

        /*
         *  remove equivalent systems in all addresses
         */
        body = s_new();
        cp = s_to_c(mp->body);
        for(f = firstfield; f; f = f->next){
                if(f->node->c == MIMEVERSION)
                        mp->havemime = 1;
                if(f->node->c == FROM)
                        mp->havefrom = getaddr(f->node);
                if(f->node->c == SENDER)
                        mp->havesender = getaddr(f->node);
                if(f->node->c == REPLY_TO)
                        mp->havereplyto = getaddr(f->node);
                if(f->node->c == TO)
                        mp->haveto = 1;
                if(f->node->c == DATE)
                        mp->havedate = 1;
                if(f->node->c == SUBJECT)
                        mp->havesubject = getstring(f->node);
                if(f->node->c == PRECEDENCE && f->node->next && f->node->next->next){
                        s = f->node->next->next->s;
                        if(s && (strcmp(s_to_c(s), "bulk") == 0
                                || strcmp(s_to_c(s), "Bulk") == 0))
                                        mp->bulk = 1;
                }
                for(p = f->node; p; p = p->next){
                        if(p->s){
                                if(p->addr){
                                        cp = skipequiv(s_to_c(p->s));
                                        s_append(body, cp);
                                } else 
                                        s_append(body, s_to_c(p->s));
                        }else{
                                s_putc(body, p->c);
                                s_terminate(body);
                        }
                        if(p->white)
                                s_append(body, s_to_c(p->white));
                        cp = p->end+1;
                }
                s_append(body, "\n");
        }

        if(*s_to_c(body) == 0){
                s_free(body);
                return;
        }

        if(*cp != '\n')
                s_append(body, "\n");
        s_memappend(body, cp, s_len(mp->body) - (cp - s_to_c(mp->body)));
        s_terminate(body);

        firstfield = 0;
        mp->size += s_len(body) - s_len(mp->body);
        s_free(mp->body);
        mp->body = body;
}

/* read in a message, interpret the 'From' header */
extern message *
m_read(Biobuf *fp, int rmail, int interactive)
{
        message *mp;
        Resub subexp[10];
        char *line;
        int first;
        int n;

        mp = m_new();

        /* parse From lines if remote */
        if (rmail) {
                /* get remote address */
                String *sender=s_new();

                if (rfprog == 0)
                        rfprog = regcomp(REMFROMRE);
                first = 1;
                while(s_read_line(fp, s_restart(mp->body)) != 0) {
                        memset(subexp, 0, sizeof(subexp));
                        if (regexec(rfprog, s_to_c(mp->body), subexp, 10) == 0){
                                if(first == 0)
                                        break;
                                if (fprog == 0)
                                        fprog = regcomp(FROMRE);
                                memset(subexp, 0, sizeof(subexp));
                                if(regexec(fprog, s_to_c(mp->body), subexp,10) == 0)
                                        break;
                                s_restart(mp->body);
                                append_match(subexp, s_restart(sender), SENDERMATCH);
                                append_match(subexp, s_restart(mp->date), DATEMATCH);
                                break;
                        }
                        append_match(subexp, s_restart(sender), REMSENDERMATCH);
                        append_match(subexp, s_restart(mp->date), REMDATEMATCH);
                        if(subexp[REMSYSMATCH].sp!=subexp[REMSYSMATCH].ep){
                                append_match(subexp, mp->sender, REMSYSMATCH);
                                s_append(mp->sender, "!");
                        }
                        first = 0;
                }
                s_append(mp->sender, s_to_c(sender));

                s_free(sender);
        }
        if(*s_to_c(mp->sender)=='\0')
                default_from(mp);

        /* if sender address is unreturnable, treat message as bulk mail */
        if(!returnable(s_to_c(mp->sender)))
                mp->bulk = 1;

        /* get body */
        if(interactive && !rmail){
                /* user typing on terminal: terminator == '.' or EOF */
                for(;;) {
                        line = s_read_line(fp, mp->body);
                        if (line == 0)
                                break;
                        if (strcmp(".\n", line)==0) {
                                mp->body->ptr -= 2;
                                *mp->body->ptr = '\0';
                                break;
                        }
                }
                mp->size = mp->body->ptr - mp->body->base;
        } else {
                /*
                 *  read up to VMLIMIT bytes (more or less) into main memory.
                 *  if message is longer put the rest in a tmp file.
                 */
                mp->size = mp->body->ptr - mp->body->base;
                n = s_read(fp, mp->body, VMLIMIT);
                if(n < 0){
                        perror("m_read");
                        exit(1);
                }
                mp->size += n;
                if(n == VMLIMIT){
                        if(m_read_to_file(fp, mp) < 0){
                                perror("m_read");
                                exit(1);
                        }
                }

        }

        /*
         *  ignore 0 length messages from a terminal
         */
        if (!rmail && mp->size == 0)
                return 0;

        rfc822cruft(mp);

        return mp;
}

/* return a piece of message starting at `offset' */
extern int
m_get(message *mp, long offset, char **pp)
{
        static char buf[4*1024];

        /*
         *  are we past eof?
         */
        if(offset >= mp->size)
                return 0;

        /*
         *  are we in the virtual memory portion?
         */
        if(offset < s_len(mp->body)){
                *pp = mp->body->base + offset;
                return mp->body->ptr - mp->body->base - offset;
        }

        /*
         *  read it from the temp file
         */
        offset -= s_len(mp->body);
        if(mp->fd < 0)
                return -1;
        if(seek(mp->fd, offset, 0)<0)
                return -1;
        *pp = buf;
        return read(mp->fd, buf, sizeof buf);
}

/* output the message body without ^From escapes */
static int
m_noescape(message *mp, Biobuf *fp)
{
        long offset;
        int n;
        char *p;

        for(offset = 0; offset < mp->size; offset += n){
                n = m_get(mp, offset, &p);
                if(n <= 0){
                        Bflush(fp);
                        return -1;
                }
                if(Bwrite(fp, p, n) < 0)
                        return -1;
        }
        return Bflush(fp);
}

/*
 *  Output the message body with '^From ' escapes.
 *  Ensures that any line starting with a 'From ' gets a ' ' stuck
 *  in front of it.
 */
static int
m_escape(message *mp, Biobuf *fp)
{
        char *p, *np;
        char *end;
        long offset;
        int m, n;
        char *start;

        for(offset = 0; offset < mp->size; offset += n){
                n = m_get(mp, offset, &start);
                if(n < 0){
                        Bflush(fp);
                        return -1;
                }

                p = start;
                for(end = p+n; p < end; p += m){
                        np = memchr(p, '\n', end-p);
                        if(np == 0){
                                Bwrite(fp, p, end-p);
                                break;
                        }
                        m = np - p + 1;
                        if(m > 5 && strncmp(p, "From ", 5) == 0)
                                Bputc(fp, ' ');
                        Bwrite(fp, p, m);
                }
        }
        Bflush(fp);
        return 0;
}

static int
printfrom(message *mp, Biobuf *fp)
{
        String *s;
        int rv;

        if(!returnable(s_to_c(mp->sender)))
                return Bprint(fp, "From: Postmaster\n");

        s = username(mp->sender);
        if(s) {
                s_append(s, " <");
                s_append(s, s_to_c(mp->sender));
                s_append(s, ">");
        } else {
                s = s_copy(s_to_c(mp->sender));
        }
        s = unescapespecial(s);
        rv = Bprint(fp, "From: %s\n", s_to_c(s));
        s_free(s);
        return rv;
}

static char *
rewritezone(char *z)
{
        int mindiff;
        char s;
        Tm *tm;
        static char x[7];

        tm = localtime(time(0));
        mindiff = tm->tzoff/60;

        /* if not in my timezone, don't change anything */
        if(strcmp(tm->zone, z) != 0)
                return z;

        if(mindiff < 0){
                s = '-';
                mindiff = -mindiff;
        } else
                s = '+';

        sprint(x, "%c%.2d%.2d", s, mindiff/60, mindiff%60);
        return x;
}

int
isutf8(String *s)
{
        char *p;
        
        for(p = s_to_c(s);  *p; p++)
                if(*p&0x80)
                        return 1;
        return 0;
}

void
printutf8mime(Biobuf *b)
{
        Bprint(b, "MIME-Version: 1.0\n");
        Bprint(b, "Content-Type: text/plain; charset=\"UTF-8\"\n");
        Bprint(b, "Content-Transfer-Encoding: 8bit\n");
}

/* output a message */
extern int
m_print(message *mp, Biobuf *fp, char *remote, int mbox)
{
        String *date, *sender;
        char *f[6];
        int n;

        sender = unescapespecial(s_clone(mp->sender));

        if (remote != 0){
                if(print_remote_header(fp,s_to_c(sender),s_to_c(mp->date),remote) < 0){
                        s_free(sender);
                        return -1;
                }
        } else {
                if(print_header(fp, s_to_c(sender), s_to_c(mp->date)) < 0){
                        s_free(sender);
                        return -1;
                }
        }
        s_free(sender);
        if(!rmail && !mp->havedate){
                /* add a date: line Date: Sun, 19 Apr 1998 12:27:52 -0400 */
                date = s_copy(s_to_c(mp->date));
                n = getfields(s_to_c(date), f, 6, 1, " \t");
                if(n == 6)
                        Bprint(fp, "Date: %s, %s %s %s %s %s\n", f[0], f[2], f[1],
                         f[5], f[3], rewritezone(f[4]));
        }
        if(!rmail && !mp->havemime && isutf8(mp->body))
                printutf8mime(fp);
        if(mp->to){
                /* add the to: line */
                if (Bprint(fp, "%s\n", s_to_c(mp->to)) < 0)
                        return -1;
                /* add the from: line */
                if (!mp->havefrom && printfrom(mp, fp) < 0)
                        return -1;
                if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
                        if (Bprint(fp, "\n") < 0)
                                return -1;
        } else if(!rmail){
                /* add the from: line */
                if (!mp->havefrom && printfrom(mp, fp) < 0)
                        return -1;
                if(!mp->rfc822headers && *s_to_c(mp->body) != '\n')
                        if (Bprint(fp, "\n") < 0)
                                return -1;
        }

        if (!mbox)
                return m_noescape(mp, fp);
        return m_escape(mp, fp);
}

/* print just the message body */
extern int
m_bprint(message *mp, Biobuf *fp)
{
        return m_noescape(mp, fp);
}