]> jfr.im git - irc/quakenet/newserv.git/blobdiff - trusts/trusts_management.c
Merge throttle.
[irc/quakenet/newserv.git] / trusts / trusts_management.c
index c2669191584bf3a7f5d8de5bf5fb5f9f11087e9b..4520fb5af633f09ff8db9a03bc1d3d0d6e2c89ca 100644 (file)
@@ -1,17 +1,23 @@
 #include <stdio.h>
 #include <string.h>
 #include "../control/control.h"
+#include "../lib/version.h"
 #include "../lib/irc_string.h"
 #include "../lib/strlfunc.h"
 #include "../core/config.h"
 #include "../core/schedule.h"
+#include "../irc/irc.h"
 #include "../lib/stringbuf.h"
+#include "../control/control.h"
+#include "../control/control_policy.h"
 #include "trusts.h"
 
+MODULE_VERSION("");
+
 static void registercommands(int, void *);
 static void deregistercommands(int, void *);
 
-typedef int (*trustmodificationfn)(trustgroup *, char *arg);
+typedef int (*trustmodificationfn)(void *, char *arg, nick *, int);
 
 struct trustmodification {
   char name[50];
@@ -41,6 +47,25 @@ static int trusts_cmdtrustadd(void *source, int cargc, char **cargv) {
     return CMD_ERROR;
   }
 
+  if(!is_normalized_ipmask(&ip, bits)) {
+    controlreply(sender, "Invalid IP Mask.");
+    return CMD_ERROR;
+  }
+
+  /* Don't allow non-developers to add trusts for large subnets or modify protected groups. */
+  if (!noperserv_policy_command_permitted(NO_DEVELOPER, sender)) {
+    int minbits = irc_in_addr_is_ipv4(&ip)?TRUST_MIN_UNPRIVILEGED_BITS_IPV4:TRUST_MIN_UNPRIVILEGED_BITS_IPV6;
+    if(bits < minbits) {
+      controlreply(sender, "You don't have the necessary privileges to add a subnet larger than /%d.", irc_bitlen(&ip, minbits));
+      return CMD_ERROR;
+    }
+
+    if(tg->flags & TRUST_PROTECTED) {
+      controlreply(sender, "You don't have the necessary privileges to modify a protected trust group.");
+      return CMD_ERROR;
+    }
+  }
+
   /* OKAY! Lots of checking here!
    *
    * Need to check:
@@ -67,15 +92,13 @@ static int trusts_cmdtrustadd(void *source, int cargc, char **cargv) {
   if(superset) {
     /* a superset exists for us, we will be more specific than one existing host */
 
-    controlreply(sender, "Warning: this host already exists in another group, but this new host will override it as it has a smaller prefix.");
+    controlreply(sender, "Note: This host already exists in another group, but this new host will override it as it has a smaller prefix.");
   }
   if(subset) {
     /* a subset of us exists, we will be less specific than some existing hosts */
 
-    controlreply(sender, "Warning: this host already exists in at least one other group, the new host has a larger prefix and therefore will not override those hosts.");
+    controlreply(sender, "Note: This host already exists in at least one other group, the new host has a larger prefix and therefore will not override those hosts.");
   }
