// $Id$ // vi: tabstop=4 // Synchronet Service for the Finger protocol (RFC 1288) // and/or the Active Users (SYSTAT) protocol (RFC 866) // Example configurations (in ctrl/services.ini) // [Finger] // Port=79 // Command=fingerservice.js // [ActiveUser-UDP] // Port=11 // Options=UDP // Command=fingerservice.js -u // Command-line options: // -n add to the Command line to eliminate user age and gender // information from the query results. // -a report aliases only (no real names) // -ff enable the findfile feature (requires a "guest" account) // -u report users only (ignore any request), a.k.a. Active Users protocol // !WARNING! // Finger is an open protocol utilizing no forms of authorization // or authentication. FINGER IS A KNOWN AND ACCEPTED SECURITY RISK. // Detailed information about your system and its users is made // available to ANYONE using this service. If there is anything in // this script that you do not want to be made available to anyone // and everyone, please comment-out (using /* and */) that portion // of the script or use the command-line options or modopts.ini to // disable those elements. "use strict"; const REVISION = "$Revision$".split(' ')[1]; var active_users = false; // Active-Users/SYSTAT protocol mode (Finger when false) var options = load({}, 'modopts.js', 'fingerservice'); if(!options) options = {}; if(options.include_age === undefined) options.include_age = true; if(options.include_gender === undefined) options.include_gender = true; if(options.include_real_name === undefined) options.include_real_name = true; if(options.findfile === undefined) options.findfile = false; if(options.bbslist === undefined) options.bbslist = false; load("nodedefs.js"); load("sockdefs.js"); load("sbbsdefs.js"); load("portdefs.js"); if(options.bbslist) var sbbslist = load({}, "sbbslist_lib.js"); for(i=0;i<argc;i++) { switch(argv[i].toLowerCase()) { case "-n": // no age or gender options.include_age = false; options.include_gender = false; break; case "-a": // aliases only options.include_real_name = false; break; case "-ff": // enable findfile (requires "guest" account) options.findfile=true; break; case "-u": // Active Users only active_users=true; break; } } var output_buf = ""; // Write a string to the client socket function write(str) { output_buf += str; } // Write all the output at once function flush() { client.socket.send(output_buf); } // Write a crlf terminated string to the client socket function writeln(str) { write(str + "\r\n"); } // Send the contents of a text file to the client socket function send_file(fname) { var f = new File(fname); if(!f.open("r")) return; var txt = f.readAll(); f.close(); for(var l in txt) writeln(txt[l]); } // Returns true if a connection on the local 'port' was succesful function test_port(port) { var sock = new Socket(); var success = sock.connect(system.host_name,port); sock.close(); return(success); } function xtrn_name(code) { if(js.global.xtrn_area==undefined) return(code); if(xtrn_area.prog!=undefined) if(xtrn_area.prog[code]!=undefined && xtrn_area.prog[code].name!=undefined) return(xtrn_area.prog[code].name); else { /* old way */ for(s in xtrn_area.sec_list) for(p in xtrn_area.sec_list[s].prog_list) if(xtrn_area.sec_list[s].prog_list[p].code.toLowerCase()==code.toLowerCase()) return(xtrn_area.sec_list[s].prog_list[p].name); } return(code); } function done() { flush(); exit(); } function node_misc(node) { var str = ''; if(node.misc&(NODE_AOFF|NODE_POFF|NODE_NMSG|NODE_MSGW)) { str += " ("; if(node.misc&NODE_LOCK) str += 'L'; if(node.misc&NODE_AOFF) str += 'A'; if(node.misc&NODE_POFF) str += 'P'; if(node.misc&(NODE_NMSG|NODE_MSGW)) str += 'M'; str += ")"; } return str; } var request=""; if(datagram || !active_users) { // Get Finger Request (the main entry point) if(datagram == undefined) // TCP request = client.socket.recvline(128 /*maxlen*/, 10 /*timeout*/); else // UDP request = datagram; if(request==null) { log(LOG_WARNING,"!TIMEOUT waiting for request"); exit(); } request = truncsp(request); log(LOG_DEBUG,"client request: " + request); if(request.substr(0,2).toUpperCase()=="/W") // "higher level of verbosity" request=request.slice(2); // ignored... while(request.charAt(0)==' ') // skip prepended spaces request=request.slice(1); } if(request=="") { // no specific user requested, give list of active users log("client requested active user list"); write(format("%-25.25s %-31.31s Time %3s %3s Node\r\n" ,"User","Action" ,options.include_age ? "Age":"" ,options.include_gender ? "Sex":"" )); var dashes="----------------------------------------"; write(format("%-25.25s %-31.31s %8.8s %3.3s %3.3s %4.4s\r\n" ,dashes,dashes,dashes ,options.include_age ? dashes : "" ,options.include_gender ? dashes : "" ,dashes)); var u = new User(1); for(n=0;n<system.node_list.length;n++) { var node = system.node_list[n]; if(node.status!=NODE_INUSE) continue; if(node.misc&NODE_ANON) continue; u.number=node.useron; if(node.action==NODE_XTRN && node.aux) action=format("running %s",xtrn_name(u.curxtrn)); else action=format(NodeAction[node.action],node.aux); action += node_misc(node); t=time()-u.logontime; if(t&0x80000000) t=0; write(format("%-25.25s %-31.31s%3u:%02u:%02u %3s %3s %4d\r\n" ,u.alias ,action ,Math.floor(t/(60*60)) ,Math.floor(t/60)%60 ,t%60 ,options.include_age ? u.age.toString() : "" ,options.include_gender ? u.gender : "" ,n+1 )); } done(); } // MODIFICATION BY MERLIN PART 1 STARTS HERE... if(options.findfile && 0) { // What is this supposed to do? if ((request.slice(0,9)) == "?findfile") { request=request.slice(9); request="findfile?".concat(request); } if ((request.slice(0,9)) == "?filefind") { request=request.slice(9); request="findfile?".concat(request); } } // MODIFICATION BY MERLIN PART 1 ENDS HERE if(request.charAt(0)=='?' || request.charAt(0)=='.') { // Handle "special" requests request=request.slice(1); switch(request.toLowerCase()) { case "ver": writeln("Synchronet Finger Service " + REVISION); writeln(server.version); writeln(system.version_notice + system.revision + system.beta_version); writeln("Compiled " + system.compiled_when + " with " + system.compiled_with); writeln(system.js_version); writeln(system.os_version); break; case "uptime": t=system.uptime; writeln("Duration: " + String(time()-t)); writeln(t); writeln(system.timestr(t) + " 0x" + t.toString(16)); case "time": t=time(); writeln(system.timestr(t) + " " + system.zonestr() + " 0x" + t.toString(16)); break; case "logon.lst": send_file(system.data_dir + "logon.lst"); break; case "auto.msg": send_file(system.data_dir + "msgs/auto.msg"); break; case "sockopts": for(i in sockopts) writeln(format("%-12s = %d" ,sockopts[i],client.socket.getoption(sockopts[i]))); break; case "stats": /* Statistics */ for(i in system.stats) writeln(format("%-25s = ", i) + system.stats[i]); var total = time()-system.uptime; var days = Math.floor(total/(24*60*60)); if(days) total%=(24*60*60); var hours = Math.floor(total/(60*60)); var min = (Math.floor(total/60))%60; var sec = total%60; writeln(format("uptime = %u days, %u hours, %u minutes and %u seconds" ,days,hours,min,sec)); break; case "stats.json": write(JSON.stringify(system.stats)); break; case "nodelist": var u = new User(1); for(n=0;n<system.node_list.length;n++) { write(format("Node %2d ",n+1)); var node = system.node_list[n]; if(node.status==NODE_INUSE && !(node.misc&NODE_ANON)) { u.number=node.useron; write(u.alias); if(options.include_age || options.include_gender) { write(" ("); if(options.include_age) write(u.age); if(options.include_gender) write((options.include_age ? ' ' : '') + u.gender); write(")"); } write(' '); if(node.action==NODE_XTRN && node.aux) write(format("running %s",xtrn_name(u.curxtrn))); else write(format(NodeAction[node.action],node.aux)); t=time()-u.logontime; if(t&0x80000000) t=0; write(format(" for %u minutes",Math.floor(t/60))); write(node_misc(node)); } else write(format(NodeStatus[node.status],node.aux)); write("\r\n"); } break; case "active-users.json": var u = new User(1); var list = []; for(var n=0;n<system.node_list.length;n++) { var node = system.node_list[n]; if(node.status!=NODE_INUSE) continue; if(node.misc&(NODE_ANON|NODE_POFF)) continue; u.number=node.useron; var action; if(node.action==NODE_XTRN && node.aux) action=format("running %s",xtrn_name(u.curxtrn)); else action=format(NodeAction[node.action] ,node.aux); var t = time()-u.logontime; if(t&0x80000000) t = 0; var obj = { name: u.alias, action: action, naction: node.action, aux: node.aux, xtrn: xtrn_name(u.curxtrn), timeon: t, node: n + 1, location: u.location }; if(options.include_age) obj.age = u.age; if(options.include_gender) obj.sex = u.gender; if(u.chat_settings & CHAT_NOPAGE) obj.do_not_disturb = true; if(node.misc&(NODE_NMSG|NODE_MSGW)) obj.msg_waiting = true; list.push(obj); } write(JSON.stringify(list)); break; case "services": /* Services running on this host */ var ports = []; for(i in standard_service_port) { if(i == "finger") continue; if(ports.indexOf(standard_service_port[i]) >= 0) // Already tested this port continue; ports.push(standard_service_port[i]); if(test_port(standard_service_port[i])) writeln(i); } break; case "bbslist": if(options.bbslist) { var list = sbbslist.read_list(); for(var i in list) writeln(list[i].name); } break; default: if(options.bbslist && request.indexOf("bbs:") == 0) { var list = sbbslist.read_list(); var index = sbbslist.system_index(list, request.slice(4)); if(index < 0) { writeln("!BBS NOT FOUND"); break; } writeln(JSON.stringify(list[index])); break; } if(file_exists(system.data_dir + "finger/" + file_getname(request))) { send_file(system.data_dir + "finger/" + file_getname(request)); break; } writeln("Supported special requests (prepended with '?' or '.'):"); writeln("\tver"); writeln("\ttime"); writeln("\tstats"); writeln("\tstats.json"); writeln("\tservices"); writeln("\tsockopts"); writeln("\tnodelist"); writeln("\tactive-users.json"); if(options.findfile) writeln("\tfindfile"); if(options.bbslist) { writeln("\tbbslist"); writeln("\tbbs:<name>"); } writeln("\tauto.msg"); writeln("\tlogon.lst"); var more = directory(system.data_dir + "finger/*"); for(var m in more) if(!file_isdir(more[m])) writeln("\t" + file_getname(more[m])); log(format("!UNSUPPORTED SPECIAL REQUEST: '%s'",request)); break; } done(); } // MODIFICATION BY MERLIN PART 3 STARTS HERE... if(options.findfile) { request=request.toLowerCase(); if ((request.slice(0,9)) == "filefind?") { request=request.slice(9); request="findfile?".concat(request); } if ((request == "filefind") || (request == "findfile") || (request == "")) { request="findfile?"; } if ((request.slice(0,9)) == "findfile?") { request=request.slice(9); write(format("\r\nFinger FindFile at %s",system.inetaddr)); if (request.indexOf("?") != -1) { writeln(""); writeln(""); writeln("Invalid: You can not use wildcards"); done(); } if (request.indexOf("*") != -1) { writeln(""); writeln(""); writeln("Invalid: You can not use wildcards"); done(); } if (request == "") { writeln(""); writeln(""); writeln(format("Invalid: You must use findfile?filename.ext@%s",system.inetaddr)); done(); } if(!login("guest")) { writeln("\r\n\r\nFailed to login as 'guest'"); done(); } var notfound = true; writeln(format(" searching for '%s'...",request)); log(format("FindFile searching for: '%s'",request)); writeln(""); for(l in file_area.lib_list) { for(d in file_area.lib_list[l].dir_list) { var dirpath=file_area.lib_list[l].dir_list[d].path; dirpath=dirpath.concat(request); if (file_exists(dirpath)) { var path="ftp://".concat(system.inetaddr,file_area.lib_list[l].dir_list[d].link,request); path=path.toLowerCase(); writeln(format("Found at %s",path)); notfound=false; } } } if (notfound) { writeln("Sorry, that file is not available here"); } done(); } } // MODIFICATION BY MERLIN PART 3 ENDS HERE... // User info is handled here var usernum=Number(request); if(!usernum) { var at = request.indexOf('@'); if(at>0) request = request.substr(0,at-1); usernum = system.matchuser(request); if(!usernum) { log(format("!UNKNOWN USER: '%s'",request)); exit(); } } var u = new User(usernum); if(u == null) { log(format("!INVALID USER NUMBER: %d",usernum)); exit(); } var uname = format("%s #%d", u.alias, u.number); write(format("User: %-30s", uname)); if(options.include_real_name) write(format(" In real life: %s", u.name)); write("\r\n"); write(format("From: %-36s Handle: %s\r\n",u.location,u.handle)); if(options.include_age) write(format("%-42s ", format("Birth: %s (Age: %u years)" , u.birthdate,u.age))); if(options.include_gender) write(format("Gender: %s", u.gender)); if(options.include_age || options.include_gender) write("\r\n"); write(format("Shell: %-34s Editor: %s\r\n" ,u.command_shell,u.editor)); write(format("Last login %s %s\r\nvia %s from %s [%s]\r\n" ,system.timestr(u.stats.laston_date) ,system.zonestr() ,u.connection ,u.host_name ,u.ip_address)); var plan; plan=format("%suser/%04d.plan",system.data_dir,u.number); if(file_exists(plan)) { write("Plan:\r\n"); send_file(plan); } else write("No plan.\r\n"); done(); /* End of fingerservice.js */