Skip to content
Snippets Groups Projects
mailsrvr.c 209 KiB
Newer Older
                case 'G':   /* Temp directory */
                    strcat(cmd,scfg.temp_dir);
                    break;
                case 'J':
                    strcat(cmd,scfg.data_dir);
                    break;
                case 'K':
                    strcat(cmd,scfg.ctrl_dir);
                    break;
				case 'L':
					strcat(cmd,lstpath);
					break;
				case 'F':
				case 'M':
					strcat(cmd,msgpath);
					break;
                case 'O':   /* SysOp */
                    strcat(cmd,scfg.sys_op);
                    break;
                case 'Q':   /* QWK ID */
                    strcat(cmd,scfg.sys_id);
                    break;
				case 'R':	/* reverse path */
					strcat(cmd,reverse_path);
					break;
				case 'S':	/* sender name */
				case 'T':	/* recipient */
					strcat(cmd,rcpt_addr);
					break;
				case 'A':	/* sender address */
					strcat(cmd,sender_addr);
					break;
                    SAFEPRINTF2(str,"%s%c",VERSION,REVISION);
                    break;
                case 'Z':
                    strcat(cmd,scfg.text_dir);
                    break;
                case '!':   /* EXEC Directory */
                    strcat(cmd,scfg.exec_dir);
                    break;
                case '@':   /* EXEC Directory for DOS/OS2/Win32, blank for Unix */
#ifndef __unix__
                    strcat(cmd,scfg.exec_dir);
#endif
                    break;
                case '%':   /* %% for percent sign */
                    strcat(cmd,"%");
                    break;
				case '?':	/* Platform */
#ifdef __OS2__
					SAFECOPY(str,"OS2");
#else
					SAFECOPY(str,PLATFORM_DESC);
#endif
					strlwr(str);
					strcat(cmd,str);
					break;
					SAFEPRINTF(str,"%u",usernum);
                default:    /* unknown specification */
                    break; 
			}
            j=strlen(cmd); 
		}
        else
            cmd[j++]=instr[i]; 
	}
    cmd[j]=0;

    return(cmd);
}
rswindell's avatar
rswindell committed
typedef struct {
	SOCKET		sock;
	const char*	log_prefix;
	const char*	proc_name;
} private_t;

static void
js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
	char	line[64];
	char	file[MAX_PATH+1];
	char*	warning;
rswindell's avatar
rswindell committed
	private_t*	p;
rswindell's avatar
rswindell committed
	if((p=(private_t*)JS_GetContextPrivate(cx))==NULL)
