]> jfr.im git - irc/quakenet/newserv.git/commitdiff
Merge.
authorChris Porter <redacted>
Sun, 28 Mar 2010 00:25:43 +0000 (00:25 +0000)
committerChris Porter <redacted>
Sun, 28 Mar 2010 00:25:43 +0000 (00:25 +0000)
38 files changed:
chanserv/authcmds/authhistory.c
chanserv/authtracker/authtracker.c
chanserv/authtracker/authtracker.h
chanserv/authtracker/authtracker_db.c
chanserv/chancmds/giveowner.c
chanserv/chancmds/invite.c
chanserv/chanserv_messages.h
chanserv/chanservuser.c
helpmod2/helpmod2.c
lib/sstring-new.c
lib/sstring-old.c
lib/version.h
localuser/localuserchannel.c
localuser/localuserchannel.h
lua/lib/bootstrap.lua [new file with mode: 0644]
lua/lib/class.lua [new file with mode: 0644]
lua/lib/country.lua [new file with mode: 0644]
lua/lib/db.lua [new file with mode: 0644]
lua/lib/jit.lua [new file with mode: 0644]
lua/lib/json.lua [new file with mode: 0644]
lua/lib/minute_count.lua [new file with mode: 0644]
lua/lib/missing.lua [new file with mode: 0644]
lua/lib/quakenet.lua [new file with mode: 0644]
lua/lib/quakenet/access.lua [new file with mode: 0644]
lua/lib/quakenet/achievements.lua [new file with mode: 0644]
lua/lib/quakenet/iterators.lua [new file with mode: 0644]
lua/lib/quakenet/legacy.lua [new file with mode: 0644]
lua/lib/schedule.lua [new file with mode: 0644]
lua/lib/serialise.lua [new file with mode: 0644]
lua/lib/socket.lua [new file with mode: 0644]
lua/luacommands.c
proxyscan/Makefile.in
proxyscan/pns-scan.c [new file with mode: 0644]
proxyscan/proxyscan.c
proxyscan/proxyscan.h
proxyscan/proxyscan_newsearch.c [new file with mode: 0644]
proxyscan/proxyscancache.c
proxyscan/proxyscanhandlers.c

index 3621657d3a5935f7d4f8639f3022c912024ddf42..44f32a278a105cfb68edd88462ba789744828697 100644 (file)
@@ -3,7 +3,7 @@
  *
  * CMDNAME: authhistory
  * CMDLEVEL: QCMD_AUTHED
- * CMDARGS: 1
+ * CMDARGS: 2
  * CMDDESC: View auth history for an account.
  * CMDFUNC: csa_doauthhistory
  * CMDPROTO: int csa_doauthhistory(void *source, int cargc, char **cargv);
@@ -94,36 +94,67 @@ void csdb_doauthhistory_real(DBConn *dbconn, void *arg) {
 
 void csdb_retreiveauthhistory(nick *np, reguser *rup, int limit) {
   struct authhistoryinfo *ahi;
+  char limitstr[30];
+
+  if (limit) {
+    sprintf(limitstr, " limit %d",limit);
+  } else {
+    limitstr[0]='\0';
+  }
 
   ahi=(struct authhistoryinfo *)malloc(sizeof(struct authhistoryinfo));
   ahi->numeric=np->numeric;
   ahi->userID=rup->ID;
   q9a_asyncquery(csdb_doauthhistory_real, (void *)ahi,
     "SELECT userID, nick, username, host, authtime, disconnecttime, quitreason from chanserv.authhistory where "
-    "userID=%u order by authtime desc limit %d", rup->ID, limit);
+    "userID=%u order by authtime desc%s", rup->ID, limitstr);
 }
 
 int csa_doauthhistory(void *source, int cargc, char **cargv) {
   reguser *rup, *trup;
   nick *sender=source;
-
+  unsigned int arg=0;
+  unsigned int limit=10;
+  
   if (!(rup=getreguserfromnick(sender)))
     return CMD_ERROR;
 
-  if (cargc >= 1) {
-    if (!(trup=findreguser(sender, cargv[0])))
-      return CMD_ERROR;
+  if (cargc) {
+    if (!ircd_strcmp(cargv[0], "-a")) {
+      if (UHasOperPriv(rup))
+        limit=0;
+      
+      arg++;
+    }
+  }
 
-    /* don't allow non-opers to view oper auth history, but allow helpers to view non-oper history */
-    if ((trup != rup) && ((UHasOperPriv(trup) && !UHasOperPriv(rup)) || !UHasHelperPriv(rup))) {
-      chanservstdmessage(sender, QM_NOACCESSONUSER, "authhistory", cargv[0]);
+  if (cargc > arg) {
+    if (!(trup=findreguser(sender, cargv[arg])))
       return CMD_ERROR;
+
+    /* if target != command issuer */
+    if (trup != rup) {
+      /* only opers and helpers can view authhistory of other users */
+      if (!UHasHelperPriv(rup)) {
+        chanservstdmessage(sender, QM_NOACCESSONUSER, "authhistory", cargv[arg]);
+        return CMD_ERROR;
+      }
+
+      /* and only opers can view opers history */
+      if (UHasOperPriv(trup) && !UHasOperPriv(rup)) {
+        chanservwallmessage("%s (%s) just FAILED using AUTHHISTORY on %s", sender->nick, rup->username, trup->username);
+        chanservstdmessage(sender, QM_NOACCESSONUSER, "authhistory", cargv[arg]);
+        return CMD_ERROR;
+      }
+
+      /* checks passed */
+      chanservwallmessage("%s (%s) used AUTHHISTORY on %s", sender->nick, rup->username, trup->username);
     }
   } else {
     trup=rup;
   }
-  
-  csdb_retreiveauthhistory(sender, trup, 10);
+
+  csdb_retreiveauthhistory(sender, trup, limit);
 
   return CMD_OK;
 }
index 4a5a5a2cfcbf9e4e5bd63c56ff50a56c41dc65cd..e79914a837dfc74276b3b6afcbf0c268ff28cd3b 100644 (file)
@@ -11,12 +11,15 @@ DBModuleIdentifier authtrackerdb;
 void _init() {
   authtrackerdb = dbgetid();
 
+  chanservaddcommand("dumpauthtracker",QCMD_DEV,1,at_dumpdb,"Shows servers with dangling authtracker entries.\n","");
   at_finddanglingsessions();
 }
 
 void _fini() {
   at_hookfini();
   nsfreeall(POOL_AUTHTRACKER);
+  
+  chanservremovecommand("dumpauthtracker",at_dumpdb);
 
   dbfreeid(authtrackerdb);
 }
index 1e26d69fa3536b53d334d0473f828a309f3c6a6d..0947dccd948947ff8e48efe33b7c83c74eb1c2f5 100644 (file)
@@ -21,6 +21,7 @@ void at_lostnick(unsigned int numeric, unsigned long userid, time_t accountts, t
 int at_foundnick(unsigned int numeric, unsigned long userid, time_t accountts);
 void at_serverback(unsigned int server);
 void at_flushghosts();
+int at_dumpdb(void *source, int argc, char **argv);
 
 /* authtracker_hooks.c */
 unsigned long at_getuserid(nick *np);
index 127417f557a8bb1a007ef8587e76ae1423c50763..c6c760dde311fe49ae5bacc0b309879ee17ed972 100644 (file)
@@ -11,6 +11,7 @@
  */ 
 
 #include "authtracker.h"
+#include "../chanserv.h"
 #include "../../core/nsmalloc.h"
 #include "../../server/server.h"
 #include "../../irc/irc_config.h"
@@ -138,3 +139,25 @@ void at_flushghosts() {
       at_serverback(i);
   }
 }
+
+int at_dumpdb(void *source, int argc, char **argv) {
+  nick *np=source;
+  struct dangling_entry *dep;
+  unsigned int i,j,k;
+  
+  for(i=0;i<MAXSERVERS;i++) {
+    if (ds[i]) {
+      k=0;
+      for(j=0;j<DANGLING_HASHSIZE;j++) {
+        for (dep=ds[i]->de[j];dep;dep=dep->next) {
+          k++;
+        }
+      }
+      chanservsendmessage(np, "Server %d (%s) has %d entries.",i,longtonumeric(i,2),k);
+    }
+  }
+
+  chanservstdmessage(np,QM_ENDOFLIST);
+  
+  return CMD_OK;
+}
\ No newline at end of file
index 7cd7f6101e33687cc1e2aa602182e95fe788ae32..dab11172be5b172673bc7d099e66474578236638 100644 (file)
@@ -91,7 +91,8 @@ int csc_dogiveowner(void *source, int cargc, char **cargv) {
   /* OK, hash matches, do it. */
   oldflags = rcup->flags;
   rcup->flags |= QCUFLAG_OWNER;
-  
+  rcup->changetime=time(NULL);
+          
   chanservstdmessage(sender,QM_DONE);
 
   strcpy(flagbuf,printflags(oldflags,rcuflags));
index 3b9f71982fc1365edd2362609f5ce6df6c6bf12c..69ee2eb0221e7066b27a31cf63e124733adf364e 100644 (file)
@@ -55,7 +55,7 @@ int csc_doinvite(void *source, int cargc, char **cargv) {
         continue;
       }
       
-      localinvite(chanservnick, rcup->chan->index->channel, sender);
+      localinvite(chanservnick, rcup->chan->index, sender);
     }
     
     chanservstdmessage(sender, QM_DONE);
@@ -67,7 +67,7 @@ int csc_doinvite(void *source, int cargc, char **cargv) {
     return CMD_ERROR;
 
   if (cip->channel) {
-    localinvite(chanservnick, cip->channel, sender);
+    localinvite(chanservnick, cip, sender);
   }
   chanservstdmessage(sender, QM_DONE);
  
index d2784ab31c56760b75a14d56c59600c7bb9447d6..696c69b2467899235ac80de1cc09626175307313 100644 (file)
@@ -65,7 +65,7 @@ BeginMessages() {
   msg(QM_NOTPROTECTED, "Sorry, your nick $0 is not protected.", "s"),
   msg(QM_SAMEAUTH, "$0 is authed as $1. Cannot reclaim nick.", "ss"),
   msg(QM_AUTHFAIL, "Username or password incorrect.", ""),
-  msg(QM_AUTHOK, "You are now logged in as $0.", "s"),
+  msg(QM_AUTHOK, "You are now logged in as $0.\nRemember: NO-ONE from QuakeNet will ever ask for your password.  NEVER send your password to ANYONE except Q@CServe.quakenet.org.", "s"),
   msg(QM_NEWACCOUNT, "Account $0 created successfully.\nInformation about how to access and use your new account will be sent to your email address, $1.\nIf you do not see an email soon be sure to check your spam folder.", "ss"),
   msg(QM_AUTHNAMEINUSE, "Someone already has the account name $0!\nIf this is your account use AUTH to login, otherwise please change your nick using /NICK and try again.", "s"),
   msg(QM_ALREADYONCHAN, "You're already on $0.", "s"),
index 8e6a2ef59e14f593267b40a665a03bbdd21dca75..7bbcd692cba9db674825e34a35c6591349837bbc 100644 (file)
@@ -787,7 +787,6 @@ void cs_doallautomodes(nick *np) {
           }
         }
 
-
        localsetmodeinit(&changes, rcup->chan->index->channel, chanservnick);
        if (*lp & CUMODE_OP) {
          if (!IsService(np) && (CUIsDeny(rcup) || (CIsBitch(rcup->chan) && !CUHasOpPriv(rcup))))
@@ -812,11 +811,16 @@ void cs_doallautomodes(nick *np) {
       } else {
        /* Channel exists but user is not joined: invite if they are +j-b */
        if (CUIsAutoInvite(rcup) && CUKnown(rcup) && !CUIsBanned(rcup)) {
-         localinvite(chanservnick, rcup->chan->index->channel, np);
+         localinvite(chanservnick, rcup->chan->index, np);
        }
       }
-    }
-  }
+    } /* if (rcup->chan->index->channel) */ else {
+      /* Channel doesn't currently exist - send invite anyway for +j */
+      if (CUIsAutoInvite(rcup) && CUKnown(rcup) && !CUIsBanned(rcup)) {
+        localinvite(chanservnick, rcup->chan->index, np);
+      }
+    } 
+  } /* for */
 }
 
 void cs_checknickbans(nick *np) {
index d625833942766e4d181507e1e1400f4ace75cbbe..c36af709104ba23bce6778598098b4c43e1dd634 100644 (file)
@@ -185,7 +185,7 @@ void helpmod_invite(hchannel *hchan, huser *husr)
     if (hchan->flags & H_PASSIVE)
         return;
 
-    localinvite(helpmodnick, hchan->real_channel, husr->real_user);
+    localinvite(helpmodnick, hchan->real_channel->index, husr->real_user);
 }
 
 static void hmode_set_channel(hchannel  *hchan)
