Skip to content
Snippets Groups Projects
imapservice.js 56.5 KiB
Newer Older
deuce's avatar
deuce committed
/*
 * IMAP Server... whee!
 * Refer to RFC 3501
 *
 * Copyright 2009, Stephen Hurd.
 * Don't steal my code bitches.
 *
 * $Id: imapservice.js,v 1.76 2020/08/12 06:48:14 rswindell Exp $
deuce's avatar
deuce committed
 */

require('sbbsdefs.js', 'SCAN_CFG_NEW');
require('smbdefs.js', 'MSG_READ');
deuce's avatar
deuce committed
load("822header.js");
deuce's avatar
deuce committed
var sepchar="|";
var debug=false;
var debugRX=false;
deuce's avatar
deuce committed

deuce's avatar
deuce committed
// Global variables
const UnAuthenticated=0;
const Authenticated=1;
const Selected=2;
var state=UnAuthenticated;
var base;
var index={offsets:[],idx:{}};
deuce's avatar
deuce committed
var line;
var readonly=true;
var orig_ptrs={};
var msg_ptrs={};
var curr_status={exists:0,recent:0,unseen:0,uidnext:0,uidvalidity:0};
var saved_config={mail:{scan_ptr:0}};
var scan_ptr;
deuce's avatar
deuce committed
/**********************/
/* Encoding functions */
/**********************/
/* 
 * These encode at least as is passed (token/string/binary)
 */
function encode_binary(str)
{
	return '{'+str.length+'}\r\n'+str;
}