-  if(superset || subset)
-    controlreply(sender, "Adding anyway...");
 
   th = th_new(tg, host);
   if(!th) {
@@ -95,69 +118,68 @@ static int trusts_cmdtrustadd(void *source, int cargc, char **cargv) {
 static int trusts_cmdtrustgroupadd(void *source, int cargc, char **cargv) {
   nick *sender = source;
   char *name, *contact, *comment, createdby[ACCOUNTLEN + 2];
-  unsigned int howmany, maxperident, enforceident;
-  time_t howlong, expires;
+  long howmany, maxperident, enforceident;
   trustgroup *tg, itg;
+  int override, flags;
 
-  if(cargc < 6)
+  if(cargc < 5)
     return CMD_USAGE;
 
+  override = noperserv_policy_command_permitted(NO_DEVELOPER, sender);
+
   name = cargv[0];
-  howmany = strtoul(cargv[1], NULL, 10);
-  if(!howmany || (howmany > MAXTRUSTEDFOR)) {
+  howmany = strtol(cargv[1], NULL, 10);
+  if(!override && (!howmany || (howmany > MAXTRUSTEDFOR))) {
     controlreply(sender, "Bad value maximum number of clients.");
     return CMD_ERROR;
   }
 
-  howlong = durationtolong(cargv[2]);
-  if((howlong < 0) || (howlong > MAXDURATION)) {
-    controlreply(sender, "Invalid duration supplied.");
-    return CMD_ERROR;
-  }
-
-  if(howlong)
-    expires = howlong + time(NULL);
-  else
-    expires = 0;
-
-  maxperident = strtoul(cargv[3], NULL, 10);
-  if(!howmany || (maxperident > MAXPERIDENT)) {
+  maxperident = strtol(cargv[2], NULL, 10);
+  if(maxperident < 0 || (maxperident > MAXPERIDENT)) {
     controlreply(sender, "Bad value for max per ident.");
     return CMD_ERROR;
   }
 
-  if(cargv[4][0] != '1' && cargv[4][0] != '0') {
+  if(cargv[3][0] != '1' && cargv[3][0] != '0') {
     controlreply(sender, "Bad value for enforce ident (use 0 or 1).");
     return CMD_ERROR;
   }
-  enforceident = cargv[4][0] == '1';
+  enforceident = cargv[3][0] == '1';
 
-  contact = cargv[5];
+  contact = cargv[4];
 
-  if(cargc < 7) {
+  if(cargc < 6) {
     comment = "(no comment)";
   } else {
-    comment = cargv[6];
+    comment = cargv[5];
   }
 
   /* don't allow #id or id forms */
-  if((name[0] == '#') || strtoul(name, NULL, 10)) {
+  if((name[0] == '#') || strtol(name, NULL, 10)) {
     controlreply(sender, "Invalid trustgroup name.");
     return CMD_ERROR;
   }
 
   tg = tg_strtotg(name);
   if(tg) {
-    controlreply(sender, "A group with that name already exists");
+    controlreply(sender, "A group with that name already exists.");
     return CMD_ERROR;
   }
 
   snprintf(createdby, sizeof(createdby), "#%s", sender->authname);
 
+  flags = 0;
+
+  if(maxperident > 0)
+    flags |= TRUST_RELIABLE_USERNAME;
+
+  if(enforceident)
+    flags |= TRUST_ENFORCE_IDENT;
+
   itg.trustedfor = howmany;
-  itg.mode = enforceident;
+  itg.flags = flags;
   itg.maxperident = maxperident;
-  itg.expires = expires;
+  itg.expires = 0;
   itg.createdby = getsstring(createdby, CREATEDBYLEN);
   itg.contact = getsstring(contact, CONTACTLEN);
   itg.comment = getsstring(comment, COMMENTLEN);
@@ -184,8 +206,8 @@ static int trusts_cmdtrustgroupadd(void *source, int cargc, char **cargv) {
 
   controlwall(NO_OPER, NL_TRUSTS, "%s TRUSTGROUPADD'ed '%s'", controlid(sender), tg->name->content);
   trustlog(tg, sender->authname, "Created trust group '%s' (ID #%d): howmany=%d, enforceident=%d, maxperident=%d, "
-    "expires=%d, createdby=%s, contact=%s, comment=%s",
-    tg->name->content, howmany, tg->id, enforceident, maxperident, expires, createdby, contact, comment);
+    "createdby=%s, contact=%s, comment=%s",
+    tg->name->content, howmany, tg->id, enforceident, maxperident, createdby, contact, comment);
 
   return CMD_OK;
 }
@@ -203,6 +225,14 @@ static int trusts_cmdtrustgroupdel(void *source, int cargc, char **cargv) {
     return CMD_ERROR;
   }
 
+  /* Don't allow non-developers to delete protected groups. */
+  if (!noperserv_policy_command_permitted(NO_DEVELOPER, sender)) {
+    if(tg->flags & TRUST_PROTECTED) {
+      controlreply(sender, "You don't have the necessary privileges to modify a protected trust group.");
+      return CMD_ERROR;
+    }
+  }
+
   if(tg->hosts) {
     controlreply(sender, "Delete all hosts before deleting the group.");
     return CMD_ERROR;
@@ -241,6 +271,20 @@ static int trusts_cmdtrustdel(void *source, int cargc, char **cargv) {
     return CMD_ERROR;
   }
 
+  /* Don't allow non-developers to remove trusts for large subnets or modify protected groups. */
+  if (!noperserv_policy_command_permitted(NO_DEVELOPER, sender)) {
+    int minbits = irc_in_addr_is_ipv4(&ip)?TRUST_MIN_UNPRIVILEGED_BITS_IPV4:TRUST_MIN_UNPRIVILEGED_BITS_IPV6;
+    if(bits < minbits) {
+      controlreply(sender, "You don't have the necessary privileges to remove a subnet larger than /%d.", irc_bitlen(&ip, minbits));
+      return CMD_ERROR;
+    }
+
+    if(tg->flags & TRUST_PROTECTED) {
+      controlreply(sender, "You don't have the necessary privileges to modify a protected trust group.");
+      return CMD_ERROR;
+    }
+  }
+
   for(th=tg->hosts;th;th=th->next)
     if(ipmask_check(&ip, &th->ip, th->bits) && th->bits == bits)
       break;
@@ -260,7 +304,8 @@ static int trusts_cmdtrustdel(void *source, int cargc, char **cargv) {
   return CMD_OK;
 }
 
-static int modifycomment(trustgroup *tg, char *comment) {
+static int modifycomment(void *arg, char *comment, nick *source, int override) {
+  trustgroup *tg = arg;
   sstring *n = getsstring(comment, COMMENTLEN);
   if(!n)
     return 0;
@@ -271,7 +316,8 @@ static int modifycomment(trustgroup *tg, char *comment) {
   return 1;
 }
 
-static int modifycontact(trustgroup *tg, char *contact) {
+static int modifycontact(void *arg, char *contact, nick *source, int override) {
+  trustgroup *tg = arg;
   sstring *n = getsstring(contact, CONTACTLEN);
   if(!n)
     return 0;
@@ -282,33 +328,58 @@ static int modifycontact(trustgroup *tg, char *contact) {
   return 1;
 }
 
-static int modifytrustedfor(trustgroup *tg, char *num) {
-  unsigned int trustedfor = strtoul(num, NULL, 10);
+static int modifytrustedfor(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+  long trustedfor = strtol(num, NULL, 10);
 
-  if(trustedfor > MAXTRUSTEDFOR)
+  if(trustedfor < 0) {
+    controlreply(source, "The clone limit must not be negative.");
     return 0;
+  }
+
+  if(!override) {
+    if (trustedfor == 0) {
+      controlreply(source, "You don't have the necessary privileges to set an unlimited clone limit.");
+      return 0;
+    }
+
+    if (trustedfor > MAXTRUSTEDFOR) {
+      controlreply(source, "You don't have the necessary privileges to set the clone limit to a value higher than %d.", MAXTRUSTEDFOR);
+      return 0;
+    }
+  }
 
   tg->trustedfor = trustedfor;
 
   return 1;
 }
 
-static int modifymaxperident(trustgroup *tg, char *num) {
-  unsigned int maxperident = strtoul(num, NULL, 10);
+static int modifymaxperident(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+  long maxperident = strtol(num, NULL, 10);
+
+  if(maxperident < 0) {
+    controlreply(source, "Ident limit must not be negative.");
+    return 0;
+  }
 
-  if(maxperident > MAXPERIDENT)
+  if(maxperident > MAXPERIDENT) {
+    controlreply(source, "Ident limit must not be higher than %d. Consider setting it to 0 (unlimited) instead.", MAXPERIDENT);
     return 0;
+  }
 
   tg->maxperident = maxperident;
 
   return 1;
 }
 
-static int modifyenforceident(trustgroup *tg, char *num) {
+static int modifyenforceident(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+
   if(num[0] == '1') {
-    tg->mode = 1;
+    tg->flags |= TRUST_ENFORCE_IDENT;
   } else if(num[0] == '0') {
-    tg->mode = 0;
+    tg->flags &= ~TRUST_ENFORCE_IDENT;
   } else {
     return 0;
   }
@@ -316,29 +387,154 @@ static int modifyenforceident(trustgroup *tg, char *num) {
   return 1;
 }
 
-static int modifyexpires(trustgroup *tg, char *expires) {
+static int modifyreliableusername(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+
+  if(num[0] == '1') {
+    tg->flags |= TRUST_RELIABLE_USERNAME;
+  } else if(num[0] == '0') {
+    tg->flags &= ~TRUST_RELIABLE_USERNAME;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int modifyunthrottle(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+
+  if(num[0] == '1') {
+    tg->flags |= TRUST_UNTHROTTLE;
+  } else if(num[0] == '0') {
+    tg->flags &= ~TRUST_UNTHROTTLE;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int modifyexpires(void *arg, char *expires, nick *source, int override) {
+  trustgroup *tg = arg;
   int howlong = durationtolong(expires);
 
-  if((howlong < 0) || (howlong > MAXDURATION))
+  if((howlong < 0) || (howlong > MAXDURATION)) {
+    controlreply(source, "Duration cannot be negative or greater than %s (use 0 instead if you don't want the group to expire).", longtoduration(MAXDURATION, 0));
     return 0;
+  }
 
   if(howlong)
-    tg->expires = time(NULL) + howlong;
+    tg->expires = getnettime() + howlong;
   else
     tg->expires = 0; /* never */
 
   return 1;
 }
 
-static array trustmods_a;
-static struct trustmodification *trustmods;
+static int modifycleanup(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+
+  if(!override) {
+    controlreply(source, "You don't have the necessary privileges to modify this attribute.");
+    return 0;
+  }
+
+  if(num[0] == '1') {
+    tg->flags &= ~TRUST_NO_CLEANUP;
+  } else if(num[0] == '0') {
+    tg->flags |= TRUST_NO_CLEANUP;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int modifyprotected(void *arg, char *num, nick *source, int override) {
+  trustgroup *tg = arg;
+
+  if(!override) {
+    controlreply(source, "You don't have the necessary privileges to modify this attribute.");
+    return 0;
+  }
+
+  if(num[0] == '1') {
+    tg->flags |= TRUST_PROTECTED;
+  } else if(num[0] == '0') {
+    tg->flags &= ~TRUST_PROTECTED;
+  } else {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int modifymaxpernode(void *arg, char *num, nick *source, int override) {
+  trusthost *th = arg;
+  int maxpernode = strtol(num, NULL, 10);
+
+  if(maxpernode < 0) {
+    controlreply(source, "Node limit must not be negative.");
+    return 0;
+  }
+
+  if(maxpernode>MAXPERNODE) {
+    controlreply(source, "Node limit must not be higher than %d. Consider setting it to 0 (unlimited) instead.", MAXPERNODE);
+    return 0;
+  } 
+  th->maxpernode = maxpernode;
+
+  return 1;
+}
+
+static int modifynodebits(void *arg, char *num, nick *source, int override) {
+  trusthost *th = arg;
+  int nodebits = strtol(num, NULL, 10);
+
+  if(nodebits < 0) {
+    controlreply(source, "Node bits must not be negative.");
+    return 0;
+  }
+
+  if(irc_in_addr_is_ipv4(&th->ip))
+    nodebits += 96;
+
+  if(nodebits > 128) {
+    controlreply(source, "Node bits is invalid.");
+    return 0;
+  }
+
+  if(!override) {
+    int minbits = irc_in_addr_is_ipv4(&th->ip)?TRUST_MIN_UNPRIVILEGED_NODEBITS_IPV4:TRUST_MIN_UNPRIVILEGED_NODEBITS_IPV6;
+
+    if(nodebits < minbits) {
+      controlreply(source, "You don't have the necessary privileges to set node bits to a subnet larger than /%d.", irc_bitlen(&th->ip, minbits));
+      return 0;
+    }
+  }
+
+  if(nodebits<th->bits) {
+    controlreply(source, "Node bits must be smaller or equal to the trusted CIDR's subnet size.");
+    return 0;
+  }
+
+  th->nodebits = nodebits;
+
+  return 1;
+}
+
+static array trustgroupmods_a;
+static struct trustmodification *trustgroupmods;
+static array trusthostmods_a;
+static struct trustmodification *trusthostmods;
 
 static int trusts_cmdtrustgroupmodify(void *source, int cargc, char **cargv) {
   trustgroup *tg;
   nick *sender = source;
-  char *what, *to, validfields[512];
-  int i;
-  StringBuf b;
+  char *what, *to;
+  int i, override;
 
   if(cargc < 3)
     return CMD_USAGE;
@@ -352,26 +548,26 @@ static int trusts_cmdtrustgroupmodify(void *source, int cargc, char **cargv) {
   what = cargv[1];
   to = cargv[2];
 
-  sbinit(&b, validfields, sizeof(validfields));
-  for(i=0;i<trustmods_a.cursi;i++) {
-    if(!strcmp(what, trustmods[i].name)) {
-      if(!(trustmods[i].fn)(tg, to)) {
+  override = noperserv_policy_command_permitted(NO_DEVELOPER, sender);
+
+  /* Don't allow non-developers to modify protected groups. */
+  if (!override && tg->flags & TRUST_PROTECTED) {
+    controlreply(sender, "You don't have the necessary privileges to modify a protected trust group.");
+    return CMD_ERROR;
+  }
+
+  for(i=0;i<trustgroupmods_a.cursi;i++) {
+    if(!strcmp(what, trustgroupmods[i].name)) {
+      if(!(trustgroupmods[i].fn)(tg, to, sender, override)) {
         controlreply(sender, "An error occured changing that property, check the syntax.");
         return CMD_ERROR;
       }
       break;
     }
-
-    if(i > 0)
-      sbaddstr(&b, ", ");
-    sbaddstr(&b, trustmods[i].name);
   }
 
-  if(i == trustmods_a.cursi) {
-    sbterminate(&b);
-    controlreply(sender, "No such field, valid fields are: %s", validfields);
-    return CMD_ERROR;
-  }
+  if(i == trustgroupmods_a.cursi)
+    return CMD_USAGE;
 
   triggerhook(HOOK_TRUSTS_MODIFYGROUP, tg);
   tg_update(tg);
@@ -383,17 +579,89 @@ static int trusts_cmdtrustgroupmodify(void *source, int cargc, char **cargv) {
   return CMD_OK;
 }
 
-static int trusts_cmdtrustlogspew(void *source, int cargc, char **cargv) {
+static int trusts_cmdtrusthostmodify(void *source, int cargc, char **cargv) {
+  trustgroup *tg;
+  trusthost *th;
+  nick *sender = source;
+  char *what, *to;
+  int i, override;
+  struct irc_in_addr ip;
+  unsigned char bits;
+
+  if(cargc < 4)
+    return CMD_USAGE;
+
+  tg = tg_strtotg(cargv[0]);
+  if(!tg) {
+    controlreply(sender, "Couldn't look up trustgroup.");
+    return CMD_ERROR;
+  }
+
+  if(!ipmask_parse(cargv[1], &ip, &bits)) {
+    controlreply(sender, "Invalid host.");
+    return CMD_ERROR;
+  }
+
+  /* Don't allow non-developers to modify trusts for large subnets or modify protected groups. */
+  if (!noperserv_policy_command_permitted(NO_DEVELOPER, sender)) {
+    int minbits = irc_in_addr_is_ipv4(&ip)?TRUST_MIN_UNPRIVILEGED_BITS_IPV4:TRUST_MIN_UNPRIVILEGED_BITS_IPV6;
+    if(bits < minbits) {
+      controlreply(sender, "You don't have the necessary privileges to modify a subnet larger than /%d.", irc_bitlen(&ip, minbits));
+      return CMD_ERROR;
+    }
+
+    if(tg->flags & TRUST_PROTECTED) {
+      controlreply(sender, "You don't have the necessary privileges to modify a protected trust group.");
+      return CMD_ERROR;
+    }
+  }
+
+  th = th_getbyhostandmask(&ip, bits);
+
+  if(!th || th->group != tg) {
+    controlreply(sender, "Host does not belong to the specified group.");
+    return CMD_ERROR;
+  }
+
+  what = cargv[2];
+  to = cargv[3];
+
+  override = noperserv_policy_command_permitted(NO_DEVELOPER, sender);
+
+  for(i=0;i<trusthostmods_a.cursi;i++) {
+    if(!strcmp(what, trusthostmods[i].name)) {
+      if(!(trusthostmods[i].fn)(th, to, sender, override)) {
+        controlreply(sender, "An error occured changing that property, check the syntax.");
+        return CMD_ERROR;
+      }
+      break;
+    }
+  }
+
+  if(i == trusthostmods_a.cursi)
+    return CMD_USAGE;
+
+  triggerhook(HOOK_TRUSTS_MODIFYHOST, th);
+  th_update(th);
+  controlreply(sender, "Host modified.");
+
+  controlwall(NO_OPER, NL_TRUSTS, "%s TRUSTMODIFIED'ed host '%s' in group '%s' (field: %s, value: %s)", controlid(sender), CIDRtostr(ip, bits), tg->name->content, what, to);
+  trustlog(tg, sender->authname, "Modified %s for host '%s': %s", what, CIDRtostr(ip, bits), to);
+
+  return CMD_OK;
+}
+
+static int trusts_cmdtrustlog(void *source, int cargc, char **cargv) {
   nick *sender = source;
   char *name;
   int groupid;
-  int limit = 0;
+  long limit = 0;
 
   if(cargc < 1)
     return CMD_USAGE;
 
   if(cargc>1)
-    limit = strtoul(cargv[1], NULL, 10);
+    limit = strtol(cargv[1], NULL, 10);
 
   if(limit==0)
     limit = 100;
@@ -401,7 +669,7 @@ static int trusts_cmdtrustlogspew(void *source, int cargc, char **cargv) {
   name = cargv[0];
 
   if (name[0] == '#') {
-    groupid = strtoul(name + 1, NULL, 10);
+    groupid = strtol(name + 1, NULL, 10);
     trustlogspewid(sender, groupid, limit);
   } else {
     trustlogspewname(sender, name, limit);
@@ -413,7 +681,7 @@ static int trusts_cmdtrustlogspew(void *source, int cargc, char **cargv) {
 static int trusts_cmdtrustloggrep(void *source, int cargc, char **cargv) {
   nick *sender = source;
   char *pattern;
-  int limit = 0;
+  long limit = 0;
 
   if(cargc < 1)
     return CMD_USAGE;
@@ -421,7 +689,7 @@ static int trusts_cmdtrustloggrep(void *source, int cargc, char **cargv) {
   pattern = cargv[0];
 
   if(cargc>1)
-    limit = strtoul(cargv[1], NULL, 10);
+    limit = strtol(cargv[1], NULL, 10);
 
   if(limit==0)
     limit = 100;
@@ -442,6 +710,11 @@ static int trusts_cmdtrustcomment(void *source, int cargc, char **cargv) {
   name = cargv[0];
   comment = cargv[1];
 
+  if(strlen(comment)>TRUSTLOGLEN) {
+    controlreply(sender, "Your comment is too long (max: %d characters).", TRUSTLOGLEN);
+    return CMD_OK;
+  }
+
   tg = tg_strtotg(name);
 
   if(!tg) {
@@ -449,7 +722,7 @@ static int trusts_cmdtrustcomment(void *source, int cargc, char **cargv) {
     return CMD_OK;
   }
 
-    controlwall(NO_OPER, NL_TRUSTS, "%s TRUSTCOMMENT'ed group '%s': %s", controlid(sender), tg->name->content, comment);
+  controlwall(NO_OPER, NL_TRUSTS, "%s TRUSTCOMMENT'ed group '%s': %s", controlid(sender), tg->name->content, comment);
   trustlog(tg, sender->authname, "Comment: %s", comment);
 
   return CMD_OK;
@@ -469,16 +742,43 @@ static int trusts_cmdtrustcleanup(void *source, int cargc, char **cargv) {
 static int commandsregistered;
 
 static void registercommands(int hooknum, void *arg) {
+  static char tgmhelp[512], thmhelp[512];
+  char validfields[512];
+  StringBuf b;
+  int i;
+
   if(commandsregistered)
     return;
   commandsregistered = 1;
 
-  registercontrolhelpcmd("trustgroupadd", NO_OPER, 7, trusts_cmdtrustgroupadd, "Usage: trustgroupadd <name> <howmany> <howlong> <maxperident> <enforceident> <contact> ?comment?");
+  registercontrolhelpcmd("trustgroupadd", NO_OPER, 6, trusts_cmdtrustgroupadd, "Usage: trustgroupadd <name> <howmany> <maxperident> <enforceident> <contact> ?comment?");
   registercontrolhelpcmd("trustadd", NO_OPER, 2, trusts_cmdtrustadd, "Usage: trustadd <#id|name|id> <host>");
   registercontrolhelpcmd("trustgroupdel", NO_OPER, 1, trusts_cmdtrustgroupdel, "Usage: trustgroupdel <#id|name|id>");
   registercontrolhelpcmd("trustdel", NO_OPER, 2, trusts_cmdtrustdel, "Usage: trustdel <#id|name|id> <ip/mask>");
-  registercontrolhelpcmd("trustgroupmodify", NO_OPER, 3, trusts_cmdtrustgroupmodify, "Usage: trustgroupmodify <#id|name|id> <field> <new value>");
-  registercontrolhelpcmd("trustlogspew", NO_OPER, 2, trusts_cmdtrustlogspew, "Usage: trustlogspew <#id|name> ?limit?\nShows log for the specified trust group.");
+
+  sbinit(&b, validfields, sizeof(validfields));
+  for(i=0;i<trustgroupmods_a.cursi;i++) {
+    if(i > 0)
+      sbaddstr(&b, ", ");
+    sbaddstr(&b, trustgroupmods[i].name);
+  }
+  sbterminate(&b);
+
+  snprintf(tgmhelp, sizeof(tgmhelp), "Usage: trustgroupmodify <#id|name|id> <field> <new value>\nModifies a trust group.\nValid fields: %s", validfields);
+  registercontrolhelpcmd("trustgroupmodify", NO_OPER, 3, trusts_cmdtrustgroupmodify, tgmhelp);
+
+  sbinit(&b, validfields, sizeof(validfields));
+  for(i=0;i<trusthostmods_a.cursi;i++) {
+    if(i > 0)
+      sbaddstr(&b, ", ");
+    sbaddstr(&b, trusthostmods[i].name);
+  }
+  sbterminate(&b);
+
+  snprintf(thmhelp, sizeof(thmhelp), "Usage: trusthostmodify <#id|name|id> <host> <field> <new value>\nModifies a trust host\nValid fields: %s", validfields);
+  registercontrolhelpcmd("trusthostmodify", NO_OPER, 4, trusts_cmdtrusthostmodify, thmhelp);
+
+  registercontrolhelpcmd("trustlog", NO_OPER, 2, trusts_cmdtrustlog, "Usage: trustlog <#id|name> ?limit?\nShows log for the specified trust group.");
   registercontrolhelpcmd("trustloggrep", NO_OPER, 2, trusts_cmdtrustloggrep, "Usage trustloggrep <pattern> ?limit?\nShows maching log entries.");
   registercontrolhelpcmd("trustcomment", NO_OPER, 2, trusts_cmdtrustcomment, "Usage: trustcomment <#id|name> <comment>\nLogs a comment for a trust.");
   registercontrolhelpcmd("trustcleanup", NO_DEVELOPER, 0, trusts_cmdtrustcleanup, "Usage: trustcleanup\nCleans up unused trusts.");
@@ -494,7 +794,8 @@ static void deregistercommands(int hooknum, void *arg) {
   deregistercontrolcmd("trustgroupdel", trusts_cmdtrustgroupdel);
   deregistercontrolcmd("trustdel", trusts_cmdtrustdel);
   deregistercontrolcmd("trustgroupmodify", trusts_cmdtrustgroupmodify);
-  deregistercontrolcmd("trustlogspew", trusts_cmdtrustlogspew);
+  deregistercontrolcmd("trusthostmodify", trusts_cmdtrusthostmodify);
+  deregistercontrolcmd("trustlog", trusts_cmdtrustlog);
   deregistercontrolcmd("trustloggrep", trusts_cmdtrustloggrep);
   deregistercontrolcmd("trustcomment", trusts_cmdtrustcomment);
   deregistercontrolcmd("trustcleanup", trusts_cmdtrustcleanup);
@@ -503,15 +804,23 @@ static void deregistercommands(int hooknum, void *arg) {
 static int loaded;
 
 #define _ms_(x) (struct trustmodification){ .name = # x, .fn = modify ## x }
-#define MS(x) { int slot = array_getfreeslot(&trustmods_a); trustmods = (struct trustmodification *)trustmods_a.content; memcpy(&trustmods[slot], &_ms_(x), sizeof(struct trustmodification)); }
+#define MSGROUP(x) { int slot = array_getfreeslot(&trustgroupmods_a); trustgroupmods = (struct trustmodification *)trustgroupmods_a.content; memcpy(&trustgroupmods[slot], &_ms_(x), sizeof(struct trustmodification)); }
+#define MSHOST(x) { int slot = array_getfreeslot(&trusthostmods_a); trusthostmods = (struct trustmodification *)trusthostmods_a.content; memcpy(&trusthostmods[slot], &_ms_(x), sizeof(struct trustmodification)); }
 
 static void setupmods(void) {
-  MS(expires);
-  MS(enforceident);
-  MS(maxperident);
-  MS(contact);
-  MS(comment);
-  MS(trustedfor);
+  MSGROUP(expires);
+  MSGROUP(enforceident);
+  MSGROUP(reliableusername);
+  MSGROUP(maxperident);
+  MSGROUP(contact);
+  MSGROUP(comment);
+  MSGROUP(trustedfor);
+  MSGROUP(cleanup);
+  MSGROUP(protected);
+  MSGROUP(unthrottle);
+
+  MSHOST(maxpernode);
+  MSHOST(nodebits);
 }
 
 static int cleanuptrusts_active;
@@ -525,7 +834,7 @@ static void cleanuptrusts(void *arg) {
   int i;
   array expiredths, expiredtgs;
 
-  now = time(NULL);
+  now = getnettime();
   to_age = now - (CLEANUP_TH_INACTIVE * 3600 * 24);
 
   if(np) {
@@ -546,6 +855,9 @@ static void cleanuptrusts(void *arg) {
   for(tg=tglist;tg;tg=tg->next) {
     array_init(&expiredths, sizeof(trusthost *));
 
+    if(tg->flags & TRUST_NO_CLEANUP)
+      continue;
+
     for(th=tg->hosts;th;th=th->next) {
       if((th->count == 0 && th->created < to_age && th->lastseen < to_age) || (tg->expires && tg->expires < now)) {
         int pos = array_getfreeslot(&expiredths);
@@ -554,15 +866,16 @@ static void cleanuptrusts(void *arg) {
     }
 
     for(i=0;i<expiredths.cursi;i++) {
-      char *cidrstr;
+      const char *cidrstr;
 
       th = ((trusthost **)(expiredths.content))[i];
       triggerhook(HOOK_TRUSTS_DELHOST, th);
-      th_delete(th);
 
-      cidrstr = trusts_cidr2str(&th->ip, th->bits);
+      cidrstr = CIDRtostr(th->ip, th->bits);
       trustlog(tg, "cleanuptrusts", "Removed host '%s' because it was unused for %d days.", cidrstr, CLEANUP_TH_INACTIVE);
 
+      th_delete(th);
+
       thcount++;
     }
 
@@ -580,7 +893,7 @@ static void cleanuptrusts(void *arg) {
     tgcount++;
   }
 
-  controlwall(NO_OPER, NL_TRUSTS, "CLEANUPTRUSTS: Removed %d trust hosts (inactive for %d days) and %d trust groups.", thcount, CLEANUP_TH_INACTIVE, tgcount);
+  controlwall(NO_OPER, NL_TRUSTS, "CLEANUPTRUSTS: Removed %d trust hosts (inactive for %d days) and %d empty trust groups.", thcount, CLEANUP_TH_INACTIVE, tgcount);
 
   cleanuptrusts_active=0;
 }
@@ -599,7 +912,8 @@ static void schedulecleanup(int hooknum, void *arg) {
 void _init(void) {
   sstring *m;
 
-  array_init(&trustmods_a, sizeof(struct trustmodification));
+  array_init(&trustgroupmods_a, sizeof(struct trustmodification));
+  array_init(&trusthostmods_a, sizeof(struct trustmodification));
   setupmods();
 
   m = getconfigitem("trusts", "master");
@@ -619,7 +933,8 @@ void _init(void) {
 }
 
 void _fini(void) {
-  array_free(&trustmods_a);
+  array_free(&trustgroupmods_a);
+  array_free(&trusthostmods_a);
 
   if(!loaded)
     return;