index 22f4f7116ff508d7571ccd03a92e8e07f930c801..90581f93af0b93ae2c0cea65d9e5637b26424f8b 100644 (file)
@@ -13,7 +13,7 @@
 
 #include <assert.h>
 #include <stdlib.h>
-#define __USE_GNU
+#define _GNU_SOURCE
 #include <string.h>
 
 /* List of free stuff */
index 4e6e5c1ad9da9028475b54f8048f293fc0c66329..028e96889b07707223039591cd6fba801d0cfae4 100644 (file)
@@ -11,7 +11,7 @@
 
 #include <assert.h>
 #include <stdlib.h>
-#define __USE_GNU
+#define _GNU_SOURCE
 #include <string.h>
 
 /* List of free stuff */
index 0b37a9dadd1f280036f9f5f9bae4001d168c0248..447ae8853c9ee38c594021e3182e734c32242ba7 100644 (file)
@@ -10,6 +10,6 @@
 #define _BUILDID "" XStringify(BUILDID) ""
 #endif
 
-#define MODULE_VERSION(id) const char *_version(const char **version) { *version=(id[0]=='\0')?NULL:id; return _BUILDID; };
+#define MODULE_VERSION(id) const char *_version(const char **version) { *version=(id[0]=='\0')?NULL:id; return _BUILDID; }
 
 #endif
index 86f4ed11e2b388c1fca80f5bd2e30dd11169d988..67d0b1b629331d37790f7c073cd933a936481bed 100644 (file)
@@ -1066,7 +1066,7 @@ void sendopnoticetochannel(nick *source, channel *cp, char *format, ... ) {
   }
 }
 