function encode_string(str)
{
	if(str=='')
		return('""');

	if(str.search(/[\r\n\x80-\xff]/)==-1) {
		str=str.replace(/([\\\"])/g, "\\$1");
deuce's avatar
deuce committed
		return '"'+str+'"';
	}

	return encode_binary(str);
}

function encode_token(str)
{
	if(str=='')
		return(encode_string(str));

	if(str.search(/[\(\)\{ \x00-\x1F\*\%\"\\\]]/)==-1)
		return str;

	return(encode_string(str));
}

/*****************************/
/* Message header extensions */
/*****************************/

MsgBase.HeaderPrototype.get_from=function (force)
{
	if(force===true)
		delete this.from_header;

	if(this.from_header==undefined) {
		if(!this.from_net_type || this.from_net_addr.length==0)    /* local message */
			this.from_header = this.from + " <" + this.from.replace(/ /g,".").toLowerCase() + "@" + system.inetaddr + ">";
		else if(!this.from_net_addr.length)
			this.from_header = this.from;
		else if(this.from_net_addr.indexOf('@')!=-1)
			this.from_header = this.from+" <"+this.from_net_addr+">";
		else
			this.from_header = this.from+" <"+this.from.replace(/ /g,".").toLowerCase()+"@"+this.from_net_addr+">";
	}
	return(this.from_header);
deuce's avatar
deuce committed
};

MsgBase.HeaderPrototype.parse_headers=function(force)
{
	if(force===true)
		delete this.parsed_headers;

	if(this.parsed_headers==undefined)
deuce's avatar
deuce committed
		this.parsed_headers=parse_headers(this.get_rfc822_header(force));
deuce's avatar
deuce committed
};

MsgBase.HeaderPrototype.get_envelope=function (force)
{
	function parse_header(header, is_addresses) {
deuce's avatar
deuce committed
		var m2;

		if(header==undefined || header.length==0)
			return("NIL");

		header=header.pop().replace(new RegExp("^"+abnf.field_name+abnf.WSP+"*:","i"),"");
		header=header.replace(/\r\n$/,'');
		/* TODO: Use mime.js ABNF to parse this correctly */
		if(is_addresses) {
			if((m2=header.match(/^\s*(.*)\s+<([^@]*)@(.*)>\s*$/))!=null) {
				m2[1]=m2[1].replace(/^"(.*)"$/, "$1");
				return '(('+[encode_string(m2[1]), "NIL", encode_string(m2[2]), encode_string(m2[3])].join(" ")+'))';
			}
			else if((m2=header.match(/^\s*(.*)\s+<([^@]*)>\s*$/))!=null) {
				m2[1]=m2[1].replace(/^"(.*)"$/, "$1");
				return '(('+[encode_string(m2[1]), "NIL", encode_string(m2[2]), "NIL"].join(" ")+'))';
			}
			else if((m2=header.match(/^\s*<([^@]*)@(.*)>\s*$/))!=null) {
				return '(('+["NIL", "NIL", encode_string(m2[1]), encode_string(m2[2])].join(" ")+'))';
			}
			else if((m2=header.match(/^\s*([^@]*)@(.*)\s*$/))!=null) {
				return '(('+["NIL", "NIL", encode_string(m2[1]), encode_string(m2[2])].join(" ")+'))';
			}
			else
				return '(('+[encode_string(header), "NIL", "NIL", "NIL"].join(" ")+'))';
		}
		else
			return(encode_string(header));
	}

	var hdrs;

	if(this.envelope==undefined) {
		hdrs=this.parse_headers();
deuce's avatar
deuce committed
		this.envelope=[];
		this.envelope.push(parse_header(hdrs.date, false));
		this.envelope.push(parse_header(hdrs.subject, false));
		this.envelope.push(parse_header(hdrs.from, true));
		this.envelope.push(parse_header(hdrs.sender, true));
		this.envelope.push(parse_header(hdrs['reply-to'], true));
		this.envelope.push(parse_header(hdrs.to, true));
		this.envelope.push(parse_header(hdrs.cc, true));
		this.envelope.push(parse_header(hdrs.bcc, true));
		this.envelope.push(parse_header(hdrs['in-reply-to'], false));
		this.envelope.push(parse_header(hdrs['message-id'], false));
deuce's avatar
deuce committed
	return(this.envelope);
deuce's avatar
deuce committed
};


/***********************/
/* Debugging Functions */
/***********************/

function dump_obj(obj, name)
{
	var i;

	for(i in obj) {
		if(typeof(obj[i])=='object')
			dump_obj(obj[i], name+'['+i+']');
		else
			log(name+'['+i+']="'+obj[i]+'"');
	}
}

function debug_log(line, rx)
deuce's avatar
deuce committed
{
	if(debug)
		log(line);
	else if(rx && debugRX)
		log(line);
deuce's avatar
deuce committed
}


/**************/
/* Socket I/O */
/**************/

deuce's avatar
deuce committed
function tagged(tag, msg, desc)
{
	client.socket.send(tag+" "+msg+" "+desc+"\r\n");
	debug_log("Send: "+tag+" "+msg+" "+desc, false);
deuce's avatar
deuce committed
}

function untagged(msg)
{
	client.socket.send("* "+msg+"\r\n");
	debug_log("Send: * "+msg.length+": "+msg, false);
deuce's avatar
deuce committed
}

/*************************************************************/
/* Fetch response generation... this is the tricky bit.  :-) */
/*************************************************************/
function send_fetch_response(msgnum, fmat, uid)
	var resp='';
	var sent_uid=false;
	var i,j;
	var hdr;
	var rfc822={};
	var objtype;
	var m;
	var re;
	var tmp;
	var sent_flags=false;
	var seen_changed=false;
	var envelope;
deuce's avatar
deuce committed
	var tmp2;
	/*
	 * Most of these functions just diddle variables in this function
	 */
	function get_header() {
		if(hdr == undefined)
			hdr=base.get_msg_header(msgnum, /* expand_fields: */false);
		/* If that didn't work, make up a minimal useless header */
		if (hdr == undefined) {
			hdr = Object.create(MsgBase.HeaderPrototype);
			hdr.netattr = 0;
			hdr.when_imprted_time = 0;
			hdr.when_imported_zone_offset = 0;
			hdr.from="deleted@example.com";
			hdr.to="deleted@example.com";
			hdr.id="<DELETED>";
			hdr.subject="<DELETED>";
			hdr.date="<undefined>";
	}

	function get_rfc822_header() {
		get_header();
deuce's avatar
deuce committed
		rfc822.header=hdr.get_rfc822_header();
	}

	function get_rfc822_text() {
		if(rfc822.text==undefined)
			rfc822.text=base.get_msg_body(msgnum, true, true, true);
		if(rfc822.text==undefined)
			rfc822.text='';
	}

	function get_rfc822() {
		get_rfc822_header();
		get_rfc822_text();
	}

deuce's avatar
deuce committed
	function get_mime() {
		if(mime==undefined) {
			get_rfc822();
			mime=parse_message(rfc822.header+rfc822.text);
		}
	}

	function get_rfc822_size() {
		get_rfc822();
		rfc822.size=rfc822.header.length+rfc822.text.length;
	}

deuce's avatar
deuce committed
	 * Sets the seen flag on a message
	function set_seen_flag() {
		if(readonly)
			return;
		if(base.subnum==-1) {
			get_header();
			if(!(hdr.attr & MSG_READ)) {
				hdr.attr |= MSG_READ;
				index=read_index(base);
				hdr=base.get_msg_header(msgnum, /* expand_fields: */false);
				if(hdr.attr & MSG_READ)
					seen_changed=true;
			}
		}
			lock_cfg();
			read_cfg(index.code, false);
deuce's avatar
deuce committed
			if(saved_config[index.code] == undefined)
				saved_config[index.code] = {};
			if(saved_config[index.code].Seen == undefined)
				saved_config[index.code].Seen = {};
			if(saved_config[index.code].Seen[msgnum] != 1)
				seen_changed=true;
deuce's avatar
deuce committed
			saved_config[index.code].Seen[msgnum]=1;
			apply_seen(index);
	}

	// Moves flags to the end...
	function sort_format(a,b)
	{
		if(typeof(a)=='object')
			a=0;
		else {
			if(a.substr(0,5).toUpperCase()=='FLAGS')
				a=100;
			else
				a=0;
		}
		if(typeof(b)=='object')
			b=0;
		else {
			if(b.substr(0,5).toUpperCase()=='FLAGS')
				b=100;
			else
				b=0;
		}

		return a-b;
	}

deuce's avatar
deuce committed
	function get_mime_part(fmat) {
		var m=fmat.match(/^BODY((?:\.PEEK)?)\[([^[\]]*)(?:\]\<([0-9]+)\.([0-9]+)\>)?/i);
		var specifiers;
		var i;
		var tmp;
		var part_name='';

		function encode_binary_part(start, len, str)
		{
			if(start==undefined || start=='')
				start=0;
			else
				start=parseInt(start,10);
			if(len==undefined || len=='')
				len=str.length;
			else
				len=parseInt(len,10);
			return(encode_binary(str.substr(start,len)));
		}

		part=mime;
		if(m==null)
			return(undefined);
		if(m[1].toUpperCase()!='.PEEK')
			set_seen_flag();
		part_name='BODY['+m[2]+']';
		specifiers=m[2].split('.');
		for(i=0; i<specifiers.length; i++) {
			tmp=parseInt(specifiers[i], 10);
			if(tmp > 0) {
				if(part.mime != undefined && part.mime.parts != undefined && part.mime.parts[tmp-1]!=undefined) {
					part=part.mime.parts[tmp-1];
				}
			}
			else
				break;
		}
		if(m[3]!=undefined && m[3]!='')
			part_name += '<'+m[3]+'>';
		switch(specifiers[i]) {
			case 'HEADER':
				if(specifiers[i+1]!=undefined) {
					objtype='BODY['+specifiers.join('.');
					return undefined;
				}
				else
					return(part_name+" "+encode_binary_part(m[3],m[4],part.headers['::'].join('')+"\r\n")+' ');
			case 'MIME':
				return(part_name+" "+encode_binary_part(m[3],m[4],part.headers[':mime:'].join('')+"\r\n")+' ');
			case '':
				if(specifiers.length==1)
					return(part_name+" "+encode_binary_part(m[3],m[4],part.headers['::'].join('')+'\r\n'+part.text)+' ');
				// Fall-through
			case undefined:
			case 'TEXT':
				return(part_name+' '+encode_binary_part(m[3],m[4],part.text)+' ');
		}
	}

	function add_part(mime) {
		var i;
		var ret='(NIL) ';
deuce's avatar
deuce committed

		if (mime.mime.parsed == undefined) {
			log(LOG_WARNING, "MIME part was not actually parsed!");
			return '';
		}
deuce's avatar
deuce committed
		if(mime.mime.parts != undefined) {
			for(i in mime.mime.parts)
				ret += add_part(mime.mime.parts[i]);
		}
		else
			ret += encode_string(mime.mime.parsed['content-type'].vals[0])+" ";

		ret += encode_string(mime.mime.parsed['content-type'].vals[1])+" ";
		if(mime.mime.parsed['content-type'].attrs==undefined)
			ret += 'NIL ';
		else {
			ret += '(';
			for(i in mime.mime.parsed['content-type'].attrs) {
				ret += encode_string(i)+" ";
				ret += encode_string(mime.mime.parsed['content-type'].attrs[i])+" ";
			}
			ret=ret.replace(/ $/, ") ");
		}
		if(mime.mime.parsed['content-id']==undefined)
			ret += 'NIL ';
		else
			ret += encode_string(mime.mime.parsed['content-id'].vals[0])+' ';
		if(mime.mime.parsed['content-description']==undefined)
			ret += 'NIL ';
		else
			ret += encode_string(mime.mime.parsed['content-description'].vals[0])+' ';

		if(mime.mime.parsed['content-type'].vals[0]!='multipart') {
			if(mime.mime.parsed['content-transfer-encoding']==undefined)
				ret += 'NIL ';
			else
				ret += encode_string(mime.mime.parsed['content-transfer-encoding'].vals[0])+' ';

			ret += encode_token(mime.text.length.toString())+' ';
		}

		if(mime.mime.parsed['content-type'].vals[0]=='text' || mime.mime.parsed['content-type'].vals[0]=='message') {
			i=mime.text.split(/\x0d\x0a/);
			ret=ret+encode_token(i.length.toString())+' ';
		}
		
		if(extension) {
			// TODO Add extension data...
		}
		
		ret=ret.replace(/ $/, ') ');
		return ret;
	}

	idx=index.idx[msgnum];
	resp=idx.offset;
	resp += " FETCH (";
	fmat=fmat.sort(sort_format);

		/*
		 * This bit is for when a paremeter includes a list.
		 * This list will be an object, so we need special handling
		 */
		if(typeof(fmat[i])=='object') {
			// We already handled this I hope...
			if(objtype == undefined)
				continue;
deuce's avatar
deuce committed

			if(objtype.search(/^BODY\[[0-9.]*HEADER\.FIELDS$/i)==0) {
				tmp='';
				for(j in fmat[i]) {
					if(part.headers[fmat[i][j].toLowerCase()]!=undefined)
						tmp += part.headers[fmat[i][j].toLowerCase()];
				}

				resp += objtype+" ("+fmat[i].join(" ")+")] "+encode_binary(tmp+"\r\n")+" ";
			}
			if(objtype.search(/^BODY\[[0-9.]*HEADER\.FIELDS\.NOT$/i)==0) {
				tmp=eval(part.headers.toSource());
				delete tmp['::'];
				delete tmp[':mime:'];
				for(j in fmat[i]) {
					if(tmp[fmat[i][j].toLowerCase()]!=undefined)
						delete tmp[fmat[i][j].toLowerCase()];
				}
				tmp2='';
				for(j in tmp)
					tmp2 += tmp[j];

				resp += objtype+" ("+fmat[i].join(" ")+")] "+encode_binary(tmp2+"\r\n")+" ";
		if(fmat[i].toUpperCase().substr(0,4)=='BODY') {
			get_mime();

			if((tmp=get_mime_part(fmat[i].toUpperCase()))==undefined) {
				switch(fmat[i].toUpperCase()) {
					case 'BODY':
					case 'BODYSTRUCTURE':
						resp += 'BODYSTRUCTURE '+add_part(mime);
						break;
				}
			switch(fmat[i].toUpperCase()) {
					resp += "FLAGS ("+calc_msgflags(idx.attr, hdr.netattr, base.subnum, msgnum, readonly)+") ";
					sent_flags=true;
					break;
				case 'UID':
					resp += "UID "+idx.number+" ";
					sent_uid=true;
					break;
				case 'INTERNALDATE':
					get_header();
					resp += 'INTERNALDATE '+strftime('"%d-%b-%Y %H:%M:%S ', hdr.when_imported_time)+format('%+05d" ', hdr.when_imported_zone_offset);
					break;
				case 'RFC822.SIZE':
					get_rfc822_size();
					resp += "RFC822.SIZE "+rfc822.size+" ";
					break;
				case 'RFC822.TEXT':
					set_seen_flag();
					get_rfc822_text();
					resp += fmat[i].replace(/\.PEEK/i,"").toUpperCase()+" "+encode_binary(rfc822.text)+" ";
					break;
				case 'RFC822.HEADER':
					set_seen_flag();
					get_rfc822_header();
					resp += fmat[i].replace(/\.PEEK/i,"").toUpperCase()+" "+encode_binary(rfc822.header)+" ";
					break;
				case 'RFC822':
					set_seen_flag();
					get_rfc822();
					resp += fmat[i].replace(/\.PEEK/i,"").toUpperCase()+" "+encode_binary(rfc822.header+rfc822.text)+" ";
					break;
				case 'ENVELOPE':
					set_seen_flag();
					get_header();
					resp += 'ENVELOPE ('+hdr.get_envelope().join(" ")+') ';
	if(seen_changed && !sent_flags) {
		get_header();
		resp += "FLAGS ("+calc_msgflags(idx.attr, hdr.netattr, base.subnum, msgnum, readonly)+") ";
	if(uid && !sent_uid)
		resp += "UID "+idx.number+" ";
	resp=resp.replace(/ $/,'');
	resp += ")";
	untagged(resp);
	js.gc(false);
/*
 * Parses a data items FETCH parameter for send_fetch_response()
 */
function parse_data_items(obj)
{
	if(typeof(obj)=='string') {
		switch(obj.toUpperCase()) {
			case 'ALL':
				obj=["FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE"];
				break;
			case 'FAST':
				obj=["FLAGS","INTERNALDATE","RFC822.SIZE"];
				break;
			case 'FULL':
				obj=["FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE","BODY"];
				break;
			default:
				obj=[];
		}
	}
	return(obj);
}

/*
 * Returns an array of Message Numbers which correspond to the specified sets
 */
function parse_seq_set(set, uid) {
	var response=[];
	var chunks=set.split(/,/);
	var chunk;
	var range;
	var i;
	var max;
	var idx;
	for(chunk in chunks) {
		range=chunks[chunk].split(/:/);
		if(range.length == 1)
			range.push(range[0]);
		if(range[0]=='*')
			range[0]=max;
		if(range[1]=='*')
			range[1]=max;
		range[0]=parseInt(range[0],10);
		range[1]=parseInt(range[1],10);
		if(range[0] > range[1]) {
			i=range[0];
			range[0]=range[1];
			range[1]=i;
		}
		for(i=range[0]; i<=range[1]; i++) {
			msgnum=parseInt(i, 10);
			if(!uid)
				msgnum=index.offsets[msgnum-1];
			if(msgnum==undefined || index.idx[msgnum]==undefined)
				continue;
			response.push(msgnum);
deuce's avatar
deuce committed
		}
	response=response.sort(function(a,b) { return a-b; });
	for(i=0; i<response.length; i++) {
		if(response[i]==response[i+1]) {
			response.splice(i+1,1);
			i--;
		}
	}
	return(response);
}

function handle_command(command, args, defs)
{
	if(defs[command] != undefined) {
		if(defs[command].arguments != undefined) {
			if(args.length-1 == defs[command].arguments) {
				defs[command].handler(args);
				return(true);
			}
		}
		else if(defs[command].arguments_valid(args.length-1)) {
			defs[command].handler(args);
			return(true);
		}
	}
	return false;
}

function parse_command(line)
{
	function execute_line(args) {
		if(args.length >= 2) {
			command=args[1].toUpperCase();
			args.splice(1,1);
			if(handle_command(command, args, any_state_command_handlers))
				return;
			switch(state) {
				case UnAuthenticated:
					if(handle_command(command, args, unauthenticated_command_handlers))
						return;
					break;
				case Authenticated:
					if(handle_command(command, args, authenticated_command_handlers))
						return;
					break;
				case Selected:
					if(handle_command(command, args, selected_command_handlers))
						return;
					if(handle_command(command, args, authenticated_command_handlers))
						return;
deuce's avatar
deuce committed
		}
		// Ignore empty lines (Seamonkey sends these...)
		if (args.length > 0)
			tagged(args[0], "BAD", "Bad dog, no cookie.");
deuce's avatar
deuce committed
	}

	function parse_line() {
		var at_start=true;
		var	in_quote=false;
		var paren_depth=0;
		var string_len;
		var args=[];
		var pos;

		function parse_atom() {
			var ret='';

			while(line.length) {
				switch(line.charAt(0)) {
					case ')':
						return(ret);
					case ' ':
						line=line.substr(1);
						return(ret);
					default:
						ret += line.charAt(0);
						line=line.substr(1);
						break;
				}
			}
			return(ret);
		}
		function parse_string()
		{
			var ret='';
Deucе's avatar
Deucе committed
			if (line.search(/^{([0-9]+)}$/) !== 0)
Deucе's avatar
Deucе committed
				throw new Error('invalid string literal ('+line+'), aborting');
			line=line.replace(/^{([0-9]+)}$/, "$1");
			client.socket.send("+ Give me more of that good stuff\r\n");
			var len = parseInt(line);
			if(len) {
				ret=client.socket.recv(len);
				line=client.socket.recvline(10240, 1800);
			}
		function parse_quotedstring() {
			var ret='';
	
			line=line.substr(1);	// Remove leading "
			while(line.length) {
				switch(line.charAt(0)) {
					case '"':
						line=line.substr(1);
						return(ret);
					default:
						ret += line.charAt(0);
						line=line.substr(1);
						break;
				}
			}
			return(ret);
		}
		while(line) {
			switch(line.charAt(0)) {
				case '"':
					args.push(parse_quotedstring());
					break;
				case ')':
					return(args);
				case '(':
					line=line.substr(1);
					args.push(parse_line());
					break;
				case '{':
					args.push(parse_string());
					break;
				case ' ':
					line=line.substr(1);
					break;
				default:
					args.push(parse_atom());
					break;
deuce's avatar
deuce committed
		}
		return(args);
deuce's avatar
deuce committed
	}
	return(execute_line(parse_line()));
deuce's avatar
deuce committed
}

// Command handling functions
deuce's avatar
deuce committed
var any_state_command_handlers = {
deuce's avatar
deuce committed
	CAPABILITY:{
		arguments:0,
		handler:function (args) {
			var tag=args[0];
			if (client.socket.ssl_session)
				untagged("CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 AUTH=PLAIN CHILDREN IDLE UNSELECT");
			else
				untagged("CAPABILITY IMAP4rev1 AUTH=CRAM-MD5 LOGINDISABLED CHILDREN IDLE UNSELECT");
			tagged(tag, "OK", "Capability completed, no STARTTLS support... deal with it.");
deuce's avatar
deuce committed
		},
	},
	NOOP:{
		arguments:0,
		handler:function(args) {
			var tag=args[0];

			tagged(tag, "OK", "No operation performed... anything else you'd like me to not do?");
		},
	},
	LOGOUT:{
		arguments:0,
		handler:function(args) {
			var tag=args[0];
deuce's avatar
deuce committed
			untagged("BYE I'll go now");
			tagged(tag, "OK", "Thanks for stopping by.");
deuce's avatar
deuce committed
		}
	},
	IDLE:{	// RFC2177
		arguments:0,
		handler:function(args) {
			var tag=args[0];
			var elapsed=0;

			client.socket.send("+ Ooo, Idling... my favorite.\r\n");
			while(1) {
				line=client.socket.recvline(10240, 5);
					if (js.termianted) {
						untagged("BYE server terminated.");
						exit(0);
					}
					if(elapsed > 1800) {
						untagged("BYE And I though *I* liked to idle!");
						exit(0);
					}
					update_status();
				}
				else {
					debug_log("DONE IDLE: '"+line+"'", true);
					tagged(tag, "OK", "That was fun.");
					return;
				}
deuce's avatar
deuce committed
};

deuce's avatar
deuce committed
var unauthenticated_command_handlers = {
deuce's avatar
deuce committed
	STARTTLS:{
		arguments:0,
		handler:function(args) {
			var tag=args[0];

			tagged(tag, "BAD", "I told you to deal with the lack of STARTTLS damnit!");
deuce's avatar
deuce committed
		},
	},
	AUTHENTICATE:{
		arguments:1,
		handler:function(args) {
			var tag=args[0];
			var mechanism=args[1];
			var line;
			var args;
			var challenge;
			var un;
			var u;

			function hmac(k, text) {
				var ik='', ok='', i;
				var m;

				if (k.length > 64)
					k = base64_decode(md5_calc(k));
				while (k.length < 64)
					k = k + '\x00';
				for (i=0; i<64; i++) {
					ik += ascii(ascii(k[i]) ^ 0x36);
					ok += ascii(ascii(k[i]) ^ 0x5C);
				}
				return md5_calc(ok + base64_decode(md5_calc(ik+text)), true);
			}

			function setcfg(u)
				cfgfile=new File(format(system.data_dir+"user/%04d.imap", u.number));
				if (!cfgfile.open(cfgfile.exists ? 'r+':'w+', true, 0)) {
					tagged(tag, "NO", "Can't open imap state file");
					return;
				}
			}
deuce's avatar
deuce committed

			if(mechanism.toUpperCase()=="PLAIN") {
				if (!client.socket.ssl_session) {
					tagged(tag, "NO", "No AUTH for you.");
					return;
				}
deuce's avatar
deuce committed
				client.socket.send("+\r\n");
				line=client.socket.recvline(10240, 1800);
deuce's avatar
deuce committed
				args=base64_decode(line).split(/\x00/);
				if(!login(args[1],args[2])) {
//					log(LOG_INFO, format("Attempted login: '%s', pw: '%s'", args[1], args[2]));
deuce's avatar
deuce committed
					tagged(tag, "NO", "No AUTH for you.");
					return;
				}
				setcfg(system.matchuser(args[1], false));
deuce's avatar
deuce committed
				tagged(tag, "OK", "Howdy.");
				state=Authenticated;
			}
			else if(mechanism.toUpperCase() == "CRAM-MD5") {
				challenge = '<'+random(2147483647)+"."+time()+"@"+system.host_name+'>';
				client.socket.send("+ "+base64_encode(challenge)+"\r\n");
				line=client.socket.recvline(10240, 1800);
				args=base64_decode(line).split(/ /);
				un = system.matchuser(args[0], false);
				if (un == 0) {
					tagged(tag, "NO", "No AUTH for you.");
					return;
				}
				u = new User(un);
				if (u.number < 1) {
					tagged(tag, "NO", "No AUTH for you.");
					return;
				}
				// First, try as-stored...
				if (args[1] === hmac(u.security.password, challenge)) {
					setcfg(u);
					login(u.alias, u.security.password);
					tagged(tag, "OK", "Howdy.");
					state=Authenticated;
					return;
				}
				// Lower-case
				if (args[1] === hmac(u.security.password.toLowerCase(), challenge)) {
					setcfg(u);
					login(u.alias, u.security.password);
					tagged(tag, "OK", "Howdy.");
					state=Authenticated;
					return;
				}
				// Upper-case
				if (args[1] === hmac(u.security.password.toUpperCase(), challenge)) {
					setcfg(u);
					login(u.alias, u.security.password);
					tagged(tag, "OK", "Howdy.");
					state=Authenticated;
					return;
				}
				tagged(tag, "NO", "No AUTH for you.");
				return;
			}
deuce's avatar
deuce committed
			else
				tagged(tag, "NO", "No "+mechanism+" authenticate supported yet... give me a reason to do it and I'll think about it.");
		},
	},
	LOGIN:{
		arguments:2,
		handler:function(args) {
			var tag=args[0];
			var usr=args[1];
			var pass=args[2];
			var u;
deuce's avatar
deuce committed

			if (!client.socket.ssl_session) {
deuce's avatar
deuce committed
				tagged(tag, "NO", "Basic RFC stuff here! A client implementation MUST NOT send a LOGIN command if the LOGINDISABLED capability is advertised.");
deuce's avatar
deuce committed
			if(!login(usr, pass)) {
				tagged(tag, "NO", "No login for you.");
				return;
			}
			u = system.matchuser(usr, false);
			cfgfile=new File(format(system.data_dir+"user/%04d.imap", u.number));
			if (!cfgfile.open(cfgfile.exists ? 'r+':'w+', true, 0)) {
				tagged(tag, "NO", "Can't open imap state file");
				return;
			}
deuce's avatar
deuce committed
			tagged(tag, "OK", "Sure, come on in.");
			state=Authenticated;
		},
	}
};

function sendflags(perm)
{
	flags=calc_msgflags(0xffff, 0xffff, base.subnum, base.last_msg, readonly);
	pflags=calc_msgflags(0xffff, 0xffff, base.subnum, base.last_msg, readonly);
deuce's avatar
deuce committed
	if(perm)
		untagged("OK [PERMANENTFLAGS ("+pflags+")] Overkill!");
deuce's avatar
deuce committed
	else
		untagged("FLAGS ("+flags+") Overkill!");
}

function parse_flags(inflags)
{
	var i;
	var flags={attr:0, netattr:0};

	for(i in inflags) {
		switch(inflags[i].toUpperCase()) {
			case '\\SEEN':
				flags.attr |= MSG_READ;
				break;
			case '\\ANSWERED':
			case 'REPLIED':
				flags.attr |= MSG_REPLIED;
				break;
			case '\\FLAGGED':
			case 'VALIDATED':
				flags.attr |= MSG_VALIDATED;
				break;