#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <termios.h>
#include <fcntl.h>
#include <sqlite3.h>
#include <sys/wait.h>

#include "jamlib/jam.h"
#include "libuuid/uuid.h"

#include "bluewave.h"
#include "bbs.h"
#include "mail_utils.h"

extern struct bbs_config conf;
extern struct user_record *gUser;
extern int mynode;
extern char upload_filename[1024];
extern int sshBBS;
extern int bbs_stdin;
extern int bbs_stdout;
extern int bbs_stderr;

tLONG convertl(tLONG l) {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
	unsigned char result_bytes[4];
	unsigned int result;
	result_bytes[0] = (unsigned char)((l >> 24) & 0xFF);
	result_bytes[1] = (unsigned char)((l >> 16) & 0xFF);
	result_bytes[2] = (unsigned char)((l >> 8) & 0xFF);
	result_bytes[3] = (unsigned char)(l & 0xFF);
	memcpy(&result, result_bytes, 4);
	return result;
#else
	return l;
#endif
}

tWORD converts(tWORD s) {
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
	unsigned char result_bytes[2];
	unsigned short result;
	result_bytes[0] = (unsigned char)((s >> 8) & 0xFF);
	result_bytes[1] = (unsigned char)(s & 0xFF);
	memcpy(&result, result_bytes, 4);
	return result;
#else
	return s;
#endif
}

int bwave_scan_email(int areano, int totmsgs, FILE *fti_file, FILE *mix_file, FILE *dat_file, int *last_ptr) {
	sqlite3 *db;
	sqlite3_stmt *res;
	int rc;
	char *sql = "SELECT sender,subject,date,body,id FROM email WHERE recipient LIKE ? AND seen = 0";
	char *sqlseen = "UPDATE email SET seen = 1 WHERE recipient LIKE ?";
	char buffer[PATH_MAX];
	MIX_REC mix;
	FTI_REC fti;
	long mixptr;
	struct tm timeStruct;
	char *month_name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
	time_t thetime;
	char *body;
	int area_msgs = 0;

	snprintf(buffer, PATH_MAX, "%s/email.sq3", conf.bbs_path);
	rc = sqlite3_open(buffer, &db);
	if (rc != SQLITE_OK) {
		dolog("Cannot open database: %s", sqlite3_errmsg(db));
		sqlite3_close(db);
		return totmsgs;
	}
	sqlite3_busy_timeout(db, 5000);
	rc = sqlite3_prepare_v2(db, sql, -1, &res, 0);

	if (rc == SQLITE_OK) {
		sqlite3_bind_text(res, 1, gUser->loginname, -1, 0);
	} else {
		dolog("Failed to execute statement: %s", sqlite3_errmsg(db));
		sqlite3_finalize(res);
		sqlite3_close(db);
		return totmsgs;
	}

	mixptr = ftell(fti_file);

	while (sqlite3_step(res) == SQLITE_ROW) {
		memset(&fti, 0, sizeof(FTI_REC));
		strlcpy(fti.from, sqlite3_column_text(res, 0), sizeof fti.from);
		strlcpy(fti.to, gUser->loginname, sizeof fti.to);
		strlcpy(fti.subject, sqlite3_column_text(res, 1), sizeof fti.subject);
		thetime = sqlite3_column_int(res, 2);
		localtime_r((time_t *)&thetime, &timeStruct);

		snprintf(fti.date, sizeof fti.date, "%02d-%s-%04d %02d:%02d", timeStruct.tm_mday, month_name[timeStruct.tm_mon], timeStruct.tm_year + 1900, timeStruct.tm_hour, timeStruct.tm_min);
		fti.msgnum = converts((tWORD)sqlite3_column_int(res, 4));
		body = strdup(sqlite3_column_text(res, 3));
		fti.replyto = 0;
		fti.replyat = 0;
		fti.msgptr = convertl(*last_ptr);
		fti.msglength = convertl(strlen(body));

		*last_ptr += strlen(body);
		fti.flags |= FTI_MSGLOCAL;
		fti.flags = converts(fti.flags);
		fti.orig_zone = 0;
		fti.orig_net = 0;
		fti.orig_node = 0;
		fwrite(body, 1, strlen(body), dat_file);
		fwrite(&fti, sizeof(FTI_REC), 1, fti_file);
		free(body);
		area_msgs++;
		totmsgs++;
	}

	sqlite3_finalize(res);

	rc = sqlite3_prepare_v2(db, sqlseen, -1, &res, 0);
	if (rc == SQLITE_OK) {
		sqlite3_bind_text(res, 1, gUser->loginname, -1, 0);
		sqlite3_step(res);
	}
	sqlite3_finalize(res);
	sqlite3_close(db);

	memset(&mix, 0, sizeof(MIX_REC));

	snprintf(mix.areanum, 6, "%d", 1);
	mix.totmsgs = converts(area_msgs);
	mix.numpers = converts(area_msgs);
	mix.msghptr = convertl(mixptr);
	fwrite(&mix, sizeof(MIX_REC), 1, mix_file);

	return totmsgs;
}

int bwave_scan_area(int confr, int area, int areano, int totmsgs, FILE *fti_file, FILE *mix_file, FILE *dat_file, int *last_ptr) {
	struct msg_headers *msghs = read_message_headers(confr, area, gUser, 0);
	int all_unread = 1;
	s_JamBase *jb;
	s_JamLastRead jlr;
	int i;
	int k;
	MIX_REC mix;
	int area_msgs;
	int personal_msgs;
	long mixptr;
	FTI_REC fti;
	struct fido_addr *fido;
	char *body;
	struct tm timeStruct;
	char realname[66];
	char *month_name[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

	snprintf(realname, 65, "%s %s", gUser->firstname, gUser->lastname);
	if (msghs == NULL) {
		return totmsgs;
	}

	struct mail_area *ma = get_area(confr, area);
	jb = open_jam_base(ma->path);
	if (!jb) {
		dolog("Error opening JAM base.. %s", ma->path);
		if (msghs != NULL) {
			free_message_headers(msghs);
		}
		return totmsgs;
	}
	all_unread = 0;
	if (JAM_ReadLastRead(jb, gUser->id, &jlr) == JAM_NO_USER) {
		jlr.LastReadMsg = 0;
		jlr.HighReadMsg = 0;
		jlr.UserCRC = JAM_Crc32(gUser->loginname, strlen(gUser->loginname));
		jlr.UserID = gUser->id;
		all_unread = 1;
	} else if (jlr.LastReadMsg == 0 && jlr.HighReadMsg == 0) {
		all_unread = 1;
	}

	if (all_unread == 0) {
		k = jlr.HighReadMsg;
		for (i = 0; i < msghs->msg_count; i++) {
			if (msghs->msgs[i]->msg_h->MsgNum == k) {
				break;
			}
		}
		i += 1;
	} else {
		i = 0;
	}

	mixptr = ftell(fti_file);
	area_msgs = 0;
	personal_msgs = 0;

	for (k = i; k < msghs->msg_count; k++) {

		if (totmsgs == conf.bwave_max_msgs) {
			break;
		}

		if (msghs->msgs[k]->to != NULL) {
			if (strcasecmp(msghs->msgs[k]->to, gUser->loginname) == 0 || strncasecmp(msghs->msgs[k]->to, realname, 42) == 0) {
				personal_msgs++;
			}
		}

		memset(&fti, 0, sizeof(FTI_REC));

		if (msghs->msgs[k]->from != NULL) {
			strlcpy(fti.from, msghs->msgs[k]->from, sizeof fti.from);
		} else {
			strlcpy(fti.from, "(Missing From)", sizeof fti.from);
		}
		if (msghs->msgs[k]->to != NULL) {
			strlcpy(fti.to, msghs->msgs[k]->to, sizeof fti.to);
		} else {
			strlcpy(fti.to, "(Missing To)", sizeof fti.to);
		}

		if (msghs->msgs[k]->subject != NULL) {
			strlcpy(fti.subject, msghs->msgs[k]->subject, sizeof fti.subject);
		} else {
			strlcpy(fti.subject, "(Missing Subject)", sizeof fti.subject);
		}

		localtime_r((time_t *)&msghs->msgs[k]->msg_h->DateWritten, &timeStruct);

		snprintf(fti.date, sizeof fti.date, "%02d-%s-%04d %02d:%02d", timeStruct.tm_mday, month_name[timeStruct.tm_mon], timeStruct.tm_year + 1900, timeStruct.tm_hour, timeStruct.tm_min);
		fti.msgnum = converts((tWORD)msghs->msgs[k]->msg_h->MsgNum);
		fti.replyto = 0;
		fti.replyat = 0;
		fti.msgptr = convertl(*last_ptr);
		fti.msglength = convertl(msghs->msgs[k]->msg_h->TxtLen);

		*last_ptr += msghs->msgs[k]->msg_h->TxtLen;

		if (msghs->msgs[k]->msg_h->Attribute & JAM_MSG_LOCAL) {
			fti.flags |= FTI_MSGLOCAL;
		}

		fti.flags = converts(fti.flags);

		fido = parse_fido_addr(msghs->msgs[k]->oaddress);
		if (fido != NULL) {
			fti.orig_zone = converts(fido->zone);
			fti.orig_net = converts(fido->net);
			fti.orig_node = converts(fido->node);
			free(fido);
		} else {
			fti.orig_zone = 0;
			fti.orig_net = 0;
			fti.orig_node = 0;
		}
		// write msg data
		body = (char *)malloz(msghs->msgs[k]->msg_h->TxtLen);
		JAM_ReadMsgText(jb, msghs->msgs[k]->msg_h->TxtOffset, msghs->msgs[k]->msg_h->TxtLen, body);

		fwrite(body, 1, msghs->msgs[k]->msg_h->TxtLen, dat_file);
		fwrite(&fti, sizeof(FTI_REC), 1, fti_file);

		free(body);
		jlr.LastReadMsg = msghs->msgs[k]->msg_h->MsgNum;
		if (jlr.HighReadMsg < msghs->msgs[k]->msg_h->MsgNum) {
			jlr.HighReadMsg = msghs->msgs[k]->msg_h->MsgNum;
		}

		JAM_WriteLastRead(jb, gUser->id, &jlr);

		area_msgs++;
		totmsgs++;
	}

	//if (area_msgs) {

	memset(&mix, 0, sizeof(MIX_REC));

	snprintf(mix.areanum, 6, "%d", areano);
	mix.totmsgs = converts(area_msgs);
	mix.numpers = converts(personal_msgs);
	mix.msghptr = convertl(mixptr);
	fwrite(&mix, sizeof(MIX_REC), 1, mix_file);
	//}
	JAM_CloseMB(jb);
	free(jb);
	free_message_headers(msghs);
	return totmsgs;
}

void bwave_create_packet() {
	char buffer[1024];
	char archive[1024];
	INF_HEADER hdr;
	struct ptr_vector areas;
	INF_AREA_INFO *area = NULL;
	int i;
	int j;
	int area_count;
	tWORD flags;
	int lasttot;
	int last_ptr = 0;
	int stout;
	int stin;
	int sterr;
	char *weekday[] = {"SU", "MO", "TU", "WE", "TH", "FR", "SA"};
	struct stat s;
	struct termios oldit;
	struct termios oldot;
	struct tm time_tm;
	time_t thetime;
	FILE *mix_file;
	FILE *fti_file;
	FILE *dat_file;
	FILE *inf_file;
	int tot_areas = 0;
	int totmsgs = 0;
	int ret;
	char **args, *arg;
	char *cmd;
	pid_t pid;

	init_ptr_vector(&areas);

	for (size_t i = 0; i < ptr_vector_len(&conf.mail_conferences); i++) {
		struct mail_conference *mc = ptr_vector_get(&conf.mail_conferences, i);
		for (size_t j = 0; j < ptr_vector_len(&mc->mail_areas); j++) {
			if (msgbase_is_subscribed(i, j)) {
				tot_areas++;
			}
		}
	}

	if (tot_areas == 0) {
		s_printf(get_string(224));
		s_printf(get_string(6));
		s_getchar();
		return;
	}

	area_count = 0;

	memset(&hdr, 0, sizeof(INF_HEADER));
	hdr.ver = PACKET_LEVEL;
	strlcpy(hdr.loginname, gUser->loginname, sizeof hdr.loginname);
	//strlcpy(hdr.aliasname, gUser->loginname, sizeof hdr.aliasname);
	snprintf(hdr.aliasname, sizeof hdr.aliasname, "%s %s", gUser->firstname, gUser->lastname);
	hdr.zone = converts(conf.main_aka->zone);
	hdr.node = converts(conf.main_aka->node);
	hdr.net = converts(conf.main_aka->net);
	hdr.point = converts(conf.main_aka->point);
	strlcpy(hdr.sysop, conf.sysop_name, sizeof hdr.sysop);

	strlcpy(hdr.systemname, conf.bbs_name, sizeof hdr.systemname);
	hdr.inf_header_len = converts(sizeof(INF_HEADER));
	hdr.inf_areainfo_len = converts(sizeof(INF_AREA_INFO));
	hdr.mix_structlen = converts(sizeof(MIX_REC));
	hdr.fti_structlen = converts(sizeof(FTI_REC));
	hdr.uses_upl_file = 1;
	hdr.from_to_len = 35;
	hdr.subject_len = 71;
	memcpy(hdr.packet_id, conf.bwave_name, strlen(conf.bwave_name));

	snprintf(buffer, sizeof buffer, "%s/node%d", conf.bbs_path, mynode);

	if (stat(buffer, &s) != 0) {
		mkdir(buffer, 0755);
	}

	snprintf(buffer, sizeof buffer, "%s/node%d/bwave/", conf.bbs_path, mynode);

	if (stat(buffer, &s) == 0) {
		recursive_delete(buffer);
	}
	mkdir(buffer, 0755);

	snprintf(buffer, sizeof buffer, "%s/node%d/bwave/%s.FTI", conf.bbs_path, mynode, conf.bwave_name);

	fti_file = fopen(buffer, "wb");

	snprintf(buffer, sizeof buffer, "%s/node%d/bwave/%s.MIX", conf.bbs_path, mynode, conf.bwave_name);
	mix_file = fopen(buffer, "wb");

	snprintf(buffer, sizeof buffer, "%s/node%d/bwave/%s.DAT", conf.bbs_path, mynode, conf.bwave_name);
	dat_file = fopen(buffer, "wb");

	s_printf("\r\n");

	totmsgs = bwave_scan_email(area_count + 1, totmsgs, fti_file, mix_file, dat_file, &last_ptr);
	s_printf(get_string(195), "Private Email", "Private Email", totmsgs);

	flags = 0;
	area = (INF_AREA_INFO *)malloz(sizeof(INF_AREA_INFO));

	snprintf(area->areanum, 6, "%d", area_count + 1);

	memcpy(area->echotag, "PRIVATE_EMAIL", 13);

	strlcpy(area->title, "Private Email", 49);
	flags |= INF_POST;
	flags |= INF_NO_PUBLIC;
	flags |= INF_SCANNING;

	area->area_flags = converts(flags);
	area->network_type = INF_NET_FIDONET;
	ptr_vector_append(&areas, area);

	area_count++;

	if (totmsgs < conf.bwave_max_msgs) {
		for (size_t i = 0; i < ptr_vector_len(&conf.mail_conferences); i++) {
			struct mail_conference *mc = ptr_vector_get(&conf.mail_conferences, i);
			for (size_t j = 0; j < ptr_vector_len(&mc->mail_areas); j++) {
				struct mail_area *ma = get_area(i, j);
				if (ma->read_sec_level <= gUser->sec_level && ma->qwkname != NULL && msgbase_is_subscribed(i, j)) {
					lasttot = totmsgs;
					totmsgs = bwave_scan_area(i, j, area_count + 1, totmsgs, fti_file, mix_file, dat_file, &last_ptr);
					s_printf(get_string(195), mc->name, ma->name, totmsgs - lasttot);
					//if (lasttot == totmsgs) {
					//	continue;
					//}

					area = (INF_AREA_INFO *)malloz(sizeof(INF_AREA_INFO));

					snprintf(area->areanum, sizeof area->areanum, "%d", area_count + 1);
					strlcpy(area->echotag, ma->qwkname, sizeof area->echotag);
					strlcpy(area->title, ma->name, sizeof area->title);

					flags = 0;
					if (ma->write_sec_level <= gUser->sec_level) {
						flags |= INF_POST;
					}

					if (ma->type == TYPE_NETMAIL_AREA) {
						flags |= INF_NO_PUBLIC;
						flags |= INF_NETMAIL;
						flags |= INF_ECHO;
					}

					if (ma->type == TYPE_ECHOMAIL_AREA || ma->type == TYPE_NEWSGROUP_AREA) {
						flags |= INF_NO_PRIVATE;
						flags |= INF_ECHO;
					}

					if (ma->type == TYPE_LOCAL_AREA) {
						flags |= INF_NO_PRIVATE;
					}

					flags |= INF_SCANNING;

					area->area_flags = converts(flags);
					area->network_type = INF_NET_FIDONET;

					ptr_vector_append(&areas, area);
					area_count++;
					if (totmsgs == conf.bwave_max_msgs) {
						break;
					}
				}
			}
			if (totmsgs == conf.bwave_max_msgs) {
				break;
			}
		}
	}

	fclose(dat_file);
	fclose(mix_file);
	fclose(fti_file);

	snprintf(buffer, 1024, "%s/node%d/bwave/%s.INF", conf.bbs_path, mynode, conf.bwave_name);

	inf_file = fopen(buffer, "wb");
	fwrite(&hdr, sizeof(INF_HEADER), 1, inf_file);

	for (i = 0; i < area_count; i++) {
		fwrite(ptr_vector_get(&areas, i), sizeof(INF_AREA_INFO), 1, inf_file);
	}

	fclose(inf_file);

	ptr_vector_apply(&areas, free);
	destroy_ptr_vector(&areas);

	if (totmsgs > 0) {
		// create archive
		if (gUser->bwavestyle) {
			thetime = time(NULL);
			localtime_r(&thetime, &time_tm);

			if (gUser->bwavepktno / 10 != time_tm.tm_wday) {
				gUser->bwavepktno = time_tm.tm_wday * 10;
			}

			snprintf(archive, sizeof archive, "%s/node%d/%s.%s%d",
			         conf.bbs_path, mynode, conf.bwave_name, weekday[time_tm.tm_wday], gUser->bwavepktno % 10);
		} else {
			snprintf(archive, sizeof archive, "%s/node%d/%s.%03d", conf.bbs_path, mynode, conf.bwave_name, gUser->bwavepktno);
		}

		struct archiver *arc = ptr_vector_get(&conf.archivers, gUser->defarchiver - 1);
		assert(arc != NULL);
		char *b = buffer;
		size_t blen = sizeof buffer;
		for (const char *p = arc->pack; *p != '\0' && blen >= 1; ++p) {
			if (*p != '*') {
				*b++ = *p;
				--blen;
				continue;
			}
			p++;
			size_t alen = 0;
			if (*p == 'a') {
				strlcpy(b, archive, blen);
				alen = strlen(archive);
			} else if (*p == 'f') {
				snprintf(b, blen, "%s/node%d/bwave/%s.INF %s/node%d/bwave/%s.MIX %s/node%d/bwave/%s.FTI %s/node%d/bwave/%s.DAT",
				         conf.bbs_path, mynode, conf.bwave_name, conf.bbs_path, mynode, conf.bwave_name, conf.bbs_path,
				         mynode, conf.bwave_name, conf.bbs_path, mynode, conf.bwave_name);
				alen = strlen(b);
			} else if (*p == '*') {
				*b++ = '*';
				alen = 1;
			}
			b += alen;
			blen -= alen;
		}
		*b = '\0';

		if (sshBBS) {
			stout = dup(STDOUT_FILENO);
			stin = dup(STDIN_FILENO);
			sterr = dup(STDERR_FILENO);

			dup2(bbs_stdout, STDOUT_FILENO);
			dup2(bbs_stderr, STDERR_FILENO);
			dup2(bbs_stdin, STDIN_FILENO);
		}

		args = split_on_space(buffer, NULL);
		cmd = args[0];
		pid = fork();
		if (pid == 0) {
			execvp(cmd, args);
			exit(0);
		} else if (pid > 0) {
			waitpid(pid, &ret, 0);
		} else {
			ret = -1;
		}
		free(args);
		if (sshBBS) {

			dup2(stout, STDOUT_FILENO);
			dup2(sterr, STDERR_FILENO);
			dup2(stin, STDIN_FILENO);

			close(stin);
			close(stout);
			close(sterr);
		}

		if (ret != -1 && ret >> 8 != 127) {
			do_download(gUser, archive);
		} else {
			s_printf(get_string(274));
		}

		snprintf(buffer, 1024, "%s/node%d/bwave", conf.bbs_path, mynode);
		recursive_delete(buffer);

		unlink(archive);
		gUser->bwavepktno++;
		if (gUser->bwavepktno > 999) {
			gUser->bwavepktno = 0;
		}
		save_user(gUser);
	}

	s_printf(get_string(6));
	s_getc();
}

int bwave_add_message(int confr, int area, unsigned int dwritten, char *to, char *subject, struct fido_addr *destaddr, char *msg) {
	s_JamBase *jb;
	s_JamMsgHeader jmh;
	s_JamSubPacket *jsp;
	s_JamSubfield jsf;
	int z;
	char buffer[256];
	uuid_t magi_msgid;

	struct mail_area *ma = get_area(confr, area);
	jb = open_jam_base(ma->path);
	if (!jb) {
		dolog("Error opening JAM base.. %s", ma->path);
		return 1;
	}

	JAM_ClearMsgHeader(&jmh);
	jmh.DateWritten = dwritten;
	jmh.Attribute |= JAM_MSG_LOCAL;
	struct mail_conference *mc = get_conf(confr);
	if (mc->realnames == 0) {
		strlcpy(buffer, gUser->loginname, sizeof buffer);
	} else {
		snprintf(buffer, sizeof buffer, "%s %s", gUser->firstname, gUser->lastname);
	}

	jsp = JAM_NewSubPacket();

	jsf.LoID = JAMSFLD_SENDERNAME;
	jsf.HiID = 0;
	jsf.DatLen = strlen(buffer);
	jsf.Buffer = (char *)buffer;
	JAM_PutSubfield(jsp, &jsf);

	if (ma->type == TYPE_NEWSGROUP_AREA) {
		strlcpy(buffer, "ALL", sizeof buffer);
		jsf.LoID = JAMSFLD_RECVRNAME;
		jsf.HiID = 0;
		jsf.DatLen = strlen(buffer);
		jsf.Buffer = (char *)buffer;
		JAM_PutSubfield(jsp, &jsf);

	} else {
		jsf.LoID = JAMSFLD_RECVRNAME;
		jsf.HiID = 0;
		jsf.DatLen = strlen(to);
		jsf.Buffer = (char *)to;
		JAM_PutSubfield(jsp, &jsf);
	}

	jsf.LoID = JAMSFLD_SUBJECT;
	jsf.HiID = 0;
	jsf.DatLen = strlen(subject);
	jsf.Buffer = (char *)subject;
	JAM_PutSubfield(jsp, &jsf);

	if (ma->type == TYPE_ECHOMAIL_AREA || ma->type == TYPE_NEWSGROUP_AREA) {
		jmh.Attribute |= JAM_MSG_TYPEECHO;
		struct mail_conference *mc = get_conf(confr);
		if (mc->nettype == NETWORK_FIDO) {
			if (mc->fidoaddr->point) {
				snprintf(buffer, sizeof buffer, "%d:%d/%d.%d",
					mc->fidoaddr->zone,
				        mc->fidoaddr->net,
				        mc->fidoaddr->node,
				        mc->fidoaddr->point);
			} else {
				snprintf(buffer, sizeof buffer, "%d:%d/%d",
					mc->fidoaddr->zone,
				        mc->fidoaddr->net,
				        mc->fidoaddr->node);
			}
			jsf.LoID = JAMSFLD_OADDRESS;
			jsf.HiID = 0;
			jsf.DatLen = strlen(buffer);
			jsf.Buffer = (char *)buffer;
			JAM_PutSubfield(jsp, &jsf);

			snprintf(buffer, sizeof buffer, "%d:%d/%d.%d %08lx",
				mc->fidoaddr->zone,
			        mc->fidoaddr->net,
			        mc->fidoaddr->node,
			        mc->fidoaddr->point,
			        generate_msgid());

			jsf.LoID = JAMSFLD_MSGID;
			jsf.HiID = 0;
			jsf.DatLen = strlen(buffer);
			jsf.Buffer = (char *)buffer;
			JAM_PutSubfield(jsp, &jsf);
			jmh.MsgIdCRC = JAM_Crc32(buffer, strlen(buffer));
		} else if (mc->nettype == NETWORK_MAGI) {
			snprintf(buffer, sizeof buffer, "%d", mc->maginode);
			jsf.LoID = JAMSFLD_OADDRESS;
			jsf.HiID = 0;
			jsf.DatLen = strlen(buffer);
			jsf.Buffer = (char *)buffer;
			JAM_PutSubfield(jsp, &jsf);

			uuid_generate(magi_msgid);
			uuid_unparse_lower(magi_msgid, buffer);

			jsf.LoID = JAMSFLD_MSGID;
			jsf.HiID = 0;
			jsf.DatLen = strlen(buffer);
			jsf.Buffer = (char *)buffer;
			JAM_PutSubfield(jsp, &jsf);
			jmh.MsgIdCRC = JAM_Crc32(buffer, strlen(buffer));
		} else if (mc->nettype == NETWORK_QWK) {
			jsf.LoID = JAMSFLD_OADDRESS;
			jsf.HiID = 0;
			jsf.DatLen = strlen(conf.bwave_name);
			jsf.Buffer = (char *)conf.bwave_name;
			JAM_PutSubfield(jsp, &jsf);
		}
	} else if (ma->type == TYPE_NETMAIL_AREA && mc->nettype == NETWORK_FIDO) {
		jmh.Attribute |= JAM_MSG_TYPENET;
		jmh.Attribute |= JAM_MSG_PRIVATE;

		if (mc->fidoaddr->point) {
			snprintf(buffer, sizeof buffer, "%d:%d/%d.%d",
				mc->fidoaddr->zone,
			        mc->fidoaddr->net,
			        mc->fidoaddr->node,
			        mc->fidoaddr->point);
		} else {
			snprintf(buffer, sizeof buffer, "%d:%d/%d",
				mc->fidoaddr->zone,
			        mc->fidoaddr->net,
			        mc->fidoaddr->node);
		}
		jsf.LoID = JAMSFLD_OADDRESS;
		jsf.HiID = 0;
		jsf.DatLen = strlen(buffer);
		jsf.Buffer = (char *)buffer;
		JAM_PutSubfield(jsp, &jsf);

		if (destaddr != NULL) {
			if (destaddr->point) {
				snprintf(buffer, sizeof buffer, "%d:%d/%d.%d",
					destaddr->zone,
				        destaddr->net,
				        destaddr->node,
				        destaddr->point);
			} else {
				snprintf(buffer, sizeof buffer, "%d:%d/%d",
					destaddr->zone,
				        destaddr->net,
				        destaddr->node);
			}
			jsf.LoID = JAMSFLD_DADDRESS;
			jsf.HiID = 0;
			jsf.DatLen = strlen(buffer);
			jsf.Buffer = (char *)buffer;
			JAM_PutSubfield(jsp, &jsf);
		}

		snprintf(buffer, sizeof buffer, "%d:%d/%d.%d %08lx",
			mc->fidoaddr->zone,
		        mc->fidoaddr->net,
		        mc->fidoaddr->node,
		        mc->fidoaddr->point,
		        generate_msgid());

		jsf.LoID = JAMSFLD_MSGID;
		jsf.HiID = 0;
		jsf.DatLen = strlen(buffer);
		jsf.Buffer = (char *)buffer;
		JAM_PutSubfield(jsp, &jsf);
		jmh.MsgIdCRC = JAM_Crc32(buffer, strlen(buffer));
	}

	while (1) {
		z = JAM_LockMB(jb, 100);
		if (z == 0) {
			break;
		} else if (z == JAM_LOCK_FAILED) {
			sleep(1);
		} else {
			dolog("Failed to lock msg base!");
			JAM_CloseMB(jb);
			free(jb);
			return 1;
		}
	}
	if (JAM_AddMessage(jb, &jmh, jsp, (char *)msg, strlen(msg))) {
		dolog("Failed to add message");
		JAM_UnlockMB(jb);

		JAM_DelSubPacket(jsp);
		JAM_CloseMB(jb);
		free(jb);
		return -1;
	} else {
		JAM_UnlockMB(jb);

		JAM_DelSubPacket(jsp);
		JAM_CloseMB(jb);
		free(jb);
	}
	return 0;
}

void bwave_upload_reply() {
	char buffer[1024];
	char msgbuffer[1024];
	char originlinebuffer[256];
	int i;
	UPL_HEADER upl_hdr;
	UPL_REC upl_rec;
	int j;
	int confr;
	int area;
	tWORD msg_attr;
	struct fido_addr addr;
	stralloc sa = EMPTY_STRALLOC;
	char *body;
	char *tagline;
	struct stat s;
	int echomail = 0;
	int netmail = 0;
	FILE *upl_file;
	FILE *msg_file;
	int sem_fd;
	int msg_count;
	int stout;
	int stin;
	int sterr;
	sqlite3 *db;
	sqlite3_stmt *res;
	int rc;
	char *csql = "CREATE TABLE IF NOT EXISTS email ("
	             "id INTEGER PRIMARY KEY,"
	             "sender TEXT COLLATE NOCASE,"
	             "recipient TEXT COLLATE NOCASE,"
	             "subject TEXT,"
	             "body TEXT,"
	             "date INTEGER,"
	             "seen INTEGER);";
	char *isql = "INSERT INTO email (sender, recipient, subject, body, date, seen) VALUES(?, ?, ?, ?, ?, 0)";
	char *err_msg = 0;
	char **args, *arg;
	char *cmd;
	pid_t pid;
	int ret;
	msg_count = 0;

	snprintf(buffer, 1024, "%s/node%d", conf.bbs_path, mynode);

	if (stat(buffer, &s) != 0) {
		mkdir(buffer, 0755);
	}

	snprintf(buffer, 1024, "%s/node%d/bwave/", conf.bbs_path, mynode);

	if (stat(buffer, &s) == 0) {
		recursive_delete(buffer);
	}
	mkdir(buffer, 0755);

	if (!do_upload(gUser, buffer)) {
		s_printf(get_string(211));
		recursive_delete(buffer);
		return;
	}

	struct archiver *arc = ptr_vector_get(&conf.archivers, gUser->defarchiver - 1);
	assert(arc != NULL);
	char *b = buffer;
	size_t blen = sizeof buffer;
	for (const char *p = arc->unpack; *p != '\0' && blen > 1; ++p) {
		if (*p != '*') {
			*b++ = *p;
			--blen;
			continue;
		}
		p++;
		size_t alen = 0;
		if (*p == 'a') {
			strlcpy(b, upload_filename, blen);
			alen = strlen(upload_filename);
		} else if (*p == 'd') {
			snprintf(b, blen, "%s/node%d/bwave/", conf.bbs_path, mynode);
			alen = strlen(b);
		} else if (*p == '*') {
			*b++ = '*';
			alen = 1;
		}
		b += alen;
		blen -= alen;
	}
	*b = '\0';
	if (sshBBS) {
		stout = dup(STDOUT_FILENO);
		stin = dup(STDIN_FILENO);
		sterr = dup(STDERR_FILENO);

		dup2(bbs_stdout, STDOUT_FILENO);
		dup2(bbs_stderr, STDERR_FILENO);
		dup2(bbs_stdin, STDIN_FILENO);
	}
	args = split_on_space(buffer, NULL);
	cmd = args[0];
	pid = fork();
	if (pid == 0) {
		execvp(cmd, args);
		exit(0);
	} else if (pid > 0) {
		waitpid(pid, &ret, 0);
	} else {
		ret = -1;
	}
	free(args);
	if (sshBBS) {

		dup2(stout, STDOUT_FILENO);
		dup2(sterr, STDERR_FILENO);
		dup2(stin, STDIN_FILENO);

		close(stin);
		close(stout);
		close(sterr);
	}

	unlink(upload_filename);

	snprintf(buffer, sizeof buffer, "%s/node%d/bwave/%s.UPL", conf.bbs_path, mynode, conf.bwave_name);

	upl_file = fopen(buffer, "r");

	if (!upl_file) {
		snprintf(buffer, sizeof buffer, "%s/node%d/bwave/%s.upl", conf.bbs_path, mynode, conf.bwave_name);
		upl_file = fopen(buffer, "r");
		if (!upl_file) {
			s_printf(get_string(196));
			return;
		}
	}

	if (!fread(&upl_hdr, sizeof(UPL_HEADER), 1, upl_file)) {
		s_printf(get_string(196));
		fclose(upl_file);
		return;
	}

	while (fread(&upl_rec, sizeof(UPL_REC), 1, upl_file)) {
		if (strcmp("PRIVATE_EMAIL", upl_rec.echotag) == 0) {
			if (msg_attr & UPL_INACTIVE) {
				continue;
			}

			if (strcasecmp(upl_rec.from, gUser->loginname) != 0) {
				continue;
			}

			snprintf(msgbuffer, 1024, "%s/node%d/bwave/%s", conf.bbs_path, mynode, upl_rec.filename);
			body = file2str(msgbuffer);
			if (body == NULL) {
				continue;
			}

			char *b = body;
			for (char *p = body; *p != '\0'; ++p) {
				if (*p != '\n')
					*b++ = *p;
			}
			*b = '\0';

			snprintf(buffer, sizeof buffer, "%s/email.sq3", conf.bbs_path);

			rc = sqlite3_open(buffer, &db);

			if (rc != SQLITE_OK) {
				dolog("Cannot open database: %s", sqlite3_errmsg(db));
				sqlite3_close(db);
				free(body);
				continue;
			}
			sqlite3_busy_timeout(db, 5000);

			rc = sqlite3_exec(db, csql, 0, 0, &err_msg);
			if (rc != SQLITE_OK) {

				dolog("SQL error: %s", err_msg);

				sqlite3_free(err_msg);
				sqlite3_close(db);

				free(body);
				continue;
			}

			rc = sqlite3_prepare_v2(db, isql, -1, &res, 0);

			if (rc == SQLITE_OK) {
				sqlite3_bind_text(res, 1, gUser->loginname, -1, 0);
				sqlite3_bind_text(res, 2, upl_rec.to, -1, 0);
				sqlite3_bind_text(res, 3, upl_rec.subj, -1, 0);
				sqlite3_bind_text(res, 4, body, -1, 0);
				sqlite3_bind_int(res, 5, convertl(upl_rec.unix_date));
			} else {
				dolog("Failed to execute statement: %s", sqlite3_errmsg(db));
				sqlite3_finalize(res);
				sqlite3_close(db);
				free(body);
				continue;
			}
			sqlite3_step(res);

			sqlite3_finalize(res);
			sqlite3_close(db);
			free(body);
			msg_count++;
		} else {
			// find area
			confr = -1;
			area = -1;

			for (i = 0; i < ptr_vector_len(&conf.mail_conferences); i++) {
				struct mail_conference *mc = ptr_vector_get(&conf.mail_conferences, i);
				for (j = 0; j < ptr_vector_len(&mc->mail_areas); j++) {
					struct mail_area *ma = ptr_vector_get(&mc->mail_areas, j);
					if (strcmp(ma->qwkname, upl_rec.echotag) == 0) {
						confr = i;
						area = j;
						break;
					}
				}
				if (confr != -1) {
					break;
				}
			}

			if (confr != -1 && area != -1) {
				// import message
				struct mail_area *ma = get_area(confr, area);
				if (ma->write_sec_level <= gUser->sec_level) {
					msg_attr = converts(upl_rec.msg_attr);

					if (msg_attr & UPL_INACTIVE) {
						continue;
					}

					if (strcasecmp(upl_rec.from, gUser->loginname) != 0) {
						continue;
					}

					addr.zone = 0;
					addr.net = 0;
					addr.node = 0;
					addr.zone = 0;

					if (ma->type == TYPE_NETMAIL_AREA) {
						if (!(msg_attr & UPL_NETMAIL)) {
							continue;
						}
						addr.zone = converts(upl_rec.destzone);
						addr.net = converts(upl_rec.destnet);
						addr.node = converts(upl_rec.destnode);
						addr.zone = converts(upl_rec.destpoint);
						netmail = 1;
					} else if (ma->type == TYPE_ECHOMAIL_AREA || ma->type == TYPE_NEWSGROUP_AREA) {
						if (msg_attr & UPL_PRIVATE) {
							continue;
						}
						echomail = 1;
					} else { // Local area
						if (msg_attr & UPL_PRIVATE) {
							continue;
						}
					}

					snprintf(msgbuffer, sizeof buffer, "%s/node%d/bwave/%s", conf.bbs_path, mynode, upl_rec.filename);

					tagline = conf.default_tagline;
					struct mail_conference *mc = get_conf(confr);
					if (mc->tagline != NULL) {
						tagline = mc->tagline;
					}

					if (mc->nettype == NETWORK_FIDO) {
						if (mc->fidoaddr->point == 0) {
							snprintf(originlinebuffer, sizeof originlinebuffer, "\r--- %s\r * Origin: %s (%d:%d/%d)\r",
							         upl_hdr.reader_tear, tagline,
							         mc->fidoaddr->zone, mc->fidoaddr->net, mc->fidoaddr->node);
						} else {

							snprintf(originlinebuffer, sizeof originlinebuffer, "\r--- %s\r * Origin: %s (%d:%d/%d.%d)\r",
							         upl_hdr.reader_tear, tagline,
							         mc->fidoaddr->zone,
							         mc->fidoaddr->net,
							         mc->fidoaddr->node,
							         mc->fidoaddr->point);
						}
					} else if (mc->nettype == NETWORK_MAGI) {
						snprintf(originlinebuffer, sizeof originlinebuffer, "\r--- %s\r * Origin: %s (@%d)\r",
						         upl_hdr.reader_tear, tagline, mc->maginode);
					} else if (mc->nettype == NETWORK_QWK) {
						snprintf(originlinebuffer, sizeof originlinebuffer, "\r--- $s\r * Origin: %s (%s)\r",
						         upl_hdr.reader_tear, tagline, conf.bwave_name);
					} else {
						snprintf(originlinebuffer, sizeof originlinebuffer, "\r");
					}

					sa = file2stralloc(msgbuffer);
					if (sa.s == NULL) {
						continue;
					}
					stralloc_cats(&sa, originlinebuffer);
					stralloc_0(&sa);
					body = sa.s;
					char *p, *s;

					for (p = s = body; *p != '\0'; ++p) {
						if (*p != '\n')
							*s++ = *p;
					}
					*s = '\0';

					if (bwave_add_message(confr, area, convertl(upl_rec.unix_date), upl_rec.to, upl_rec.subj, &addr, body) != 0) {
						// failed to add message
						s_printf(get_string(197));
					} else {
						msg_count++;
					}

					free(body);
				}
			}
		}
	}

	snprintf(buffer, sizeof buffer, "%s/node%d/bwave/", conf.bbs_path, mynode);
	recursive_delete(buffer);

	if (netmail == 1) {
		if (conf.netmail_sem != NULL) {
			sem_fd = open(conf.netmail_sem, O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
			close(sem_fd);
		}
	}
	if (echomail == 1) {
		if (conf.echomail_sem != NULL) {
			sem_fd = open(conf.echomail_sem, O_RDWR | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
			close(sem_fd);
		}
	}

	s_printf("\r\n");

	if (msg_count > 0) {
		s_printf(get_string(204), msg_count);
	}

	s_printf(get_string(6));
	s_getc();
}