-void localinvite(nick *source, channel *cp, nick *target) {
+void localinvite(nick *source, chanindex *cip, nick *target) {
 
   /* Servers can't send invites */
   if (!source) 
@@ -1078,7 +1078,7 @@ void localinvite(nick *source, channel *cp, nick *target) {
    * argument */
   if (connected) {
     irc_send("%s I %s :%s",longtonumeric(source->numeric,5),
-            target->nick, cp->index->name->content);
+            target->nick, cip->name->content);
   }
 }
   
index 5f450ee9975141d3c1a5081931478e7f365e9805..14d534373e1711a4981b35aeb553bf4d431084c7 100644 (file)
@@ -41,7 +41,7 @@ void localkickuser(nick *np, channel *cp, nick *target, const char *message);
 void localusermodechange(nick *np, channel *cp, char *modes);
 void sendmessagetochannel(nick *source, channel *cp, char *format, ... ) __attribute__ ((format (printf, 3, 4)));
 void sendopnoticetochannel(nick *source, channel *cp, char *format, ... ) __attribute__ ((format (printf, 3, 4)));
-void localinvite(nick *source, channel *cp, nick *target);
+void localinvite(nick *source, chanindex *cip, nick *target);
 
 void localsetmodeinit (modechanges *changes, channel *cp, nick *np);
 void localdosetmode_nick (modechanges *changes, nick *target, short modes);
diff --git a/lua/lib/bootstrap.lua b/lua/lib/bootstrap.lua
new file mode 100644 (file)
index 0000000..6555c96
--- /dev/null
@@ -0,0 +1,32 @@
+require("lib/quakenet")
+require("lib/missing")
+require("lib/schedule")
+require("lib/serialise")
+require("lib/country")
+require("lib/db")
+require("lib/socket")
+require("lib/json")
+require("lib/class")
+
+local LASTERROR = 0
+local NOTREPORTING = false
+
+function scripterror(err)
+  local cerror = os.time()
+  if cerror > LASTERROR then
+    LASTERROR = cerror
+    NOTREPORTING = false
+  end
+
+  if not NOTREPORTING then
+    chanmsgn(traceback(err, 2))
+
+    LASTERROR = LASTERROR + 1
+    if LASTERROR > cerror + 10 then
+      NOTREPORTING = true
+      chanmsg("Too many errors for this script -- no longer reporting")
+    end
+  end
+end
+
+math.randomseed(os.time())
diff --git a/lua/lib/class.lua b/lua/lib/class.lua
new file mode 100644 (file)
index 0000000..6ec23cf
--- /dev/null
@@ -0,0 +1,46 @@
+-- class.lua
+-- Compatible with Lua 5.1 (not 5.0).
+function class(base,ctor)
+  local c = {}     -- a new class instance
+  if not ctor and type(base) == 'function' then
+      ctor = base
+      base = nil
+  elseif type(base) == 'table' then
+   -- our new class is a shallow copy of the base class!
+      for i,v in pairs(base) do
+          c[i] = v
+      end
+      c._base = base
+  end
+  -- the class will be the metatable for all its objects,
+  -- and they will look up their methods in it.
+  c.__index = c
+
+  -- expose a ctor which can be called by <classname>(<args>)
+  local mt = {}
+  mt.__call = function(class_tbl,...)
+    local obj = {}
+    setmetatable(obj,c)
+    if ctor then
+       ctor(obj,...)
+    else 
+    -- make sure that any stuff from the base class is initialized!
+       if base and base.init then
+         base.init(obj,...)
+       end
+    end
+    return obj
+  end
+  c.init = ctor
+  c.is_a = function(self,klass)
+      local m = getmetatable(self)
+      while m do 
+         if m == klass then return true end
+         m = m._base
+      end
+      return false
+    end
+  setmetatable(c,mt)
+  return c
+end
+
diff --git a/lua/lib/country.lua b/lua/lib/country.lua
new file mode 100644 (file)
index 0000000..50f6520
--- /dev/null
@@ -0,0 +1,33 @@
+local countries = { "--","AP","EU","AD","AE","AF","AG","AI","AL","AM","AN","AO","AQ","AR","AS","AT","AU","AW","AZ","BA","BB","BD","BE","BF","BG","BH","BI","BJ","BM","BN","BO","BR","BS","BT","BV","BW","BY","BZ","CA","CC","CD","CF","CG","CH","CI","CK","CL","CM","CN","CO","CR","CU","CV","CX","CY","CZ","DE","DJ","DK","DM","DO","DZ","EC","EE","EG","EH","ER","ES","ET","FI","FJ","FK","FM","FO","FR","FX","GA","GB","GD","GE","GF","GH","GI","GL","GM","GN","GP","GQ","GR","GS","GT","GU","GW","GY","HK","HM","HN","HR","HT","HU","ID","IE","IL","IN","IO","IQ","IR","IS","IT","JM","JO","JP","KE","KG","KH","KI","KM","KN","KP","KR","KW","KY","KZ","LA","LB","LC","LI","LK","LR","LS","LT","LU","LV","LY","MA","MC","MD","MG","MH","MK","ML","MM","MN","MO","MP","MQ","MR","MS","MT","MU","MV","MW","MX","MY","MZ","NA","NC","NE","NF","NG","NI","NL","NO","NP","NR","NU","NZ","OM","PA","PE","PF","PG","PH","PK","PL","PM","PN","PR","PS","PT","PW","PY","QA","RE","RO","RU","RW","SA","SB","SC","SD","SE","SG","SH","SI","SJ","SK","SL","SM","SN","SO","SR","ST","SV","SY","SZ","TC","TD","TF","TG","TH","TJ","TK","TM","TN","TO","TP","TR","TT","TV","TW","TZ","UA","UG","UM","US","UY","UZ","VA","VC","VE","VG","VI","VN","VU","WF","WS","YE","YT","YU","ZA","ZM","ZR","ZW","A1","A2","O1" }
+local countryhash
+
+function countrylookup(id)
+  return countries[id + 1]
+end
+
+function countrynamelookup(country)
+  if not countryhash then
+    local i = 0
+    countryhash = {}
+
+    for k, v in pairs(countries) do
+      countryhash[v] = k - 1
+    end
+  end
+
+  return countryhash[country]
+end
+
+function countryindex(country)
+  local i = 1
+
+  for v in pairs(countries) do
+    if countries[i] == country then
+      return i - 1
+    end
+    i = i + 1
+  end
+
+  return -1
+end
+
diff --git a/lua/lib/db.lua b/lua/lib/db.lua
new file mode 100644 (file)
index 0000000..6bcf90e
--- /dev/null
@@ -0,0 +1,32 @@
+function db_queryiter()
+  local c = db_numrows()
+  local i = -1
+  local f = db_numfields()
+  local gb = db_getvalue
+
+  return function()
+    i = i + 1
+    if i == c then
+      return nil
+    end
+
+    local t = {}
+    for j=0,f do
+      table.insert(t, gb(i, j))
+    end
+
+    return t
+  end
+end
+
+local realquery = db_query
+
+function db_query(x, fn, t)
+  if not fn then
+    realquery(x)
+  elseif not t then
+    realquery(x, fn, nil)
+  else
+    realquery(x, fn, t)
+  end
+end
diff --git a/lua/lib/jit.lua b/lua/lib/jit.lua
new file mode 100644 (file)
index 0000000..d81d74b
--- /dev/null
@@ -0,0 +1,2 @@
+--require("lib/jit.opt").start()
+
diff --git a/lua/lib/json.lua b/lua/lib/json.lua
new file mode 100644 (file)
index 0000000..1af8e1d
--- /dev/null
@@ -0,0 +1,377 @@
+-----------------------------------------------------------------------------
+-- JSON4Lua: JSON encoding / decoding support for the Lua language.
+-- json Module.
+-- Author: Craig Mason-Jones
+-- Homepage: http://json.luaforge.net/
+-- Version: 0.9.20
+-- This module is released under the The GNU General Public License (GPL).
+-- Please see LICENCE.txt for details.
+--
+-- USAGE:
+-- This module exposes two functions:
+--   encode(o)
+--     Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string.
+--   decode(json_string)
+--     Returns a Lua object populated with the data encoded in the JSON string json_string.
+--
+-- REQUIREMENTS:
+--   compat-5.1 if using Lua 5.0
+--
+-- CHANGELOG
+--   0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). 
+--          Fixed Lua 5.1 compatibility issues.
+--             Introduced json.null to have null values in associative arrays.
+--          encode() performance improvement (more than 50%) through table.concat rather than ..
+--          Introduced decode ability to ignore /**/ comments in the JSON string.
+--   0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays.
+-----------------------------------------------------------------------------
+
+-----------------------------------------------------------------------------
+-- Imports and dependencies
+-----------------------------------------------------------------------------
+local math = require('math')
+local string = require("string")
+local table = require("table")
+
+local base = _G
+
+-----------------------------------------------------------------------------
+-- Module declaration
+-----------------------------------------------------------------------------
+module("json")
+
+-- Public functions
+
+-- Private functions
+local decode_scanArray
+local decode_scanComment
+local decode_scanConstant
+local decode_scanNumber
+local decode_scanObject
+local decode_scanString
+local decode_scanWhitespace
+local encodeString
+local isArray
+local isEncodable
+
+-----------------------------------------------------------------------------
+-- PUBLIC FUNCTIONS
+-----------------------------------------------------------------------------
+--- Encodes an arbitrary Lua object / variable.
+-- @param v The Lua object / variable to be JSON encoded.
+-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode)
+function encode (v)
+  -- Handle nil values
+  if v==nil then
+    return "null"
+  end
+  
+  local vtype = base.type(v)  
+
+  -- Handle strings
+  if vtype=='string' then    
+    return '"' .. encodeString(v) .. '"'           -- Need to handle encoding in string
+  end
+  
+  -- Handle booleans
+  if vtype=='number' or vtype=='boolean' then
+    return base.tostring(v)
+  end
+  
+  -- Handle tables
+  if vtype=='table' then
+    local rval = {}
+    -- Consider arrays separately
+    local bArray, maxCount = isArray(v)
+    if bArray then
+      for i = 1,maxCount do
+        table.insert(rval, encode(v[i]))
+      end
+    else       -- An object, not an array
+      for i,j in base.pairs(v) do
+        if isEncodable(i) and isEncodable(j) then
+          table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j))
+        end
+      end
+    end
+    if bArray then
+      return '[' .. table.concat(rval,',') ..']'
+    else
+      return '{' .. table.concat(rval,',') .. '}'
+    end
+  end
+  
+  -- Handle null values
+  if vtype=='function' and v==null then
+    return 'null'
+  end
+  
+  base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v))
+end
+
+
+--- Decodes a JSON string and returns the decoded value as a Lua data structure / value.
+-- @param s The string to scan.
+-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1.
+-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil,
+-- and the position of the first character after
+-- the scanned JSON object.
+function decode(s, startPos)
+  startPos = startPos and startPos or 1
+  startPos = decode_scanWhitespace(s,startPos)
+  base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']')
+  local curChar = string.sub(s,startPos,startPos)
+  -- Object
+  if curChar=='{' then
+    return decode_scanObject(s,startPos)
+  end
+  -- Array
+  if curChar=='[' then
+    return decode_scanArray(s,startPos)
+  end
+  -- Number
+  if string.find("+-0123456789.e", curChar, 1, true) then
+    return decode_scanNumber(s,startPos)
+  end
+  -- String
+  if curChar==[["]] or curChar==[[']] then
+    return decode_scanString(s,startPos)
+  end
+  if string.sub(s,startPos,startPos+1)=='/*' then
+    return decode(s, decode_scanComment(s,startPos))
+  end
+  -- Otherwise, it must be a constant
+  return decode_scanConstant(s,startPos)
+end
+
+--- The null function allows one to specify a null value in an associative array (which is otherwise
+-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null }
+function null()
+  return null -- so json.null() will also return null ;-)
+end
+-----------------------------------------------------------------------------
+-- Internal, PRIVATE functions.
+-- Following a Python-like convention, I have prefixed all these 'PRIVATE'
+-- functions with an underscore.
+-----------------------------------------------------------------------------
+
+--- Scans an array from JSON into a Lua object
+-- startPos begins at the start of the array.
+-- Returns the array and the next starting position
+-- @param s The string being scanned.
+-- @param startPos The starting position for the scan.
+-- @return table, int The scanned array as a table, and the position of the next character to scan.
+function decode_scanArray(s,startPos)
+  local array = {}     -- The return value
+  local stringLen = string.len(s)
+  base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s )
+  startPos = startPos + 1
+  -- Infinite loop for array elements
+  repeat
+    startPos = decode_scanWhitespace(s,startPos)
+    base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.')
+    local curChar = string.sub(s,startPos,startPos)
+    if (curChar==']') then
+      return array, startPos+1
+    end
+    if (curChar==',') then
+      startPos = decode_scanWhitespace(s,startPos+1)
+    end
+    base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.')
+    object, startPos = decode(s,startPos)
+    table.insert(array,object)
+  until false
+end
+
+--- Scans a comment and discards the comment.
+-- Returns the position of the next character following the comment.
+-- @param string s The JSON string to scan.
+-- @param int startPos The starting position of the comment
+function decode_scanComment(s, startPos)
+  base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos)
+  local endPos = string.find(s,'*/',startPos+2)
+  base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos)
+  return endPos+2  
+end
+
+--- Scans for given constants: true, false or null
+-- Returns the appropriate Lua type, and the position of the next character to read.
+-- @param s The string being scanned.
+-- @param startPos The position in the string at which to start scanning.
+-- @return object, int The object (true, false or nil) and the position at which the next character should be 
+-- scanned.
+function decode_scanConstant(s, startPos)
+  local consts = { ["true"] = true, ["false"] = false, ["null"] = nil }
+  local constNames = {"true","false","null"}
+
+  for i,k in base.pairs(constNames) do
+    --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k)
+    if string.sub(s,startPos, startPos + string.len(k) -1 )==k then
+      return consts[k], startPos + string.len(k)
+    end
+  end
+  base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos)
+end
+
+--- Scans a number from the JSON encoded string.
+-- (in fact, also is able to scan numeric +- eqns, which is not
+-- in the JSON spec.)
+-- Returns the number, and the position of the next character
+-- after the number.
+-- @param s The string being scanned.
+-- @param startPos The position at which to start scanning.
+-- @return number, int The extracted number and the position of the next character to scan.
+function decode_scanNumber(s,startPos)
+  local endPos = startPos+1
+  local stringLen = string.len(s)
+  local acceptableChars = "+-0123456789.e"
+  while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true)
+       and endPos<=stringLen
+       ) do
+    endPos = endPos + 1
+  end
+  local stringValue = 'return ' .. string.sub(s,startPos, endPos-1)
+  local stringEval = base.loadstring(stringValue)
+  base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos)
+  return stringEval(), endPos
+end
+
+--- Scans a JSON object into a Lua object.
+-- startPos begins at the start of the object.
+-- Returns the object and the next starting position.
+-- @param s The string being scanned.
+-- @param startPos The starting position of the scan.
+-- @return table, int The scanned object as a table and the position of the next character to scan.
+function decode_scanObject(s,startPos)
+  local object = {}
+  local stringLen = string.len(s)
+  local key, value
+  base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s)
+  startPos = startPos + 1
+  repeat
+    startPos = decode_scanWhitespace(s,startPos)
+    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.')
+    local curChar = string.sub(s,startPos,startPos)
+    if (curChar=='}') then
+      return object,startPos+1
+    end
+    if (curChar==',') then
+      startPos = decode_scanWhitespace(s,startPos+1)
+    end
+    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.')
+    -- Scan the key
+    key, startPos = decode(s,startPos)
+    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+    startPos = decode_scanWhitespace(s,startPos)
+    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+    base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos)
+    startPos = decode_scanWhitespace(s,startPos+1)
+    base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key)
+    value, startPos = decode(s,startPos)
+    object[key]=value
+  until false  -- infinite loop while key-value pairs are found
+end
+
+--- Scans a JSON string from the opening inverted comma or single quote to the
+-- end of the string.
+-- Returns the string extracted as a Lua string,
+-- and the position of the next non-string character
+-- (after the closing inverted comma or single quote).
+-- @param s The string being scanned.
+-- @param startPos The starting position of the scan.
+-- @return string, int The extracted string as a Lua string, and the next character to parse.
+function decode_scanString(s,startPos)
+  base.assert(startPos, 'decode_scanString(..) called without start position')
+  local startChar = string.sub(s,startPos,startPos)
+  base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string')
+  local escaped = false
+  local endPos = startPos + 1
+  local bEnded = false
+  local stringLen = string.len(s)
+  repeat
+    local curChar = string.sub(s,endPos,endPos)
+    if not escaped then        
+      if curChar==[[\]] then
+        escaped = true
+      else
+        bEnded = curChar==startChar
+      end
+    else
+      -- If we're escaped, we accept the current character come what may
+      escaped = false
+    end
+    endPos = endPos + 1
+    base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos)
+  until bEnded
+  local stringValue = 'return ' .. string.sub(s, startPos, endPos-1)
+  local stringEval = base.loadstring(stringValue)
+  base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos)
+  return stringEval(), endPos  
+end
+
+--- Scans a JSON string skipping all whitespace from the current start position.
+-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached.
+-- @param s The string being scanned
+-- @param startPos The starting position where we should begin removing whitespace.
+-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string
+-- was reached.
+function decode_scanWhitespace(s,startPos)
+  local whitespace=" \n\r\t"
+  local stringLen = string.len(s)
+  while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true)  and startPos <= stringLen) do
+    startPos = startPos + 1
+  end
+  return startPos
+end
+
+--- Encodes a string to be JSON-compatible.
+-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-)
+-- @param s The string to return as a JSON encoded (i.e. backquoted string)
+-- @return The string appropriately escaped.
+function encodeString(s)
+  s = string.gsub(s,'\\','\\\\')
+  s = string.gsub(s,'"','\\"')
+-- slug
+--  s = string.gsub(s,"'","\\'")
+  s = string.gsub(s,'\n','\\n')
+  s = string.gsub(s,'\t','\\t')
+  return s 
+end
+
+-- Determines whether the given Lua type is an array or a table / dictionary.
+-- We consider any table an array if it has indexes 1..n for its n items, and no
+-- other data in the table.
+-- I think this method is currently a little 'flaky', but can't think of a good way around it yet...
+-- @param t The table to evaluate as an array
+-- @return boolean, number True if the table can be represented as an array, false otherwise. If true,
+-- the second returned value is the maximum
+-- number of indexed elements in the array. 
+function isArray(t)
+  -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable 
+  -- (with the possible exception of 'n')
+  local maxIndex = 0
+  for k,v in base.pairs(t) do
+    if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then     -- k,v is an indexed pair
+      if (not isEncodable(v)) then return false end    -- All array elements must be encodable
+      maxIndex = math.max(maxIndex,k)
+    else
+      if (k=='n') then
+        if v ~= table.getn(t) then return false end  -- False if n does not hold the number of elements
+      else -- Else of (k=='n')
+        if isEncodable(v) then return false end
+      end  -- End of (k~='n')
+    end -- End of k,v not an indexed pair
+  end  -- End of loop across all pairs
+  return true, maxIndex
+end
+
+--- Determines whether the given Lua object / table / variable can be JSON encoded. The only
+-- types that are JSON encodable are: string, boolean, number, nil, table and json.null.
+-- In this implementation, all other types are ignored.
+-- @param o The object to examine.
+-- @return boolean True if the object should be JSON encoded, false if it should be ignored.
+function isEncodable(o)
+  local t = base.type(o)
+  return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) 
+end
+
diff --git a/lua/lib/minute_count.lua b/lua/lib/minute_count.lua
new file mode 100644 (file)
index 0000000..53d1d8d
--- /dev/null
@@ -0,0 +1,24 @@
+MinuteCount = class(function(obj, minutes)
+  obj.minutes = minutes
+  obj.data = {}
+  obj.current = 0
+end)
+
+function MinuteCount:add()
+  self.current = self.current + 1
+end
+
+function MinuteCount:moveon()
+  table.insert(self.data, 0, self.current)
+  table.remove(self.data, self.minutes)
+  self.current = 0
+end
+
+function MinuteCount:sum()
+  local sum = self.current
+  for _, v in ipairs(self.data) do
+    sum = sum + v
+  end
+
+  return sum
+end
diff --git a/lua/lib/missing.lua b/lua/lib/missing.lua
new file mode 100644 (file)
index 0000000..2fde729
--- /dev/null
@@ -0,0 +1,84 @@
+function tablelen(tab)
+  local count = 0
+
+  for _, _ in pairs(tab) do
+    count = count + 1
+  end
+
+  return count
+end
+
+function tablesequal(a, b)
+  local k, v
+
+  for k, v in pairs(a) do
+    if not b[k] or b[k] ~= v then
+      return false
+    end
+  end
+
+  for k, v in pairs(b) do
+    if not a[k] or a[k] ~= v then
+      return false
+    end
+  end
+
+  return true
+end
+
+function trim(s)
+  return (string.gsub(s, "^%s*(.-)%s*$", "%1"))
+end
+
+function explode(d,p,m)
+  t={}
+  ll=0
+  f=0
+  if m and m < 1 then
+    return { p }
+  end
+
+  while true do
+    l=string.find(p,d,ll+1,true) -- find the next d in the string
+    if l~=nil then -- if "not not" found then..
+      table.insert(t, string.sub(p,ll,l-1)) -- Save it in our array.
+      ll=l+1 -- save just after where we found it for searching next time.
+      f = f + 1
+      if m and f >= m then
+        table.insert(t, string.sub(p,ll))
+        break
+      end
+    else
+      table.insert(t, string.sub(p,ll)) -- Save what's left in our array.
+      break -- Break at end, as it should be, according to the lua manual.
+    end
+  end
+  return t
+end
+
+function tableempty(table)
+  for k, v in pairs(table) do
+    return false
+  end
+
+  return true
+end
+
+function urlencode(str)
+  if (str) then
+    str = string.gsub (str, "\n", "\r\n")
+    str = string.gsub (str, "([^%w ])",
+        function (c) return string.format ("%%%02X", string.byte(c)) end)
+    str = string.gsub (str, " ", "+")
+  end
+  return str   
+end
+
+function matchtoregex(match)
+  local x = match:gsub("([%^%$%(%)%%%.%[%]%+%-])", "%%%1")
+  x = x:gsub("%*", ".*")
+  x = x:gsub("%?", ".")
+  x = "^" .. x .. "$"
+  return x
+end
+
diff --git a/lua/lib/quakenet.lua b/lua/lib/quakenet.lua
new file mode 100644 (file)
index 0000000..f460c7e
--- /dev/null
@@ -0,0 +1,51 @@
+require("lib/quakenet/access")
+require("lib/quakenet/iterators")
+require("lib/quakenet/legacy")
+require("lib/quakenet/achievements")
+
+function chanmsg(a)
+  irc_smsg(a, "#qnet.keepout")
+end
+
+function crapchanmsg(a)
+  irc_smsg(a, "#qnet.trj")
+end
+
+function statusmsg(a)
+  irc_smsg("dd," .. a, "#qnet.keepout")
+end
+
+function chanmsgn(t)
+  string.gsub(t, "[^\r\n]+", chanmsg)
+end
+
+function noticen(n, t)
+  string.gsub(t, "[^\r\n]+", function(s) irc_notice(n, s) end);
+end
+
+local irc_localrealovmode = irc_localovmode;
+
+function irc_localovmode(source, chan, ...)
+  if type(...) == 'table' then
+    irc_localrealovmode(source, chan, ...)
+  else
+    irc_localrealovmode(source, chan, { ... })
+  end
+end
+
+function irc_localaction(n, c, m)
+  irc_localchanmsg(n, c, string.char(1) .. "ACTION " .. m .. string.char(1))
+end
+
+function irctolower(l)
+  l = l:lower()
+  l = l:gsub("%[", "{")
+  l = l:gsub("%]", "}")
+  l = l:gsub("\\", "|")
+  l = l:gsub("%^", "~")
+  return l
+end
+
+function irc_localregisteruser(nickname, ident, hostname, realname, account, usermodes, handler_function)
+  return irc_localregisteruserid(nickname, ident, hostname, realname, account, 0, usermodes, handler_function)
+end
diff --git a/lua/lib/quakenet/access.lua b/lua/lib/quakenet/access.lua
new file mode 100644 (file)
index 0000000..9ead23b
--- /dev/null
@@ -0,0 +1,9 @@
+function onstaff(nick)
+  return irc_nickonchan(nick, "#qnet.staff")
+end
+
+function ontlz(nick)
+  return irc_nickonchan(nick, "#twilightzone")
+end
+
+
diff --git a/lua/lib/quakenet/achievements.lua b/lua/lib/quakenet/achievements.lua
new file mode 100644 (file)
index 0000000..04f7bab
--- /dev/null
@@ -0,0 +1,21 @@
+require "lib/class"
+
+Achievements = class(function(obj, bot_id, types)
+  obj.bot_id = bot_id
+  obj.types = types
+end)
+
+function Achievements:send(nick, type, ...)
+  local extra = ...
+  if not extra then
+    extra = 0
+  end
+
+  local ntype = self.types[type]
+  if ntype then
+    type = ntype
+  end
+
+  local user_numeric = irc_numerictobase64(nick.numeric)
+  irc_privmsg("C", "EXTACH " .. user_numeric .. " " .. self.bot_id .. " " .. type .. " " .. extra)
+end
diff --git a/lua/lib/quakenet/iterators.lua b/lua/lib/quakenet/iterators.lua
new file mode 100644 (file)
index 0000000..4ec9c6a
--- /dev/null
@@ -0,0 +1,87 @@
+nickpusher = { nick = 0, ident = 1, hostname = 2, realname = 3, authname = 4, ipaddress = 5, numeric = 6, timestamp = 7, accountts = 8, umodes = 9, country = 10 }
+chanpusher = { name = 0, totalusers = 1, topic = 2, realusers = 3, timestamp = 4, modes = 5 }
+
+function channelusers_iter(channel, items)
+  local t = { irc_getfirstchannick(channel, items) }
+  if #t == 0 then
+    return function()
+      return nil
+    end
+  end
+  local b = false
+
+  return function()
+    if not b then
+      b = true
+      return t
+    end
+
+    local t = { irc_getnextchannick() }
+    if #t == 0 then
+      return nil
+    end
+    return t
+  end
+end
+
+function channelhash_iter(items)
+  local t = { irc_getfirstchan(items) }
+  if #t == 0 then
+    return function()
+      return nil
+    end
+  end
+  local b = false
+
+  return function()
+    if not b then
+      b = true
+      return t
+    end
+
+    local t = { irc_getnextchan() }
+    if #t == 0 then
+      return nil
+    end
+    return t
+  end
+end
+
+function nickhash_iter(items)
+  local t = { irc_getfirstnick(items) }
+  if #t == 0 then
+    return function()
+      return nil
+    end
+  end
+  local b = false
+
+  return function()
+    if not b then
+      b = true
+      return t
+    end
+
+    local t = { irc_getnextnick() }
+    if #t == 0 then
+      return nil
+    end
+    return t
+  end
+end
+
+function nickchannels_iter(nick)
+  local c = irc_getnickchancount(nick)
+  local i = -1
+
+  return function()
+    i = i + 1
+    if i == c then
+      return nil
+    end
+
+    return irc_getnickchanindex(nick, i)
+  end
+end
+
+
diff --git a/lua/lib/quakenet/legacy.lua b/lua/lib/quakenet/legacy.lua
new file mode 100644 (file)
index 0000000..f0fe45d
--- /dev/null
@@ -0,0 +1,55 @@
+local function flatten(x)
+  local resulta = {}
+  local resultb = {}
+
+  for k, v in pairs(x) do
+    table.insert(resulta, k)
+    table.insert(resultb, v)
+  end
+  return resulta, resultb
+end
+
+local inickpusher, nnickpusher = flatten(nickpusher)
+local ichanpusher, nchanpusher = flatten(chanpusher)
+
+local function __nickpush(fn, input)
+  local n = { fn(input, nnickpusher) }
+  if #n == 0 then
+    return
+  end
+
+  local result = {}
+  for k, v in pairs(n) do
+    result[inickpusher[k]] = v
+  end
+
+  -- compatibility
+  result.account = result.authname
+  return result
+end
+
+function irc_getnickbynick(nick)
+  return __nickpush(irc_fastgetnickbynick, nick)
+end
+
+function irc_getnickbynumeric(numeric)
+  return __nickpush(irc_fastgetnickbynumeric, numeric)
+end
+
+function irc_getchaninfo(channel)
+  local c = { irc_fastgetchaninfo(channel, nchanpusher) }
+  if #c == 0 then
+    return
+  end
+
+  local result = {}
+  for k, v in pairs(c) do
+    result[ichanpusher[k]] = v
+  end
+
+  return result
+end
+
+function irc_localregisteruser(nickname, ident, hostname, realname, account, usermodes, handler_function)
+  return irc_localregisteruserid(nickname, ident, hostname, realname, account, 0, usermodes, handler_function)
+end
diff --git a/lua/lib/schedule.lua b/lua/lib/schedule.lua
new file mode 100644 (file)
index 0000000..0bdd884
--- /dev/null
@@ -0,0 +1,74 @@
+local sched = {}
+local tag = 0
+
+local function timesort(a, b)
+  return a[1] < b[1]
+end
+
+function doschedule()
+  local ct = os.time()
+  local out = {}
+  local callers = {}
+
+  for t, e in pairs(sched) do
+    if t <= ct then
+      table.insert(callers, { t, e })
+    else
+      out[t] = e
+    end
+  end
+  table.sort(callers, timesort)
+
+  sched = out
+
+  for _, e in ipairs(callers) do
+    for _, v in pairs(e[2]) do
+      if v[1] then
+        if v[2] then
+          v[1](unpack(v[2]))
+        else
+          v[1]()
+        end
+      else
+        scripterror("schedule.lua: event is nil!")
+      end
+    end
+  end
+end
+
+function schedule(when, callback, ...)
+  tag = tag + 1
+  local n = { callback, { ... }, tag }
+  local w = os.time() + when
+
+  if sched[w] then
+    table.insert(sched[w], n)
+  else
+    sched[w] = { n }
+  end
+  return { w, tag }
+end
+
+function delschedule(tag)
+  local c = {}
+
+  local w, o = unpack(tag)
+  if not sched[w] then
+    return
+  end
+
+  for i, v in pairs(sched[w]) do
+    if v[3] == o then
+      if #sched[w] == 1 then
+        sched[w] = nil
+      else
+        sched[w][i] = nil
+      end
+      return
+    end
+  end
+end
+
+function scheduleempty()
+  return tableempty(sched)
+end
diff --git a/lua/lib/serialise.lua b/lua/lib/serialise.lua
new file mode 100644 (file)
index 0000000..703d48e
--- /dev/null
@@ -0,0 +1,63 @@
+function savetable(filename, t)
+  local f = assert(io.open(filename, "w"))
+
+  f:write("return\n")
+
+  serialise(f, t)
+
+  f:close()
+end
+
+function loadtable(filename)
+  local func, err = loadfile(filename)
+
+  if not func then
+    return nil
+  end
+
+  return func()
+end
+
+function serialise(f, o)
+  if type(o) == "number" then
+    f:write(o)
+  elseif type(o) == "string" then
+    f:write(string.format("%q", o))
+  elseif type(o) == "boolean" then
+    if o == true then
+      f:write("true")
+    else
+      f:write("false")
+    end
+  elseif type(o) == "table" then
+    f:write("{\n")
+    for k,v in pairs(o) do
+      f:write("  [")
+      serialise(f, k)
+      f:write("] = ")
+      serialise(f, v)
+      f:write(",\n")
+    end
+    f:write("}\n")
+  elseif type(o) == "nil" then
+    f:write("nil")
+  else
+    error("cannot serialise a " .. type(o))
+  end
+end
+
+function loadtextfile(file, fn)
+  local f = io.open(file, "r")
+  if not f then
+    return false
+  end
+  
+  while true do
+    local line = f:read()
+    if line == nil then
+      return true
+    end
+
+    fn(line)
+  end
+end
diff --git a/lua/lib/socket.lua b/lua/lib/socket.lua
new file mode 100644 (file)
index 0000000..e045936
--- /dev/null
@@ -0,0 +1,138 @@
+-- transparent non-blocking writes with buffers!
+
+local socket_write_raw = socket_write
+local socket_unix_connect_raw = socket_unix_connect
+local socket_unix_bind_raw = socket_unix_bind
+local socket_tcp_connect_raw = socket_tcp_connect
+local socket_tcp_bind_raw = socket_tcp_bind
+local socket_udp_connect_raw = socket_udp_connect
+local socket_udp_bind_raw = socket_udp_bind
+local socket_close_raw = socket_close
+
+local sockets = {}
+
+function socket_handler(socket, event, tag, ...)
+  if event == "flush" then
+    local buf = sockets[socket].writebuf
+    local ret = socket_write_raw(socket, buf)
+
+    if ret == -1 then
+      return -- close will be called
+    end
+
+    sockets[socket].writebuf = buf:sub(ret + 1)
+
+    if sockets[socket].closing and sockets[socket].writebuf == "" then
+      socket_close(socket)
+    end
+
+    -- no reason to tell the caller
+    return
+  elseif event == "accept" then
+    local newsocket = ...
+    socket_new(newsocket, sockets[socket].handler)
+  end
+
+  sockets[socket].handler(socket, event, tag, ...)
+
+  if event == "close" then
+    sockets[socket] = nil
+  end
+end
+
+function socket_new(socket, handler)
+  sockets[socket] = { writebuf = "", handler = handler }
+end
+
+function socket_unix_connect(path, handler, tag)
+  local connected, socket = socket_unix_connect_raw(path, socket_handler, tag)
+  if connected == nil then
+    return nil
+  end
+
+  socket_new(socket, handler)
+  if connected then
+    socket_handler(socket, "connect", tag)
+  end
+
+  return socket
+end
+
+function socket_unix_bind(path, handler, tag)
+  local socket = socket_unix_bind_raw(path, socket_handler, tag)
+  if not socket then
+    return nil
+  end
+
+  socket_new(socket, handler)
+  return socket
+end
+
+local function socket_ip_connect(fn, address, port, handler, tag)
+  local connected, socket = fn(address, port, socket_handler, tag)
+  if connected == nil then
+    return nil
+  end
+
+  socket_new(socket, handler)
+  if connected then
+    socket_handler(socket, "connect", tag)
+  end
+
+  return socket
+end
+
+local function socket_ip_bind(fn, address, port, handler, tag)
+  local socket = fn(address, port, socket_handler, tag)
+  if not socket then
+    return nil
+  end
+
+  socket_new(socket, handler)
+  return socket
+end
+
+function socket_tcp_bind(address, port, handler, tag)
+  return socket_ip_bind(socket_tcp_bind_raw, address, port, handler, tag)
+end
+
+function socket_tcp_connect(address, port, handler, tag)
+  return socket_ip_connect(socket_tcp_connect_raw, address, port, handler, tag)
+end
+
+function socket_udp_bind(address, port, handler, tag)
+  return socket_ip_bind(socket_udp_bind_raw, address, port, handler, tag)
+end
+
+function socket_udp_connect(address, port, handler, tag)
+  return socket_ip_connect(socket_udp_connect_raw, address, port, handler, tag)
+end
+
+function socket_write(socket, data)
+  if sockets[socket].writebuf == "" then
+    local ret = socket_write_raw(socket, data)
+    if ret == -1 then
+      return false -- close will be called regardless
+    end
+
+    if ret == data:len() then
+      return true
+    end
+
+    -- lua strings start at 1
+    sockets[socket].writebuf = data:sub(ret + 1)
+  else
+    sockets[socket].writebuf = sockets[socket].writebuf .. data
+  end
+
+  return true
+end
+
+function socket_close(socket, flush)
+  if whenbufferempty and sockets[socket].writebuf ~= "" then
+    sockets[socket].closing = true
+    return
+  end
+
+  return socket_close_raw(socket)
+end
index bb6e003f9bb4ca018ddb032bdf259354972023d3..092b7f19b22e05680c5204746287c2a05092f742 100644 (file)
@@ -210,7 +210,7 @@ static int lua_invite(lua_State *ps) {
   if(!cp)
     LUA_RETURN(ps, LUA_FAIL);
 
-  localinvite(lua_nick, cp, np);
+  localinvite(lua_nick, cp->index, np);
 
   LUA_RETURN(ps, LUA_OK);
 }
