]> jfr.im git - irc/quakenet/newserv.git/blobdiff - trusts/trusts_policy.c
TRUSTS: turn on tcpkeepalive for trust policy sockets.
[irc/quakenet/newserv.git] / trusts / trusts_policy.c
index 74a9fe18fb7dea5f6fb0080dbdebc46ced4e9498..b5c70d8e9642b0172d7342b0c53e8ef40280889b 100644 (file)
@@ -1,29 +1,91 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#ifndef __USE_MISC
+#define __USE_MISC /* inet_aton */
+#endif
+
+#include <arpa/inet.h>
+#include <sys/un.h>
+#include <sys/poll.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "../lib/version.h"
+#include "../lib/hmac.h"
+#include "../core/events.h"
+#include "../core/schedule.h"
+#include "../core/nsmalloc.h"
 #include "../core/hooks.h"
+#include "../core/config.h"
 #include "../control/control.h"
 #include "../lib/irc_string.h"
+#include "../irc/irc.h"
+#include "../glines/glines.h"
+#include "../patricianick/patricianick.h"
 #include "trusts.h"
 
-static int countext;
+MODULE_VERSION("");
 
-static void policycheck(int hooknum, void *arg) {
-  void **args = arg;
-  nick *np = args[0];
-  long moving = (long)args[1];
-  trusthost *th = gettrusthost(np);
+static int countext, enforcepolicy_irc, enforcepolicy_auth;
+
+#define TRUSTBUFSIZE 8192
+#define TRUSTPASSLEN 128
+#define NONCELEN 16
+
+typedef struct trustsocket {
+  int fd;
+  int authed;
+  char authuser[SERVERLEN+1];
+  char buf[TRUSTBUFSIZE];
+  unsigned char nonce[NONCELEN];
+  int size;
+  time_t connected;
+  time_t timeout;
+  int accepted;
+  int rejected;
+  int unthrottled;
+
+  struct trustsocket *next;
+} trustsocket;
+
+static trustsocket *tslist;
+static int listenerfd = -1;
+static FILE *urandom;
+
+typedef struct trustaccount {
+  int used;
+  char server[SERVERLEN+1];
+  char password[TRUSTPASSLEN+1];
+} trustaccount;
+
+trustaccount trustaccounts[MAXSERVERS];
+
+static int checkconnection(const char *username, struct irc_in_addr *ipaddress, int hooknum, int usercountadjustment, char *message, size_t messagelen, int *unthrottle) {
+  trusthost *th;
   trustgroup *tg;
+  struct irc_in_addr ipaddress_canonical;
 
-  if(moving)
-    return;
+  ip_canonicalize_tunnel(&ipaddress_canonical, ipaddress);
 
-  if(!th)
-    return;
+  th = th_getbyhost(&ipaddress_canonical);
 
-  tg = th->group;
+  if (unthrottle)
+    *unthrottle = 0;
 
-  if(th->maxpernode && np->ipnode->usercount > th->maxpernode) {
-    controlwall(NO_OPER, NL_TRUSTS, "Hard connection limit exceeded on IP: %s (group: %s) %d connected, %d max.", IPtostr(np->p_ipaddr), tg->name->content, np->ipnode->usercount, th->maxpernode);
-    return;
-  }
+  if(messagelen>0)
+    message[0] = '\0';
+  
+  if(!th || !trustsdbloaded || irc_in_addr_is_loopback(ipaddress))
+    return POLICY_SUCCESS;
+
+  tg = th->group;
 
   /*
    * the purpose of this logic is to avoid spam like this:
@@ -33,19 +95,51 @@ static void policycheck(int hooknum, void *arg) {
    */
 
   if(hooknum == HOOK_TRUSTS_NEWNICK) {
-    if(tg->trustedfor && tg->count > tg->trustedfor) {
-/*
-      if(tg->count > (long)tg->exts[countext]) {
+    patricia_node_t *head, *node;
+    int i, nodecount = 0;
+    patricianick_t *pnp;
+    nick *npp;
+
+    head = refnode(iptree, &ipaddress_canonical, th->nodebits);
+    nodecount = head->usercount;
+
+    /* Account for borrowed IP addresses. */
+    PATRICIA_WALK(head, node) {
+      pnp = node->exts[pnode_ext];
+
+      if (pnp)
+        for (i = 0; i < PATRICIANICK_HASHSIZE; i++)
+          for (npp = pnp->identhash[i]; npp; npp=npp->exts[pnick_ext])
+            if (NickOnServiceServer(npp))
+              usercountadjustment--;
+    }
+    PATRICIA_WALK_END;
 
+    derefnode(iptree, head);
+
+    if(th->maxpernode && nodecount + usercountadjustment > th->maxpernode) {
+      controlwall(NO_OPER, NL_CLONING, "Hard connection limit exceeded on subnet: %s (group: %s): %d connected, %d max.", CIDRtostr(*ipaddress, th->nodebits), tg->name->content, nodecount + usercountadjustment, th->maxpernode);
+      snprintf(message, messagelen, "Too many connections from your host (%s) - see https://www.quakenet.org/help/trusts/connection-limit for details.", IPtostr(*ipaddress));
+      return POLICY_FAILURE_NODECOUNT;
+    }
+
+    if(tg->trustedfor && tg->count + usercountadjustment > tg->trustedfor) {
+      if(tg->count > (long)tg->exts[countext]) {
         tg->exts[countext] = (void *)(long)tg->count;
-*/
-        controlwall(NO_OPER, NL_TRUSTS, "Hard connection limit exceeded: '%s', %d connected, %d max.", tg->name->content, tg->count, tg->trustedfor);
+
+        controlwall(NO_OPER, NL_CLONING, "Hard connection limit exceeded (group %s): %d connected, %d max.", tg->name->content, tg->count + usercountadjustment, tg->trustedfor);
+        snprintf(message, messagelen, "Too many connections from your trust (%s) - see https://www.quakenet.org/help/trusts/connection-limit for details.", IPtostr(*ipaddress));
       }
-/*
+
+      snprintf(message, messagelen, "Too many connections from your trust (%s) - see https://www.quakenet.org/help/trusts/connection-limit for details.", IPtostr(*ipaddress));
+      return POLICY_FAILURE_GROUPCOUNT;
+    }
+
+    if((tg->flags & TRUST_ENFORCE_IDENT) && (username[0] == '~')) {
+      controlwall(NO_OPER, NL_CLONING, "Ident required: %s@%s (group: %s).", username, IPtostr(*ipaddress), tg->name->content);
+      snprintf(message, messagelen, "IDENTD required from your host (%s) - see https://www.quakenet.org/help/trusts/connection-limit for details.", IPtostr(*ipaddress));
+      return POLICY_FAILURE_IDENTD;
     }
-*/
-    if((tg->mode == 1) && (np->ident[0] == '~'))
-      controlwall(NO_OPER, NL_TRUSTS, "Ident required: '%s' %s!%s@%s.", tg->name->content, np->nick, np->ident, np->host->name->content);
 
     if(tg->maxperident > 0) {
       int identcount = 0;
@@ -54,35 +148,567 @@ static void policycheck(int hooknum, void *arg) {
 
       for(th2=tg->hosts;th2;th2=th2->next) {
         for(tnp=th2->users;tnp;tnp=nextbytrust(tnp)) {
-          if(!ircd_strcmp(tnp->ident, np->ident))
+          if(!ircd_strcmp(tnp->ident, username))
             identcount++;
         }
       }
 
-      if(identcount > tg->maxperident)
-        controlwall(NO_OPER, NL_TRUSTS, "Hard ident limit exceeded: '%s' %s!%s@%s, %d connected, %d max.", tg->name->content, np->nick, np->ident, np->host->name->content, identcount, tg->maxperident);
+      if(identcount + usercountadjustment > tg->maxperident) {
+        controlwall(NO_OPER, NL_CLONING, "Hard ident limit exceeded: %s@%s (group: %s): %d connected, %d max.", username, IPtostr(*ipaddress), tg->name->content, identcount + usercountadjustment, tg->maxperident);
+        snprintf(message, messagelen, "Too many connections from your username (%s@%s) - see https://www.quakenet.org/help/trusts/connection-limit for details.", username, IPtostr(*ipaddress));
+        return POLICY_FAILURE_IDENTCOUNT;
+      }
     }
   } else {
     if(tg->count < tg->maxusage)
       tg->exts[countext] = (void *)(long)tg->count;
   }
+
+  if(tg->trustedfor > 0)
+    snprintf(message, messagelen, "Trust has %d out of %d allowed connections.", tg->count + usercountadjustment, tg->trustedfor);
+
+  if(unthrottle && (tg->flags & TRUST_UNTHROTTLE))
+    *unthrottle = 1;
+
+  return POLICY_SUCCESS;
+}
+
+static int trustdowrite(trustsocket *sock, char *format, ...) {
+  char buf[1024];
+  va_list va;
+  int r;
+
+  va_start(va, format);
+  r = vsnprintf(buf, sizeof(buf), format, va);
+  va_end(va);
+
+  if(r >= sizeof(buf))
+    r = sizeof(buf) - 1;
+
+  buf[r] = '\n';
+
+  if(write(sock->fd, buf, r + 1) != r + 1)
+    return 0;
+  return 1;
+}
+
+static int policycheck_auth(trustsocket *sock, const char *sequence_id, const char *username, const char *host) {
+  char message[512];
+  int verdict, unthrottle;
+  struct irc_in_addr ipaddress;
+  unsigned char bits;
+
+  if(!ipmask_parse(host, &ipaddress, &bits)) {
+    sock->accepted++;
+    return trustdowrite(sock, "PASS %s", sequence_id);
+  }
+
+  verdict = checkconnection(username, &ipaddress, HOOK_TRUSTS_NEWNICK, 1, message, sizeof(message), &unthrottle);
+
+  if(!enforcepolicy_auth)
+    verdict = POLICY_SUCCESS;
+
+  if (verdict == POLICY_SUCCESS) {
+    sock->accepted++;
+
+    if (unthrottle) {
+      sock->unthrottled++;
+      trustdowrite(sock, "UNTHROTTLE %s", sequence_id);
+    }
+
+    if(message[0])
+      return trustdowrite(sock, "PASS %s %s", sequence_id, message);
+    else
+      return trustdowrite(sock, "PASS %s", sequence_id);
+  } else {
+    sock->rejected++;
+
+    controlwall(NO_OPER, NL_CLONING, "Rejected connection from %s@%s using IAuth: %s", username, host, message);
+    return trustdowrite(sock, "KILL %s %s", sequence_id, message);
+  }
+}
+
+static int trustkillconnection(trustsocket *sock, char *reason) {
+  trustdowrite(sock, "QUIT %s", reason);
+  return 0;
+}
+
+static void trustfreeconnection(trustsocket *sock, int unlink) {
+  trustsocket **pnext, *ts;
+
+  if(!unlink) {
+    controlwall(NO_OPER, NL_TRUSTS, "Lost connection on policy socket for '%s'.", sock->authed?sock->authuser:"<unauthenticated connection>");
+
+    deregisterhandler(sock->fd, 1);
+    nsfree(POOL_TRUSTS, sock);
+    return;
+  }
+
+  for(pnext=&tslist;*pnext;pnext=&((*pnext)->next)) {
+    ts=*pnext;
+    if(ts == sock) {
+      *pnext = sock->next;
+      trustfreeconnection(sock, 0);
+      break;
+    }
+  }
+}
+
+static int handletrustauth(trustsocket *sock, char *server_name, char *mac) {
+  int i;
+  char *password = NULL;
+  unsigned char digest[16];
+  char noncehexbuf[NONCELEN * 2 + 1];
+  char hexbuf[sizeof(digest) * 2 + 1];
+  trustsocket *ts, **pnext;
+
+  for(i=0;i<MAXSERVERS;i++) {
+    if(trustaccounts[i].used && strcmp(trustaccounts[i].server, server_name) == 0) {
+      password = trustaccounts[i].password;
+      break;
+    }
+  }
+
+  if (!password) {
+    controlwall(NO_OPER, NL_TRUSTS, "Invalid servername for policy socket: '%s'", server_name);
+    return trustkillconnection(sock, "Invalid servername.");
+  }
+
+  hmacmd5 h;
+  hmacmd5_init(&h, (unsigned char *)password, strlen(password));
+  hmacmd5_update(&h, (unsigned char *)hmac_printhex(sock->nonce, noncehexbuf, NONCELEN), NONCELEN * 2);
+  hmacmd5_final(&h, digest);
+  if(hmac_strcmp(mac, hmac_printhex(digest, hexbuf, sizeof(digest)))) {
+    controlwall(NO_OPER, NL_TRUSTS, "Invalid password for policy socket with servername '%s'.", server_name);
+    return trustkillconnection(sock, "Bad MAC.");
+  }
+
+  for(pnext=&tslist;*pnext;pnext=&((*pnext)->next)) {
+    ts = *pnext;
+    if(ts->authed && strcmp(ts->authuser, server_name) == 0) {
+      trustkillconnection(ts, "New connection with same server name.");
+      *pnext = ts->next;
+      trustfreeconnection(ts, 0);
+      break;
+    }
+  }
+
+  sock->authed = 1;
+  strncpy(sock->authuser, server_name, SERVERLEN);
+
+  controlwall(NO_OPER, NL_TRUSTS, "Successful authentication for policy socket with servername '%s'.", server_name);
+  return trustdowrite(sock, "AUTHOK");
+}
+
+#define MAXTOKENS 10
+static int handletrustline(trustsocket *sock, char *line) {
+  char *command, *p, *lastpos;
+  char *tokens[MAXTOKENS];
+  int tokensfound = -1;
+
+  for(command=lastpos=p=line;*p;p++) {
+    if(*p == ' ') {
+      *p = '\0';
+      if(tokensfound == MAXTOKENS)
+        return trustkillconnection(sock, "too many tokens");
+
+      if(tokensfound >= 0) {
+        tokens[tokensfound++] = lastpos;
+      } else {
+        tokensfound++;
+      }
+      lastpos = p + 1;
+    }
+  }
+  if(lastpos != p) {
+    if(tokensfound == MAXTOKENS)
+      return trustkillconnection(sock, "too many tokens");
+    tokens[tokensfound++] = lastpos;
+  }
+
+  if(!sock->authed && !strcmp("AUTH", command)) {
+    if(tokensfound != 2)
+      return trustkillconnection(sock, "incorrect arg count for command.");
+
+    return handletrustauth(sock, tokens[0], tokens[1]);
+  } else if(sock->authed && !strcmp("CHECK", command)) {
+    if(tokensfound != 3)
+      return trustkillconnection(sock, "incorrect arg count for command.");
+
+    policycheck_auth(sock, tokens[0], tokens[1], tokens[2]);
+    return 1;
+  } else if(!strcmp("VERSION", command)) {
+    /* Ignore this command for now. */
+    return 1;
+  } else {
+    Error("trusts_policy", ERR_WARNING, "Bad command: %s", command);
+    return 0;
+  }
+}
+
+static trustsocket *findtrustsocketbyfd(int fd) {
+  for(trustsocket *ts=tslist;ts;ts=ts->next)
+    if(ts->fd==fd)
+      return ts;
+    
+  return NULL;
+}
+
+static int handletrustclient(trustsocket *sock) {
+  int r, remaining = TRUSTBUFSIZE - sock->size, i;
+  char *lastpos, *c;
+
+  if(!remaining) {
+    trustkillconnection(sock, "Buffer overflow.");
+    return 0;
+  }
+
+  r = read(sock->fd, sock->buf + sock->size, remaining);
+  if(r <= 0)
+    return 0;
+
+  sock->size+=r;
+  lastpos = sock->buf;
+
+  for(c=sock->buf,i=0;i<sock->size;i++,c++) {
+    if(*c != '\n')
+      continue;
+    *c = '\0';
+    if(!handletrustline(sock, lastpos))
+      return 0;
+
+    lastpos = c + 1; /* is this ok? */
+  }
+  sock->size-=lastpos - sock->buf;
+  memmove(sock->buf, lastpos, sock->size);
+  
+  return 1;
+}
+
+static void processtrustclient(int fd, short events) {
+  trustsocket *sock = findtrustsocketbyfd(fd);
+
+  if(!sock)
+    return;
+
+  if (events & (POLLPRI | POLLERR | POLLHUP | POLLNVAL)) {
+    trustfreeconnection(sock, 1);
+    return;
+  }
+
+  if(events & POLLIN)
+    if(!handletrustclient(sock))
+      trustfreeconnection(sock, 1);
+}
+
+static void trustdotimeout(void *arg) {
+  time_t t = time(NULL);
+  trustsocket **pnext, *sock;
+
+  for(pnext=&tslist;*pnext;pnext=&((*pnext)->next)) {
+    sock = *pnext;
+    if(!sock->authed && t >= sock->timeout) {
+      trustkillconnection(sock, "Auth timeout.");
+      *pnext = sock->next;
+      trustfreeconnection(sock, 0);
+    }
+  }
+}
+
+static void processtrustlistener(int fd, short events) {
+  if(events & POLLIN) {
+    trustsocket *sock;
+    char buf[NONCELEN * 2 + 1];
+    int optval;
+
+    int newfd = accept(fd, NULL, NULL), flags;
+    if(newfd == -1)
+      return;
+
+    flags = fcntl(newfd, F_GETFL, 0);
+    if(flags < 0) {
+      Error("trusts_policy", ERR_WARNING, "Unable to set socket non-blocking.");
+      close(newfd);
+      return;
+    }
+
+    if(fcntl(fd, F_SETFL, flags|O_NONBLOCK) < 0) {
+      Error("trusts_policy", ERR_WARNING, "Unable to set socket non-blocking.");
+      close(newfd);
+      return;
+    }
+
+    optval = 10;
+    setsockopt(newfd, SOL_SOCKET, TCP_KEEPIDLE, &optval, sizeof(optval));
+    optval = 3;
+    setsockopt(newfd, SOL_SOCKET, TCP_KEEPCNT, &optval, sizeof(optval));
+    optval = 10;
+    setsockopt(newfd, SOL_SOCKET, TCP_KEEPINTVL, &optval, sizeof(optval));
+    optval = 1;
+    setsockopt(newfd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(optval));
+
+    registerhandler(newfd, POLLIN|POLLERR|POLLHUP, processtrustclient);
+      
+    sock = nsmalloc(POOL_TRUSTS, sizeof(trustsocket));
+    if(!sock) {
+      deregisterhandler(newfd, 1);
+      return;
+    }
+    
+    sock->fd = newfd;
+    sock->next = tslist;
+    tslist = sock;
+    
+    if(fread((char *)sock->nonce, 1, NONCELEN, urandom) != NONCELEN) {
+      Error("trusts_policy", ERR_WARNING, "Error getting random bytes.");
+      deregisterhandler(newfd, 1);
+      tslist = sock->next;
+      nsfree(POOL_TRUSTS, sock);
+    } else {
+      sock->authed = 0;
+      sock->size = 0;
+      sock->connected = time(NULL);
+      sock->timeout = time(NULL) + 30;
+      sock->accepted = 0;
+      sock->rejected = 0;
+      sock->unthrottled = 0;
+      if(!trustdowrite(sock, "AUTH %s", hmac_printhex(sock->nonce, buf, NONCELEN))) {
+        Error("trusts_policy", ERR_WARNING, "Error writing auth to fd %d.", newfd);
+        deregisterhandler(newfd, 1);
+        tslist = sock->next;
+        nsfree(POOL_TRUSTS, sock);
+        return;
+      }
+    }
+  }
+}
+
+static int createlistenersock(int port) {
+  struct sockaddr_in s;
+  int fd;
+  int optval;
+
+  memset(&s, 0, sizeof(struct sockaddr_in));
+  s.sin_family = AF_INET;
+  s.sin_addr.s_addr = INADDR_ANY;
+  s.sin_port = htons(port);
+
+  fd = socket(PF_INET, SOCK_STREAM, 0);
+  if(fd < 0) {
+    Error("trusts_policy", ERR_WARNING, "Unable to get socket for trustfd.");
+    return -1;
+  }
+
+  optval = 1;
+  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
+
+  if(bind(fd, (struct sockaddr *)&s, sizeof(struct sockaddr_in)) < 0) {
+    Error("trusts_policy", ERR_WARNING, "Unable to bind trustfd.");
+    close(fd);
+    return -1;
+  }
+
+  if(listen(fd, 5) < 0) {
+    Error("trusts_policy", ERR_WARNING, "Unable to listen on trustfd.");
+    close(fd);
+    return -1;
+  }
+
+  registerhandler(fd, POLLIN, processtrustlistener);
+
+  return fd;
+}
+
+static void policycheck_irc(int hooknum, void *arg) {
+  void **args = arg;
+  nick *np = args[0];
+  long moving = (long)args[1];
+  char message[512];
+  int verdict, unthrottle;
+
+  if(moving)
+    return;
+
+  verdict = checkconnection(np->ident, &np->ipaddress, hooknum, 0, message, sizeof(message), &unthrottle);
+    
+  if(!enforcepolicy_irc)
+    verdict = POLICY_SUCCESS;
+
+  switch (verdict) {
+    case POLICY_FAILURE_NODECOUNT:
+      glinebynick(np, POLICY_GLINE_DURATION, message, GLINE_IGNORE_TRUST, "trusts_policy");
+      break;
+    case POLICY_FAILURE_IDENTD:
+      glinebyip("~*", &np->ipaddress, 128, POLICY_GLINE_DURATION, message, GLINE_ALWAYS_USER|GLINE_IGNORE_TRUST, "trusts_policy");
+      break;
+    case POLICY_FAILURE_IDENTCOUNT:
+      glinebynick(np, POLICY_GLINE_DURATION, message, GLINE_ALWAYS_USER|GLINE_IGNORE_TRUST, "trusts_policy");
+      break;
+  }
+
+}
+
+static int trusts_cmdtrustpolicyirc(void *source, int cargc, char **cargv) {
+  nick *sender = source;
+
+  if(cargc < 1) {
+    controlreply(sender, "Use of glines for trust policy enforcement is currently %s.", enforcepolicy_irc?"enabled":"disabled");
+    return CMD_OK;
+  }
+
+  enforcepolicy_irc = atoi(cargv[0]);
+  controlwall(NO_OPER, NL_TRUSTS, "%s %s use of glines for trust policy enforcement.", controlid(sender), enforcepolicy_irc?"enabled":"disabled");
+  controlreply(sender, "Use of glines for trust policy enforcement is now %s.", enforcepolicy_irc?"enabled":"disabled");
+
+  return CMD_OK;
+}
+
+static int trusts_cmdtrustpolicyauth(void *source, int cargc, char **cargv) {
+  nick *sender = source;
+
+  if(cargc < 1) {
+    controlreply(sender, "Trust policy enforcement with IAuth is currently %s.", enforcepolicy_auth?"enabled":"disabled");
+    return CMD_OK;
+  }
+
+  enforcepolicy_auth = atoi(cargv[0]);
+  controlwall(NO_OPER, NL_TRUSTS, "%s %s trust policy enforcement with IAuth.", controlid(sender), enforcepolicy_auth?"enabled":"disabled");
+  controlreply(sender, "Trust policy enforcement with IAuth is now %s.", enforcepolicy_auth?"enabled":"disabled");
+
+  return CMD_OK;
+}
+
+
+static int trusts_cmdtrustsockets(void *source, int cargc, char **cargv) {
+  nick *sender = source;
+  time_t now;
+  trustsocket *sock;
+
+  time(&now);
+
+  controlreply(sender, "Server                              Connected for        Accepted        Rejected        Unthrottled");
+
+  for(sock=tslist;sock;sock=sock->next)
+    controlreply(sender, "%-35s %-20s %-15d %-15d %-15d", sock->authed?sock->authuser:"<unauthenticated connection>", longtoduration(now - sock->connected, 0), sock->accepted, sock->rejected, sock->unthrottled);
+
+  controlreply(sender, "-- End of list.");
+  return CMD_OK;
+}
+
+void loadtrustaccounts(void) {
+  array *accts;
+
+  memset(trustaccounts, 0, sizeof(trustaccounts));
+
+  accts = getconfigitems("trusts_policy", "server");
+  if(!accts) {
+    Error("trusts_policy", ERR_INFO, "No servers added.");
+  } else {
+    sstring **servers = (sstring **)(accts->content);
+    int i;
+    for(i=0;i<accts->cursi;i++) {
+      char server[512];
+      char *pos;
+
+      if(i>=MAXSERVERS) {
+        Error("trusts_policy", ERR_INFO, "Too many servers specified.");
+        break;
+      }
+
+      strncpy(server, servers[i]->content, sizeof(server));
+
+      pos = strchr(server, ',');
+
+      if(!pos) {
+        Error("trusts_policy", ERR_INFO, "Server line is missing password: %s", server);
+        continue;
+      }
+
+      *pos = '\0';
+
+      trustaccounts[i].used = 1;
+      strncpy(trustaccounts[i].server, server, SERVERLEN);
+      strncpy(trustaccounts[i].password, pos+1, TRUSTPASSLEN);
+    }
+  }
+}
+
+static void trustaccounts_rehash(int hooknum, void *arg) {
+  loadtrustaccounts();
 }
 
 void _init(void) {
+  sstring *m;
+  int trustport;
+
   countext = registertgext("count");
   if(countext == -1)
     return;
 
-  registerhook(HOOK_TRUSTS_NEWNICK, policycheck);
-  registerhook(HOOK_TRUSTS_LOSTNICK, policycheck);
+  m = getconfigitem("trusts_policy", "enforcepolicy_irc");
+  if(m)
+    enforcepolicy_irc = atoi(m->content);
+
+  m = getconfigitem("trusts_policy", "enforcepolicy_auth");
+  if(m)
+    enforcepolicy_auth = atoi(m->content);
+
+  m = getconfigitem("trusts_policy", "trustport");
+  if(m)
+    trustport = atoi(m->content);
+  else
+    trustport = DEFAULT_TRUSTPORT;
+
+  if(trustport)
+    listenerfd = createlistenersock(trustport);
+
+  loadtrustaccounts();
+
+  registerhook(HOOK_TRUSTS_NEWNICK, policycheck_irc);
+  registerhook(HOOK_TRUSTS_LOSTNICK, &policycheck_irc);
+  registerhook(HOOK_CORE_REHASH, trustaccounts_rehash);
+
+  registercontrolhelpcmd("trustpolicyirc", NO_DEVELOPER, 1, trusts_cmdtrustpolicyirc, "Usage: trustpolicyirc ?1|0?\nEnables or disables policy enforcement (IRC). Shows current status when no parameter is specified.");
+  registercontrolhelpcmd("trustpolicyauth", NO_DEVELOPER, 1, trusts_cmdtrustpolicyauth, "Usage: trustpolicyauth ?1|0?\nEnables or disables policy enforcement (IAuth). Shows current status when no parameter is specified.");
+  registercontrolhelpcmd("trustsockets", NO_DEVELOPER, 0, trusts_cmdtrustsockets, "Usage: trustsockets\nLists all currently active TRUST sockets.");
+
+  schedulerecurring(time(NULL)+1, 0, 5, trustdotimeout, NULL);
+  
+  urandom = fopen("/dev/urandom", "rb");
+  if(!urandom)
+    Error("trusts_policy", ERR_ERROR, "Couldn't open /dev/urandom.");
 }
 
 void _fini(void) {
+  trustsocket *sock, *next;
+
   if(countext == -1)
     return;
 
   releasetgext(countext);
 
-  deregisterhook(HOOK_TRUSTS_NEWNICK, policycheck);
-  deregisterhook(HOOK_TRUSTS_LOSTNICK, policycheck);
+  deregisterhook(HOOK_TRUSTS_NEWNICK, policycheck_irc);
+  deregisterhook(HOOK_TRUSTS_LOSTNICK, policycheck_irc);
+  deregisterhook(HOOK_CORE_REHASH, trustaccounts_rehash);
+
+  deregistercontrolcmd("trustpolicyirc", trusts_cmdtrustpolicyirc);
+  deregistercontrolcmd("trustpolicyauth", trusts_cmdtrustpolicyauth);
+  deregistercontrolcmd("trustsockets", trusts_cmdtrustsockets);
+  
+  deleteallschedules(trustdotimeout); 
+  if (urandom)
+    fclose(urandom);
+
+  if (listenerfd != -1)
+    deregisterhandler(listenerfd, 1);
+
+  for(sock=tslist;sock;) {
+    next = sock->next;
+
+    trustkillconnection(sock, "Unloading module.");
+    trustfreeconnection(sock, 0);
+
+    sock = next;
+  }
 }