shithub: riscv

ref: 5aa224c153db83a9fe82848860a062c7c3566cc5
dir: /sys/src/cmd/upas/send/message.c/

View raw version
#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);
}