index 57ebbfee148f253b8026aee006eb9540947f60b6..dc5522307cfd875d3eb462658bb40f1a77b9cd42 100644 (file)
@@ -4,6 +4,8 @@ CFLAGS+=$(INCDBAPI)
 LDFLAGS+=$(LIBDBAPI)
 
 .PHONY: all
-all: proxyscan.so  
+all: proxyscan.so proxyscan_newsearch.so
 
 proxyscan.so: proxyscan.o proxyscanext.o proxyscanalloc.o proxyscanconnect.o proxyscancache.o proxyscanqueue.o proxyscanhandlers.o proxyscandb.o
+
+proxyscan_newsearch.so: proxyscan_newsearch.o pns-scan.o
diff --git a/proxyscan/pns-scan.c b/proxyscan/pns-scan.c
new file mode 100644 (file)
index 0000000..52bf174
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * pscan functionality 
+ */
+
+#include "../newsearch/newsearch.h"
+#include "proxyscan.h"
+
+#include <stdlib.h>
+
+void *pscan_exe(searchCtx *ctx, struct searchNode *thenode, void *theinput);
+void pscan_free(searchCtx *ctx, struct searchNode *thenode);
+
+struct searchNode *pscan_parse(searchCtx *ctx, int argc, char **argv) {
+  struct searchNode *thenode;
+
+  if (!(thenode=(struct searchNode *)malloc(sizeof (struct searchNode)))) {
+    parseError = "malloc: could not allocate memory for this search.";
+    return NULL;
+  }
+
+  thenode->returntype = RETURNTYPE_BOOL;
+  thenode->exe = pscan_exe;
+  thenode->free = pscan_free;
+  thenode->localdata = (void *)0;
+
+  return thenode;
+}
+
+void *pscan_exe(searchCtx *ctx, struct searchNode *thenode, void *theinput) {
+  startnickscan((nick *)theinput);
+  thenode->localdata = (void *)((int)thenode->localdata + 1);
+  return (void *)1;
+}
+
+void pscan_free(searchCtx *ctx, struct searchNode *thenode) {
+  ctx->reply(senderNSExtern, "proxyscan now scanning: %d nick(s).", (int)thenode->localdata);
+  free(thenode);
+}
+
index 648e97ac56e72dcbe95f48527943d759d97f3cbb..11e179841571e44b84aa77395af2674d5ad57651 100644 (file)
@@ -247,6 +247,7 @@ void _init(void) {
 
   /* Default scan types */
   proxyscan_addscantype(STYPE_HTTP, 8080);
+  proxyscan_addscantype(STYPE_HTTP, 8118);
   proxyscan_addscantype(STYPE_HTTP, 80);
   proxyscan_addscantype(STYPE_HTTP, 6588);
   proxyscan_addscantype(STYPE_HTTP, 8000);
@@ -502,6 +503,7 @@ void killsock(scan *sp, int outcome) {
   int i;
   cachehost *chp;
   foundproxy *fpp;
+  time_t now;
 
   scansdone++;
   scansbyclass[sp->class]++;
@@ -546,10 +548,13 @@ void killsock(scan *sp, int outcome) {
       fpp->next=chp->proxies;
       chp->proxies=fpp;
     }
-    
-    if (!chp->glineid) {
+
+    now=time(NULL);    
+    /* the purpose of this lastgline stuff is to stop gline spam from one scan */
+    if (!chp->glineid || (now>=chp->lastgline+SCANTIMEOUT)) {
+      chp->lastgline=now;
       glinedhosts++;
-      loggline(chp, sp->node);
+      loggline(chp, sp->node);   
       irc_send("%s GL * +*@%s 1800 %jd :Open Proxy, see http://www.quakenet.org/openproxies.html - ID: %d",
               mynumeric->content,IPtostr(((patricia_node_t *)sp->node)->prefix->sin),(intmax_t)getnettime(), chp->glineid);
       Error("proxyscan",ERR_DEBUG,"Found open proxy on host %s",IPtostr(((patricia_node_t *)sp->node)->prefix->sin));
@@ -943,6 +948,15 @@ int proxyscandoshowkill(void *sender, int cargc, char **cargv) {
   return CMD_OK;
 }
 
+void startnickscan(nick *np) {
+  time_t t = time(NULL);
+  int i;
+  for(i=0;i<numscans;i++) {
+    /* @@@TODO: we allow a forced scan to scan the same IP multiple times atm */
+    queuescan(np->ipnode,thescans[i].type,thescans[i].port,SCLASS_NORMAL,t);
+  }
+}
+
 int proxyscandoscan(void *sender, int cargc, char **cargv) {
   nick *np = (nick *)sender;
   patricia_node_t *node;
@@ -953,12 +967,14 @@ int proxyscandoscan(void *sender, int cargc, char **cargv) {
   if (0 == ipmask_parse(cargv[0],&sin, &bits)) {
     sendnoticetouser(proxyscannick,np,"Usage: scan <ip>");
   } else {
+    time_t t;
     sendnoticetouser(proxyscannick,np,"Forcing scan of %s",IPtostr(sin));
     // * Just queue the scans directly here.. plonk them on the priority queue * /
     node = refnode(iptree, &sin, bits); /* node leaks node here - should only allow to scan a nick? */
+    t = time(NULL);
     for(i=0;i<numscans;i++) {
       /* @@@TODO: we allow a forced scan to scan the same IP multiple times atm */
-      queuescan(node,thescans[i].type,thescans[i].port,SCLASS_NORMAL,time(NULL));
+      queuescan(node,thescans[i].type,thescans[i].port,SCLASS_NORMAL,t);
     }
   }
   return CMD_OK;
index 110ba302fcfec1152bb216d4bf82ed60ea2676a7..6f649c674e10e3fc4d09b341bcf0efffd45e8489 100644 (file)
@@ -70,6 +70,7 @@ typedef struct cachehost {
   time_t lastscan;
   foundproxy *proxies;
   int glineid;
+  time_t lastgline;
   unsigned char marker;
 #if defined(PROXYSCAN_MAIL)
   sstring *lasthostmask; /* Not saved to disk */
@@ -171,6 +172,7 @@ void startqueuedscans();
 
 /* proxyscan.c */
 void startscan(patricia_node_t *node, int type, int port, int class);
+void startnickscan(nick *nick);
 
 /* proxyscanext.c */
 unsigned int extrascancount();
diff --git a/proxyscan/proxyscan_newsearch.c b/proxyscan/proxyscan_newsearch.c
new file mode 100644 (file)
index 0000000..6d844c3
--- /dev/null
@@ -0,0 +1,11 @@
+#include "../newsearch/newsearch.h"
+
+struct searchNode *pscan_parse(searchCtx *ctx, int argc, char **argv);
+
+void _init() {
+  registersearchterm(reg_nicksearch, "pscan", pscan_parse, 0, "");
+}
+
+void _fini() {
+  deregistersearchterm(reg_nicksearch, "pscan", pscan_parse);
+}
index 93f0c637d2878e0e9f4b24c3d51e742eb62942d7..f14fbd2b5090e4d97145c5491a8dedf7ae50b46f 100644 (file)
@@ -24,6 +24,7 @@ cachehost *addcleanhost(time_t timestamp) {
   chp->lastscan=timestamp;
   chp->proxies=NULL;
   chp->glineid=0;
+  chp->lastgline=0;  
   
   return chp;
 }
@@ -92,7 +93,7 @@ void dumpcachehosts(void *arg) {
           } else
         
           for (fpp=chp->proxies;fpp;fpp=fpp->next) 
-            fprintf(fp, "%s %lu %u %i %u\n",IPtostr(node->prefix->sin),chp->lastscan,chp->glineid,fpp->type,fpp->port);
+            fprintf(fp, "%s %lu %u %i %u %lu\n",IPtostr(node->prefix->sin),chp->lastscan,chp->glineid,fpp->type,fpp->port,chp->lastgline);
         } else {
           if (chp->lastscan < (now-cleanscaninterval)) {
             /* Needs rescan anyway, so delete it */
@@ -118,7 +119,7 @@ void dumpcachehosts(void *arg) {
 
 void loadcachehosts() {
   FILE *fp;
-  unsigned long timestamp,glineid,ptype,pport;
+  unsigned long timestamp,glineid,ptype,pport,lastgline;
   char buf[512];
   cachehost *chp=NULL;
   foundproxy *fpp;
@@ -141,7 +142,7 @@ void loadcachehosts() {
       break;
     }
 
-    res=sscanf(buf,"%s %lu %lu %lu %lu",ip,&timestamp,&glineid,&ptype,&pport);
+    res=sscanf(buf,"%s %lu %lu %lu %lu %lu",ip,&timestamp,&glineid,&ptype,&pport,&lastgline);
 
     if (res<2)
       continue;
@@ -155,8 +156,9 @@ void loadcachehosts() {
         chp=addcleanhost(timestamp);
         node->exts[ps_cache_ext] = chp;
       
-        if (res==5) {
+        if (res==6) {
           chp->glineid=glineid;
+          chp->lastgline=lastgline;
           fpp=getfoundproxy();
           fpp->type=ptype;
           fpp->port=pport;
index 23b32e8fde8480d592a08bc8b05dc53b87000d03..012ce4ad478984024086fa3165577bd5c3f4b28e 100644 (file)
@@ -15,19 +15,26 @@ void proxyscan_newnick(int hooknum, void *arg) {
   if (irc_in_addr_is_loopback(&np->p_ipaddr) || !irc_in_addr_is_ipv4(&np->p_ipaddr)) 
     return;
 
-  /* before we look at a normal host, see if we think we have an open proxy */
-  if ((esp=findextrascan(np->ipnode))) {
-    Error("proxyextra", ERR_ERROR, "connection from possible proxy %s", IPtostr(np->p_ipaddr)); 
-    for (espp=esp;espp;espp=espp->nextbynode) { 
-      /* we force a scan on any hosts that may be an open proxy, even if they are:
-       * a) already in the queue, b) we've been running < 120 seconds */
-      queuescan(np->ipnode, espp->type, espp->port, SCLASS_NORMAL, time(NULL));
-    }
-  }
+  /* slug: why is this here? why isn't it with the other queuing stuff? */
+  /* we're given a list of ip/subnets and port pairs which someone else has
+     seen a proxy on in the past, so we scan these very aggressively
+     (even ignoring the cache)
+   */
+  /* disabled as the list is hopelessly out of date */
+//  if ((esp=findextrascan(np->ipnode))) {
+//    Error("proxyextra", ERR_ERROR, "connection from possible proxy %s", IPtostr(np->p_ipaddr)); 
+//    for (espp=esp;espp;espp=espp->nextbynode) { 
+//      /* we force a scan on any hosts that may be an open proxy, even if they are:
+//       * a) already in the queue, b) we've been running < 120 seconds */
+//      queuescan(np->ipnode, espp->type, espp->port, SCLASS_NORMAL, time(NULL));
+//    }
+//  }
 
-  /* ignore newnick until initial burst complete */
-  if (!ps_ready)
+/* slug: this BREAKS all of P's design assumptions, do NOT REENABLE THIS UNDER ANY CIRCUMSTANCES */
+/* ignore newnick until initial burst complete */
+/*  if (!ps_ready)
     return;
+*/
 
   /*
    * Logic for connecting hosts: