From 7c877d1e7abd12d9d4361a381b9b695a7c32bca3 Mon Sep 17 00:00:00 2001
From: deuce <>
Date: Wed, 14 Mar 2018 22:48:27 +0000
Subject: [PATCH] It seems many IMAP clients open multiple connections to the
 server, and expect the Seen flags to be coherent across them... support this
 by leaving the users IMAP status file open, and holding a lock on the first
 byte.

We now need to flush changes to the status file more often, and we need
to call file.flush() before unlocking.
---
 exec/imapservice.js | 169 +++++++++++++++++++++++++++++---------------
 1 file changed, 111 insertions(+), 58 deletions(-)

diff --git a/exec/imapservice.js b/exec/imapservice.js
index 12b24bcc42..db476ef28d 100644
--- a/exec/imapservice.js
+++ b/exec/imapservice.js
@@ -30,6 +30,7 @@ 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;
+var cfgfile;
 
 /**********************/
 /* Encoding functions */
@@ -285,6 +286,8 @@ function send_fetch_response(msgnum, fmat, uid)
 			}
 		}
 		else {
+			lock_cfg();
+			read_cfg(index.code, false);
 			if(saved_config[index.code] == undefined)
 				saved_config[index.code] = {};
 			if(saved_config[index.code].Seen == undefined)
@@ -292,6 +295,9 @@ function send_fetch_response(msgnum, fmat, uid)
 			if(saved_config[index.code].Seen[msgnum] != 1)
 				seen_changed=true;
 			saved_config[index.code].Seen[msgnum]=1;
+			save_cfg(false);
+			apply_seen();
+			unlock_cfg();
 			idx.attr |= MSG_READ;
 		}
 	}
@@ -536,8 +542,6 @@ function send_fetch_response(msgnum, fmat, uid)
 			}
 		}
 	}