rswindell's avatar
rswindell committed
		lprintf(LOG_ERR,"%04d %s %s !JavaScript: %s"
			, p->sock, p->log_prefix, p->proc_name, message);
		SAFEPRINTF(file," %s",report->filename);
		SAFEPRINTF(line," line %u",report->lineno);
	else
		line[0]=0;

	if(JSREPORT_IS_WARNING(report->flags)) {
		if(JSREPORT_IS_STRICT(report->flags))
			warning="strict warning";
		else
			warning="warning";
		log_level=LOG_WARNING;
	} else {
		log_level=LOG_ERR;
	lprintf(log_level,"%04d %s %s !JavaScript %s%s%s: %s"
rswindell's avatar
rswindell committed
		,p->sock, p->log_prefix, p->proc_name
		,warning ,file, line, message);
js_log(JSContext *cx, uintN argc, jsval *arglist)
	jsval *argv=JS_ARGV(cx, arglist);
rswindell's avatar
rswindell committed
	private_t*	p;
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

rswindell's avatar
rswindell committed
	if((p=(private_t*)JS_GetContextPrivate(cx))==NULL)
	if(JSVAL_IS_NUMBER(argv[i])) {
		if(!JS_ValueToInt32(cx,argv[i++],&level))
			return JS_FALSE;
	}
		JSVALUE_TO_RASTRING(cx, argv[i], lstr, &lstr_sz, NULL);
		HANDLE_PENDING(cx, lstr);
deuce's avatar
deuce committed
		if(lstr==NULL)
		lprintf(level,"%04d %s %s %s"
deuce's avatar
deuce committed
			,p->sock,p->log_prefix,p->proc_name,lstr);
		JS_SET_RVAL(cx, arglist, argv[i]);
	if(lstr)
		free(lstr);
static JSBool
js_alert(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval *argv=JS_ARGV(cx, arglist);
	private_t*	p;
	jsrefcount	rc;
	char		*line;

	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

	if((p=(private_t*)JS_GetContextPrivate(cx))==NULL)
		return(JS_FALSE);

	JSVALUE_TO_MSTRING(cx, argv[0], line, NULL);
	if(line==NULL)
	    return(JS_FALSE);

	rc=JS_SUSPENDREQUEST(cx);
	lprintf(LOG_ERR,"%04d %s %s %s"
		,p->sock, p->log_prefix, p->proc_name, line);
	free(line);
	JS_RESUMEREQUEST(cx, rc);

	JS_SET_RVAL(cx, arglist, argv[0]);

    return(JS_TRUE);
}


static JSFunctionSpec js_global_functions[] = {
	{"write",			js_log,				0},
	{"writeln",			js_log,				0},
	{"print",			js_log,				0},
	{"log",				js_log,				0},
rswindell's avatar
rswindell committed
js_mailproc(SOCKET sock, client_t* client, user_t* user, struct mailproc* mailproc
			,char* msgtxt_fname, char* newtxt_fname, char* logtxt_fname
			,char* rcptlst_fname, char* proc_err_fname
			,char* sender, char* sender_addr, char* reverse_path, char* hello_name
rswindell's avatar
rswindell committed
			,JSRuntime**	js_runtime
			,JSContext**	js_cx
			,JSObject**		js_glob
			,const char*	log_prefix
)
	char		path[MAX_PATH+1];
	char		arg[MAX_PATH+1];
	BOOL		success=FALSE;
rswindell's avatar
rswindell committed
	JSObject*	js_scope=NULL;
rswindell's avatar
rswindell committed
	private_t	priv;

	SAFECOPY(fname,cmdline);
	truncstr(fname," \t");
	if(getfext(fname)==NULL) /* No extension specified, assume '.js' */
		strcat(fname,".js");

	SAFECOPY(path,fname);
	if(getfname(path)==path) { /* No path specified, assume mods or exec dir */
		SAFEPRINTF2(path,"%s%s",scfg.mods_dir,fname);
		if(scfg.mods_dir[0]==0 || !fexist(path))
			SAFEPRINTF2(path,"%s%s",scfg.exec_dir,fname);
rswindell's avatar
rswindell committed
		if(*js_runtime==NULL) {
			lprintf(LOG_DEBUG,"%04d %s JavaScript: Creating runtime: %lu bytes"
rswindell's avatar
rswindell committed
				,sock, log_prefix, startup->js.max_bytes);
rswindell's avatar
rswindell committed
			if((*js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL)
rswindell's avatar
rswindell committed
		}
rswindell's avatar
rswindell committed
		if(*js_cx==NULL) {
			if((*js_cx = JS_NewContext(*js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
			JS_SetOptions(*js_cx, startup->js.options);
rswindell's avatar
rswindell committed
		}
		JS_BEGINREQUEST(*js_cx);

		JS_SetErrorReporter(*js_cx, js_ErrorReporter);

		priv.sock=sock;
		priv.log_prefix=log_prefix;
		priv.proc_name=mailproc->name;
		JS_SetContextPrivate(*js_cx, &priv);

		if(*js_glob==NULL) {
			/* Global Objects (including system, js, client, Socket, MsgBase, File, User, etc. */
			if(!js_CreateCommonObjects(*js_cx, &scfg, &scfg, NULL
						,uptime, server_host_name(), SOCKLIB_DESC	/* system */
						,&startup->js
rswindell's avatar
rswindell committed
						,&js_server_props							/* server */
rswindell's avatar
rswindell committed
				break;
rswindell's avatar
rswindell committed
			if(!JS_DefineFunctions(*js_cx, *js_glob, js_global_functions))
				break;
rswindell's avatar
rswindell committed
			/* Area and "user" Objects */
rswindell's avatar
rswindell committed
			if(!js_CreateUserObjects(*js_cx, *js_glob, &scfg, user, client, NULL, NULL)) 
rswindell's avatar
rswindell committed
				break;
rswindell's avatar
rswindell committed
			/* Mailproc "API" filenames */
			JS_DefineProperty(*js_cx, *js_glob, "message_text_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,msgtxt_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "new_message_text_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,newtxt_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "log_text_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,logtxt_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "recipient_address"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,rcpt_addr))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "recipient_list_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,rcptlst_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "processing_error_filename"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,proc_err_fname))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "sender_name"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,sender))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "sender_address"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,sender_addr))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
rswindell's avatar
rswindell committed
			JS_DefineProperty(*js_cx, *js_glob, "reverse_path"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,reverse_path))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

			JS_DefineProperty(*js_cx, *js_glob, "hello_name"
				,STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,hello_name))
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

		}

		if((js_scope=JS_NewObject(*js_cx, NULL, NULL, *js_glob))==NULL)
			break;

		/* Convert command-line to argv/argc */
rswindell's avatar
rswindell committed
		argv=JS_NewArrayObject(*js_cx, 0, NULL);
		JS_DefineProperty(*js_cx, js_scope, "argv", OBJECT_TO_JSVAL(argv)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

		p=cmdline;
		FIND_WHITESPACE(p); 
		SKIP_WHITESPACE(p);
		for(argc=0;*p;argc++) {
			SAFECOPY(arg,p);
			truncstr(arg," \t");
rswindell's avatar
rswindell committed
			val=STRING_TO_JSVAL(JS_NewStringCopyZ(*js_cx,arg));
			if(!JS_SetElement(*js_cx, argv, argc, &val))
				break;
			FIND_WHITESPACE(p);
			SKIP_WHITESPACE(p);
		}
rswindell's avatar
rswindell committed
		JS_DefineProperty(*js_cx, js_scope, "argc", INT_TO_JSVAL(argc)
			,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

deuce's avatar
deuce committed
		if(*mailproc->eval!=0) {
			lprintf(LOG_DEBUG,"%04d %s Evaluating: %s"
rswindell's avatar
rswindell committed
				,sock, log_prefix, mailproc->eval);
			js_script=JS_CompileScript(*js_cx, js_scope, mailproc->eval, strlen(mailproc->eval), NULL, 1);
		} else {
			lprintf(LOG_DEBUG,"%04d %s Executing: %s"
rswindell's avatar
rswindell committed
				,sock, log_prefix, cmdline);
			if((js_script=JS_CompileFile(*js_cx, js_scope, path)) != NULL)
				js_PrepareToExecute(*js_cx, js_scope, path, /* startup_dir: */NULL, js_scope);
rswindell's avatar
rswindell committed
		}
		if(js_script==NULL)
			break;
		/* ToDo: Set operational callback */
rswindell's avatar
rswindell committed
		success=JS_ExecuteScript(*js_cx, js_scope, js_script, &rval);
		if(JS_GetProperty(*js_cx, js_scope, "exit_code", &rval)
			&& rval!=JSVAL_VOID && JSVAL_IS_NUMBER(rval))
			JS_ValueToInt32(*js_cx,rval,result);
		js_EvalOnExit(*js_cx, js_scope, &js_callback);
		JS_ReportPendingException(*js_cx);
rswindell's avatar
rswindell committed
		JS_ClearScope(*js_cx, js_scope);
rswindell's avatar
rswindell committed
		JS_GC(*js_cx);
rswindell's avatar
rswindell committed

void js_cleanup(JSRuntime* js_runtime, JSContext* js_cx, JSObject** js_glob)
rswindell's avatar
rswindell committed
{
	if(js_cx!=NULL) {
		JS_BEGINREQUEST(js_cx);
		JS_RemoveObjectRoot(js_cx, js_glob);
		JS_ENDREQUEST(js_cx);
rswindell's avatar
rswindell committed
		JS_DestroyContext(js_cx);
rswindell's avatar
rswindell committed
	if(js_runtime!=NULL)
		jsrt_Release(js_runtime);
}
static size_t strStartsWith_i(const char* buf, const char* match)
{
	size_t len = strlen(match);
	if (strnicmp(buf, match, len) == 0)
		return len;
	return 0;
}

/* Decode RFC2047 'Q' encoded-word (in-place), "similar to" Quoted-printable */
char* mimehdr_q_decode(char* buf)
{
	uchar*	p=(uchar*)buf;
	uchar*	dest=p;

	for(;*p != 0; p++) {
		if(*p == '=') {
			p++;
			if(IS_HEXDIGIT(*p) && IS_HEXDIGIT(*(p + 1))) {
				uchar ch = HEX_CHAR_TO_INT(*p) << 4;
				p++;
				ch |= HEX_CHAR_TO_INT(*p);
				if(ch >= ' ')
					*dest++ = ch;
			} else {	/* bad encoding */
				*dest++ = '=';
				*dest++ = *p;
			}
		}
		else if(*p == '_')
			*dest++ = ' ';
		else if(*p >= '!' && *p <= '~')
			*dest++ = *p;
	}
	*dest=0;
	return buf;
}

enum mimehdr_charset {
	MIMEHDR_CHARSET_ASCII,
	MIMEHDR_CHARSET_UTF8,
	MIMEHDR_CHARSET_CP437,
	MIMEHDR_CHARSET_OTHER
};

static enum mimehdr_charset mimehdr_charset_decode(const char* str)
{
	if (strStartsWith_i(str, "ascii?") || strStartsWith_i(str, "us-ascii?"))
		return MIMEHDR_CHARSET_ASCII;
	if (strStartsWith_i(str, "utf-8?"))
		return MIMEHDR_CHARSET_UTF8;
	if (strStartsWith_i(str, "cp437?") || strStartsWith_i(str, "ibm437?"))
		return MIMEHDR_CHARSET_CP437;
	return MIMEHDR_CHARSET_OTHER;
}

// Replace MIME (RFC 2047) "encoded-words" with their decoded-values
// Returns true if the value was MIME-encoded
bool mimehdr_value_decode(char* str, smbmsg_t* msg)
	if (str == NULL)
		return false;
	char* buf = strdup(str);
	if (buf == NULL)
		return false;
	char* state = NULL;
	*str = 0;
	char tmp[256]; // "An 'encoded-word' may not be more than 75 characters long"
	for(char* p = strtok_r(buf, " \t", &state); p != NULL; p = strtok_r(NULL, " \t", &state)) {
		char* end = lastchar(p);
		if(*p == '=' && *(p+1) == '?' && *(end - 1) == '?' && *end == '=' && end - p < sizeof(tmp)) {
			if(*str && !encoded_word)
				strcat(str, " ");
			char* cp = p + 2;
			enum mimehdr_charset charset = mimehdr_charset_decode(cp);
			if(*cp == '?' && *(cp + 1) != 0 && *(cp + 2) == '?') {
				cp++;
				char encoding = toupper(*cp);
				cp += 2;
				SAFECOPY(tmp, cp);
				char* tp = lastchar(tmp);
				*(tp - 1) = 0;	// remove the terminating "?="
				if(encoding == 'Q') {
					mimehdr_q_decode(tmp);
					if(charset == MIMEHDR_CHARSET_UTF8 && !str_is_ascii(tmp) && utf8_str_is_valid(tmp))
						msg->hdr.auxattr |= MSG_HFIELDS_UTF8;
					// TODO consider converting other 8-bit charsets (e.g. CP437, ISO-8859-1) to UTF-8 here
					p = tmp;
				}
				else if(encoding == 'B' 
					&& b64_decode(tmp, sizeof(tmp), tmp, strlen(tmp)) > 0) { // base64
					if(charset == MIMEHDR_CHARSET_UTF8 && !str_is_ascii(tmp) && utf8_str_is_valid(tmp))
						msg->hdr.auxattr |= MSG_HFIELDS_UTF8;
					p = tmp;
		} else {
			if(*str)
				strcat(str, " ");
			encoded_word = false;
static char* get_header_field(char* buf, char* name, size_t maxlen)
	size_t	len;

	if(buf[0]<=' ')	/* folded header */
		return NULL;

	if((p=strchr(buf,':'))==NULL)
		return NULL;

	len = p-buf;
deuce's avatar
deuce committed
	sprintf(name,"%.*s",(int)len,buf);
	truncsp(name);

	p++;	/* skip colon */
	SKIP_WHITESPACE(p);
	return p;
}
static int parse_header_field(char* buf, smbmsg_t* msg, ushort* type)
	if(buf[0]<=' ' && *type!=UNKNOWN) {	/* folded header, append to previous */
		p=buf;
		truncsp(p);
		if(*type==RFC822HEADER || *type==SMTPRECEIVED)
			smb_hfield_append_str(msg,*type,"\r\n");
		else { /* Unfold other common header field types (e.g. Subject, From, To) */
			smb_hfield_append_str(msg,*type," ");
		return smb_hfield_append_str(msg, *type, p);
	if((p=strchr(buf,':'))==NULL)
		return smb_hfield_str(msg, *type=RFC822HEADER, buf);

	len=(ulong)p-(ulong)buf;
	if(len>sizeof(field)-1)
		len=sizeof(field)-1;
	sprintf(field,"%.*s",len,buf);
	truncsp(field);

	p++;	/* skip colon */
	SKIP_WHITESPACE(p);
	truncsp(p);

	if(!stricmp(field, "TO"))
		return smb_hfield_str(msg, *type=RFC822TO, p);
		smb_hfield_str(msg, *type=RFC822REPLYTO, p);
		if((tp=strrchr(p,'<'))!=NULL)  {
			tp++;
			truncstr(tp,">");
			p=tp;
		}
		nettype=NET_INTERNET;
		smb_hfield(msg, REPLYTONETTYPE, sizeof(nettype), &nettype);
		return smb_hfield_str(msg, *type=REPLYTONETADDR, p);
		return smb_hfield_str(msg, *type=RFC822FROM, p);
		return smb_hfield_str(msg, *type=RFC822ORG, p);
		msg->hdr.when_written=rfc822date(p);
		return smb_hfield_str(msg, *type=RFC822MSGID, p);
		return smb_hfield_str(msg, *type=RFC822REPLYID, p);
		return smb_hfield_str(msg, *type=RFC822CC, p);
	if(!stricmp(field, "RECEIVED"))
		return smb_hfield_str(msg, *type=SMTPRECEIVED, p);

	if(!stricmp(field, "RETURN-PATH")) {
		*type=UNKNOWN;
		return SMB_SUCCESS;	/* Ignore existing "Return-Path" header fields */
	}

	if(!stricmp(field, "X-PRIORITY")) {
		msg->hdr.priority = atoi(p);
		if(msg->hdr.priority > SMB_PRIORITY_LOWEST)
			msg->hdr.priority = SMB_PRIORITY_UNSPECIFIED;
		*type=UNKNOWN;
		return SMB_SUCCESS;
	}

	return smb_hfield_str(msg, *type=RFC822HEADER, buf);
static int chk_received_hdr(SOCKET socket,const char* prot,const char *buf,IN_ADDR *dnsbl_result, char *dnsbl, char *dnsbl_ip)
	char		ip[16] = "ipv6-addr";
deuce's avatar
deuce committed
	union xp_sockaddr addr;
	struct addrinfo ai,*res;
deuce's avatar
deuce committed
	fromstr=strdup(buf);
	if(fromstr==NULL)
		return(0);
	strlwr(fromstr);
	do {
		p=strstr(fromstr,"from ");
		if(p==NULL)
			break;
		p+=4;
		for(;*p && !IS_WHITESPACE(*p) && p2<host_name+126;p++)  {
		p=strtok_r(fromstr,"[",&last);
		p=strtok_r(NULL,"]",&last);
deuce's avatar
deuce committed
		if(strnicmp("IPv6:", p, 5)) {
			p+=5;
			SKIP_WHITESPACE(p);
			memset(&ai, 0, sizeof(ai));
			ai.ai_flags = AI_NUMERICHOST|AI_NUMERICSERV|AI_PASSIVE;
			if(getaddrinfo(p, NULL, &ai, &res)!=0)
				break;
			if(res->ai_family == AF_INET6) {
deuce's avatar
deuce committed
				memcpy(&addr, res->ai_addr, res->ai_addrlen);
				freeaddrinfo(res);
			} else {
				freeaddrinfo(res);
deuce's avatar
deuce committed
				break;
deuce's avatar
deuce committed
		}
		else {
			strncpy(ip,p,16);
			ip[15]=0;
			addr.in.sin_family=AF_INET;
			addr.in.sin_addr.s_addr=inet_addr(ip);
			lprintf(LOG_DEBUG,"%04d %s DNSBL checking received header address %s [%s]",socket,prot,host_name,ip);
deuce's avatar
deuce committed
		}

		if((dnsbl_result->s_addr=dns_blacklisted(socket,prot,&addr,host_name,dnsbl,dnsbl_ip))!=0)
				lprintf(LOG_NOTICE,"%04d %s [%s] BLACKLISTED SERVER on %s: %s = %s"
					,socket, prot, ip, dnsbl, host_name, inet_ntoa(*dnsbl_result));
	} while(0);
	free(fromstr);
	return(dnsbl_result->s_addr);
}

static void parse_mail_address(char* p
							   ,char* name, size_t name_len
							   ,char* addr, size_t addr_len)
{
	char*	tp;
deuce's avatar
deuce committed
	sprintf(addr,"%.*s",(int)addr_len,tp);
	if(name != NULL) {
		SAFECOPY(tmp,p);
		p=tmp;
		/* Get the "name" (if possible) */
		if((tp=strchr(p,'"'))!=NULL) {	/* name in quotes? */
			p=tp+1;
			tp=strchr(p,'"');
		} else if((tp=strchr(p,'('))!=NULL) {	/* name in parenthesis? */
			p=tp+1;
			tp=strchr(p,')');
		} else if(*p=='<') {					/* address in brackets? */
			p++;
			tp=strchr(p,'>');
		} else									/* name, then address in brackets */
			tp=strchr(p,'<');
		if(tp) *tp=0;
		sprintf(name,"%.*s",(int)name_len,p);
		truncsp(name);
		strip_char(name, name, '\\');
	}
deuce's avatar
deuce committed
static BOOL checktag(scfg_t *scfg, char *tag, uint usernum)
{
	char	fname[MAX_PATH+1];

	if(tag==NULL)
		return(FALSE);
	SAFEPRINTF2(fname,"%suser/%04d.smtpblock",scfg->data_dir,usernum);
deuce's avatar
deuce committed
	return(findstr(tag, fname));
}

static BOOL smtp_splittag(char *in, char **name, char **tag)
{
	char	*last;

	if(in==NULL)
		return(FALSE);

	*name=strtok_r(in, "#", &last);
deuce's avatar
deuce committed
	if(*name) {
		*tag=strtok_r(NULL, "", &last);
		return(TRUE);
	}
	return(FALSE);
}

static uint smtp_matchuser(scfg_t *scfg, char *str, BOOL aliases, BOOL datdupe)
{
	char	*user=strdup(str);
	char	*name;
	char	*tag=NULL;
	uint	usernum=0;

	if(!user)
		return(0);

	if(!smtp_splittag(user, &name, &tag))
		goto end;

	if(datdupe)
		usernum=userdatdupe(scfg, 0, U_NAME, LEN_NAME, name, /* del */FALSE, /* next */FALSE, NULL, NULL);
deuce's avatar
deuce committed
	else
		usernum=matchuser(scfg, name, aliases);

	if(!usernum)
		goto end;

	if(checktag(scfg, tag, usernum))
		usernum=UINT_MAX;

end:
	free(user);
	return(usernum);
}

#define WITH_ESMTP	(1<<0)
#define WITH_AUTH	(1<<1)
#define WITH_TLS	(1<<2)

char *with_clauses[] = {
	"SMTP",			// No WITH_*
	"ESMTP",		// WITH_ESMTP
	"SMTP",			// WITH_AUTH
	"ESMTPA",		// WITH_ESMTP | WITH_AUTH
	"SMTP",			// WITH_TLS
	"ESMTPS",		// WITH_ESMTP | WITH_TLS
	"SMTP",			// WITH_TLS | WITH_AUTH
	"ESMTPSA"		// WITH_TLS | WITH_AUTH | WITH_ESMTP
};

static void smtp_thread(void* arg)
{
	int			rd;
	char		str[512];
	char		buf[1024],*p,*tp,*cp;
	char		hdrfield[512];
	char		alias_buf[128];
rswindell's avatar
rswindell committed
	char		name_alias_buf[128];
	char		reverse_path[128];
	char		date[64];
	char		rcpt_name[128];
	char		rcpt_addr[128];
	char		hello_name[128];
	char		relay_list[MAX_PATH+1];
	char		domain_list[MAX_PATH+1];
	char		spam_bait[MAX_PATH+1];
	BOOL		spam_bait_result=FALSE;
rswindell's avatar
rswindell committed
	char		spam_block_exemptions[MAX_PATH+1];
	BOOL		spam_block_exempt=FALSE;
	char		host_name[128];
deuce's avatar
deuce committed
	char		host_ip[INET6_ADDRSTRLEN];
	char		server_ip[INET6_ADDRSTRLEN];
deuce's avatar
deuce committed
	char		dnsbl_ip[INET6_ADDRSTRLEN];
	char		challenge[256];
	char		response[128];
	char		secret[64];
	char		md5_data[384];
	uchar		digest[MD5_DIGEST_SIZE];
	socklen_t	addr_len;
	ulong		login_attempts;
rswindell's avatar
rswindell committed
	ulong		waiting;
	BOOL		esmtp=FALSE;
	BOOL		forward=FALSE;
	BOOL		no_forward=FALSE;
	BOOL		routed=FALSE;
	BOOL		dnsbl_recvhdr;
	FILE*		msgtxt=NULL;
	char		newtxt_fname[MAX_PATH+1];
	char		logtxt_fname[MAX_PATH+1];
	FILE*		rcptlst;
	char		proc_err_fname[MAX_PATH+1];
	char		session_id[MAX_PATH+1];
	FILE*		spy=NULL;
	SOCKET		socket;
	smbmsg_t	msg;
	smbmsg_t	newmsg;
	user_t		user;
	client_t	client;
	smtp_t		smtp=*(smtp_t*)arg;
deuce's avatar
deuce committed
	union xp_sockaddr	server_addr;
	IN_ADDR		dnsbl_result;
rswindell's avatar
rswindell committed
	BOOL*		mailproc_to_match;
rswindell's avatar
rswindell committed
	JSRuntime*	js_runtime=NULL;
	JSContext*	js_cx=NULL;
	JSObject*	js_glob=NULL;
	int32		js_result;
	login_attempt_t attempted;
	int session = -1;
	BOOL nodelay=TRUE;
	ulong nb = 0;
	int level;
	int cstat;
	char *estr;
rswindell's avatar
rswindell committed

	enum {
			 SMTP_STATE_INITIAL
			,SMTP_STATE_HELO
rswindell's avatar
rswindell committed
			,SMTP_STATE_MAIL_FROM
			,SMTP_STATE_RCPT_TO
			,SMTP_STATE_DATA_HEADER
			,SMTP_STATE_DATA_BODY

	} state = SMTP_STATE_INITIAL;

rswindell's avatar
rswindell committed
	enum {
			 SMTP_CMD_NONE
			,SMTP_CMD_MAIL
			,SMTP_CMD_SEND
			,SMTP_CMD_SOML
			,SMTP_CMD_SAML

	} cmd = SMTP_CMD_NONE;

	client.protocol = smtp.tls_port ? "SMTPS" : "SMTP";

	lprintf(LOG_DEBUG,"%04d %s Session thread started", socket, client.protocol);
#ifdef _WIN32
	if(startup->inbound_sound[0] && !(startup->options&MAIL_OPT_MUTE)) 
		PlaySound(startup->inbound_sound, NULL, SND_ASYNC|SND_FILENAME);
	SAFEPRINTF(domain_list,"%sdomains.cfg",scfg.ctrl_dir);

	addr_len=sizeof(server_addr);
		if (get_ssl_cert(&scfg, &estr, &level) == -1) {
			if (estr) {
				lprintf(level, "%04d %s !%s", socket, client.protocol, estr);
				free_crypt_attrstr(estr);
			mail_close_socket(&socket, &session);
		if ((cstat = cryptCreateSession(&session, CRYPT_UNUSED, CRYPT_SESSION_SSL_SERVER)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "creating session");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_SSL_OPTIONS, CRYPT_SSLOPTION_DISABLE_CERTVERIFY)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "disabling certificate verification");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_PRIVATEKEY, scfg.tls_certificate)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting private key");
			mail_close_socket(&socket, &session);
			thread_down();
			return;
		}
		nodelay = TRUE;
		setsockopt(socket,IPPROTO_TCP,TCP_NODELAY,(char*)&nodelay,sizeof(nodelay));
		nb=0;
		ioctlsocket(socket,FIONBIO,&nb);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_NETWORKSOCKET, socket)) != CRYPT_OK) {
			GCES(cstat, client.protocol, socket, CRYPT_UNUSED, "setting network socket");
			mail_close_socket(&socket, &session);
		if ((cstat = cryptSetAttribute(session, CRYPT_SESSINFO_ACTIVE, 1)) != CRYPT_OK) {