From 31bf9b9dd700c842d766f4b1b7f6a206288476f4 Mon Sep 17 00:00:00 2001 From: Deon George Date: Tue, 22 Oct 2019 21:57:25 +1100 Subject: [PATCH] Added Ansitex Data transport --- mods/ansitex_export.js | 241 +++++++++++++++++++++++++++++++++++++++++ mods/ansitex_save.js | 12 +- mods/load/texfuncs.js | 21 ++++ 3 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 mods/ansitex_export.js diff --git a/mods/ansitex_export.js b/mods/ansitex_export.js new file mode 100644 index 0000000..48a701c --- /dev/null +++ b/mods/ansitex_export.js @@ -0,0 +1,241 @@ +load('texdefs.js'); +load('texfuncs.js'); +load('smbdefs.js'); + +/** + * Work out which key should be used for a page, or validate if a key used is valid + * + * @param page + * @param signed + * @returns {*} + */ +function getKey(page,signed) { + log(LOG_DEBUG,'+ CHECKING key:'+signed+' for page: '+page); + + var f = new File(file_cfgname(system.ctrl_dir,'videotex.ini')); + var match = ''; + + if (f.open("r")) { + var BreakException = {}; + + // Default Key + var key = f.iniGetValue('prefix','key'); + + try { + f.iniGetSections("prefix:").forEach(function (prefix) { + var p = prefix.substr(7); + var pk = f.iniGetValue(prefix, 'Key', ''); + var re = new RegExp('^' + p, 'g'); + + // If it was signed, is the key a value one + if (signed && signed.length) { + if (pk == signed) { + //print('SIGNED key is valid:' + signed); + key = signed; + throw BreakException; + } + + // Which key should sign this page + } else if (page.toString().match(re) && (p.length > match.length)) { + match = p; + key = pk; + } + }); + + } catch (e) { + if (e !== BreakException) throw e; + } + } + + f.close(); + + log(LOG_DEBUG,'- key:'+key); + return key; +} + +/** + * Export message from message base + * + * @param msgbase + */ +function msgBaseExport(msgbase) { + var ini = new File(msgbase.file+'.ini'); + var i=0; + + if (ini.open("r")) { + export_ptr=ini.iniGetValue('videotex','export_ptr',0); + ini.close(); + + } else { + log(LOG_ERROR,'! ERROR: Unable to open INI for ['+msgbase.file+']'); + } + + // If pointer doesnt exist, reset it from the message base. + if (export_ptr == undefined) { + var f = new File(file_getcase(msgbase.file+'.sbl')); + + if (f.open('rb')) { + export_ptr = f.readBin(4); + f.close(); + } + } + + highest = export_ptr; + var total_msgs = msgbase.total_msgs; + + log(LOG_DEBUG,'| msgBaseExport: export_ptr='+export_ptr+'|last_msg='+msgbase.last_msg+'|total_msgs='+total_msgs); + + if (msgbase.last_msg >= export_ptr) + i = total_msgs-(msgbase.last_msg-export_ptr); + + for (; i highest) + highest = idx.number; + + // Only check messages from Videotex + if (idx.from != crc16_calc('videotex')) { + log(LOG_DEBUG,'! IGNORING: Message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + // Get the message header + var hdr = msgbase.get_msg_header(true,i); + + // Ignore deleted messages + if (hdr.attr&MSG_DELETE) { + log(LOG_DEBUG,'! IGNORING: Deleted message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + // Ignore locally posted messages + if (! hdr.from_net_type) { + log(LOG_DEBUG,'! IGNORING: Local message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + log(LOG_DEBUG,'+ PROCESSING: Message offset ['+i+'] in ['+msgbase.file+'] from ['+hdr.from_net_addr+'] ('+hdr.subject+')'); + + var body = msgbase.get_msg_body( + /* by_offset: */true, + i, + /* strip Ctrl-A */true, + /* rfc822-encoded: */false, + /* include tails: */false); + + t = new File(system.temp_dir+'videotex.gpg'); + if (! t.open('w+')) { + log(LOG_ERROR,'! ERROR: Unable to open temp file ['+system.temp_dir+'videotex.gpg'+']'); + exit(1); + } + + try { + t.write(base64_decode(body)); + t.close(); + + } catch (error) { + log(LOG_ERROR,error); + exit(1); + } + + // Check if the signature is good + result = system.exec('gpgv --keyring /opt/sbbs/mods/keys/pubring.kbx '+system.temp_dir+'videotex.gpg'); + + if (result !== 0 ) { + log(LOG_ERROR,'! ERROR: Invalid Signature for message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + if (file_exists(system.temp_dir+'videotex.tex')) + file_remove(system.temp_dir+'videotex.tex') + + // Check that the signature is allowed to author the frames + result = system.exec('gpg --homedir /opt/sbbs/mods/keys --batch --status-fd 3 -o '+system.temp_dir+'videotex.tex '+system.temp_dir+'videotex.gpg 3>'+system.temp_dir+'videotex.log'); + + if (result !== 0 ) { + log(LOG_ERROR,'! ERROR: Failed to extract message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + // Put frame in place. + f = new File(system.temp_dir+'videotex.tex'); + if (! f.exists || ! f.open('r')) { + log(LOG_ERROR,'! ERROR: Failed to open frame for message offset ['+i+'] in ['+msgbase.file+']'); + exit(1); + } + + try { + frame = JSON.parse(f.read()); + x = new Frame(0); + frame.render = x.render; + + // @todo Figure out how to delete this duplicate code + Object.defineProperty(frame,'page', { + get: function() {return this.frame+this.index} + }); + + x = null; + + } catch (error) { + log(LOG_ERROR,error); + continue; + } + + // Get the key that signed the message + f = new File(system.temp_dir+'videotex.log'); + if (! f.exists || ! f.open('r')) { + log(LOG_ERROR,'! ERROR: Failed to open gpg log for message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + var signed = ''; + while (string = f.readln()) { + var matches = string.match(/\s+GOODSIG\s+.*\<(.*)\>/); + + if (matches) { + signed = matches[1]; + } + } + f.close(); + + if (signed != getKey(frame.frame,signed)) { + log(LOG_ERROR,'! ERROR: Key ['+signed+' is not authorised for message offset ['+i+'] in ['+msgbase.file+']'); + continue; + } + + // Save the frame. + log(LOG_INFO,'Updating page ['+frame.page+'] from ['+hdr.from_net_addr+'] signed with ['+signed+']'); + saveFrame(frame); + } + + if (ini.open(file_exists(ini.name) ? 'r+':'w+')) { + ini.iniSetValue('videotex','export_ptr',highest); + ini.close(); + + } else { + log(LOG_ERROR,'! ERROR: Unable to save to INI for ['+msgbase.file+']'); + } +} + +var msgbase = new MsgBase(findMsgBase(null)); +log(LOG_DEBUG,'+ ANSITEX_EXPORT: Open message base in ['+msgbase.file+']'); + +if (! msgbase.open()) { + log(LOG_ERROR,'! ERROR: Unable to open ['+msgbase.file+']'); + exit(-1); +} + +msgBaseExport(msgbase); +msgbase.close(); diff --git a/mods/ansitex_save.js b/mods/ansitex_save.js index fca9039..fe85171 100644 --- a/mods/ansitex_save.js +++ b/mods/ansitex_save.js @@ -80,17 +80,7 @@ if (! send || ! frame || file) { } // Store the frame in file - file = system.text_dir+'ansitex/'+frame.page+'.tex'; - w = new File(file); - if (! w.open('w')) { - log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+frame.page); - exit(1); - } - - w.write(JSON.stringify(frame)); - w.close(); - - printf('Saved file: %s.tex',frame.page); + saveFrame(frame); } // @NOTE: We need to use a binary signature then base64 encode it, as mailers may strip 0x0a while messages are in transit. diff --git a/mods/load/texfuncs.js b/mods/load/texfuncs.js index 6447b2b..2560a1e 100644 --- a/mods/load/texfuncs.js +++ b/mods/load/texfuncs.js @@ -108,6 +108,8 @@ function getFrame(page) { get: function() {return this.frame+this.index} }); + x = null; + } catch (error) { log(LOG_ERROR,error); return null; @@ -155,6 +157,25 @@ function pageStr(page) { return page.frame+page.index; } +/** + * Save the frame + * + * @param frame + */ +function saveFrame(frame) { + file = system.text_dir+'ansitex/'+frame.page+'.tex'; + w = new File(file); + if (! w.open('w')) { + log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+frame.page); + exit(1); + } + + w.write(JSON.stringify(frame)); + w.close(); + + log(LOG_DEBUG,'Saved file: '+frame.page+'.tex'); +} + function sendBaseline(text,reposition) { console.pushxy(); console.gotoxy(0,24);