-	if(seen_changed)
-		save_cfg();
 	if(seen_changed && !sent_flags) {
 		get_header();
 		resp += "FLAGS ("+calc_msgflags(idx.attr, hdr.netattr, base.subnum, msgnum, readonly)+") ";
@@ -794,7 +798,6 @@ var any_state_command_handlers = {
 			var elapsed=0;
 
 			client.socket.send("+ Ooo, Idling... my favorite.\r\n");
-			save_cfg();
 			while(1) {
 				line=client.socket.recvline(10240, 5);
 				if(line==null) {
@@ -844,6 +847,11 @@ var unauthenticated_command_handlers = {
 					tagged(tag, "NO", "No AUTH for you.");
 					return;
 				}
+				cfgfile=new File(format(system.data_dir+"user/%04d.imap", user.number));
+				if (!cfgfile.open(cfgfile.exists ? 'r+':'w+', true)) {
+					tagged(tag, "NO", "Can't open imap state file");
+					return;
+				}
 				tagged(tag, "OK", "Howdy.");
 				state=Authenticated;
 			}
@@ -1211,6 +1219,18 @@ function send_updates()
 	}
 }
 
+function get_base_code()
+{
+	var base_code;
+
+	if (base.cfg !== undefined)
+		base_code = base.cfg.code;
+	else
+		base_code = 'mail';
+
+	return base_code;
+}
+
 function read_index(base)
 {
 	var i;
@@ -1224,10 +1244,7 @@ function read_index(base)
 	index.subnum=base.subnum;
 	index.total=base.total_msgs;
 
-	if(base.cfg != undefined)
-		index.code=base.cfg.code;
-	else
-		index.code='mail';
+	index.code = get_base_code();
 	for(i=0; i<index.total; i++) {
 		idx=base.get_msg_index(true,i);
 		if(idx==null)
@@ -1252,30 +1269,58 @@ function read_index(base)
 	return(index);
 }
 
+function apply_seen()
+{
+	var i;
+
+	if (index.code == 'mail')
+		return;
+	for(i in index.idx) {
+		// Fake \Seen
+		index.idx[i].attr &= ~MSG_READ;
+		if(saved_config[index.code].Seen != undefined && saved_config[index.code].Seen[index.idx[i].number] == 1)
+			index.idx[i].attr |= MSG_READ;
+	}
+}
+
+function lock_cfg()
+{
+	while(!cfgfile.lock(0, 1))
+		mswait(10);
+}
+
+function unlock_cfg()
+{
+	cfgfile.unlock(0, 1);
+}
+
 function exit_func()
 {
 	close_sub();
-	save_cfg();
+	unlock_cfg();
+	save_cfg(true);
 }
 
-function save_cfg()
+function save_cfg(lck)
 {
 	var cfg;
 	var s;
 
 	if(user.number > 0) {
-		cfg=new File(format(system.data_dir+"user/%04d.imap", user.number));
-		if(cfg.open()) {
-			for(sub in saved_config) {
-				s=saved_config[sub].Seen;
-				delete saved_config[sub].Seen;
-				cfg.iniSetObject(sub,saved_config[sub]);
-				if(s != undefined)
-					cfg.iniSetObject(sub+'.seen',s);
-				saved_config[sub].Seen=s;
-			}
-			cfg.close();
+		if (lck)
+			lock_cfg();
+		cfgfile.rewind();
+		for(sub in saved_config) {
+			s=saved_config[sub].Seen;
+			delete saved_config[sub].Seen;
+			cfgfile.iniSetObject(sub,saved_config[sub]);
+			if(s != undefined)
+				cfgfile.iniSetObject(sub+'.seen',s);
+			saved_config[sub].Seen=s;
 		}
+		cfgfile.flush();
+		if (lck)
+			unlock_cfg();
 	}
 }
 
@@ -1285,7 +1330,10 @@ function close_sub()
 		msg_ptrs[base.subnum]=scan_ptr;
 		if(msg_ptrs[base.subnum]!=orig_ptrs[base.subnum]) {
 			if(base.subnum==-1) {
-				saved_config.mail.scan_ptr=scan_ptr;
+				if (saved_config.mail.scan_ptr!=scan_ptr) {
+					saved_config.mail.scan_ptr=scan_ptr;
+					save_cfg(true);
+				}
 			}
 			else
 				msg_area.sub[base.cfg.code].scan_ptr=scan_ptr;
@@ -1311,7 +1359,7 @@ function open_sub(sub)
 			msg_ptrs[base.subnum]=msg_area.sub[base.cfg.code].scan_ptr;
 		}
 	}
-	read_cfg(sub);
+	read_cfg(sub, true);
 
 	scan_ptr=orig_ptrs[base.subnum];
 	update_status();
@@ -1490,15 +1538,12 @@ var authenticated_command_handlers = {
 			}
 			if(base.cfg != undefined && orig_ptrs[base.subnum]==undefined)
 				orig_ptrs[base.subnum]=msg_area.sub[base.cfg.code].scan_ptr;
-			
-			if(base.cfg != undefined)
-				base_code=base.cfg.code;
-			else
-				base_code='mail';
+
+			base_code = get_base_code();
 			if (saved_config[base_code] != undefined)
 				old_saved = saved_config[base_code];
-			read_cfg(base_code);
-			index=read_index(base);
+			read_cfg(base_code, true);
+			apply_seen();
 			delete saved_config[base_code];
 			if (old_saved != undefined)
 				saved_config[base_code] = old_saved;
@@ -1593,23 +1638,26 @@ function do_store(seq, uid, item, data)
 			}
 		}
 		if(mod_seen && base.cfg != undefined) {
-			if(saved_config[base.cfg.code] == undefined)
+			lock_cfg();
+			read_cfg(base.cfg.code, false);
+			if(saved_config[base.cfg.code] == undefined) {
 				saved_config[base.cfg.code] = {};
+			}
 			if(saved_config[base.cfg.code].Seen == undefined) {
 				saved_config[base.cfg.code].Seen = {};
 				saved_config[base.cfg.code].Seen[header.number]=0;
 			}
 			saved_config[base.cfg.code].Seen[seq[i]] ^= 1;
-			changed=true;
+			save_cfg(false);
+			apply_seen();
+			unlock_cfg();
 		}
 		if(!silent)
 			send_fetch_response(seq[i], ["FLAGS"], uid);
 	}
 	js.gc();
-	if(changed) {
-		save_cfg();
+	if(changed)
 		index=read_index(base);
-	}
 }
 
 function datestr(date)
@@ -1963,6 +2011,8 @@ var selected_command_handlers = {
 			var data_items=parse_data_items(args[2]);
 			var i;
 
+			read_cfg(get_base_code(), true);
+			apply_seen();
 			for(i in seq) {
 				send_fetch_response(seq[i], data_items, false);
 			}
@@ -2012,6 +2062,8 @@ var selected_command_handlers = {
 					}
 					seq=parse_seq_set(args[2],true);
 					data_items=parse_data_items(args[3]);
+					read_cfg(get_base_code(), true);
+					apply_seen();
 					for(i in seq) {
 						send_fetch_response(seq[i], data_items, true);
 					}
@@ -2046,9 +2098,8 @@ var selected_command_handlers = {
 	},
 };
 
-function read_cfg(sub)
+function read_cfg(sub, lck)
 {
-	var cfg=new File(format(system.data_dir+"user/%04d.imap",user.number));
 	var secs;
 	var sec;
 	var seen;
@@ -2057,30 +2108,32 @@ function read_cfg(sub)
 	if(saved_config[sub]==undefined)
 		saved_config[sub]={};
 
-	if(cfg.open("r+")) {
-		secs=cfg.iniGetSections();
-		for(sec in secs) {
-			if(secs[sec].search(/\.seen$/)!=-1) {
-				this_sec = secs[sec].replace(/(?:\.seen)+$/,'');
-				if(saved_config[this_sec]==undefined)
-					saved_config[this_sec]={};
-				saved_config[this_sec].Seen=cfg.iniGetObject(secs[sec]);
-				if(saved_config[this_sec].Seen==null)
-					saved_config[this_sec].Seen={};
-			}
-			else {
-				if(saved_config[secs[sec]] != undefined && saved_config[secs[sec]].Seen != undefined)
-					seen = saved_config[secs[sec]].Seen;
-				else
-					seen = {};
-				saved_config[secs[sec]]=cfg.iniGetObject(secs[sec]);
-				if(saved_config[secs[sec]]==null)
-					saved_config[secs[sec]]={};
-				saved_config[secs[sec]].Seen=seen;
-			}
+	if (lck)
+		lock_cfg();
+	cfgfile.rewind();
+	secs=cfgfile.iniGetSections();
+	for(sec in secs) {
+		if(secs[sec].search(/\.seen$/)!=-1) {
+			this_sec = secs[sec].replace(/(?:\.seen)+$/,'');
+			if(saved_config[this_sec]==undefined)
+				saved_config[this_sec]={};
+			saved_config[this_sec].Seen=cfgfile.iniGetObject(secs[sec]);
+			if(saved_config[this_sec].Seen==null)
+				saved_config[this_sec].Seen={};
+		}
+		else {
+			if(saved_config[secs[sec]] != undefined && saved_config[secs[sec]].Seen != undefined)
+				seen = saved_config[secs[sec]].Seen;
+			else
+				seen = {};
+			saved_config[secs[sec]]=cfgfile.iniGetObject(secs[sec]);
+			if(saved_config[secs[sec]]==null)
+				saved_config[secs[sec]]={};
+			saved_config[secs[sec]].Seen=seen;
 		}
-		cfg.close();
 	}
+	if (lck)
+		unlock_cfg();
 
 	if(sub == 'mail') {
 		msg_ptrs[-1]=saved_config.mail.scan_ptr;
-- 
GitLab