]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/chanserv.c
This should fix the nickserv module complaining about not enough parameters when...
[irc/evilnet/x3.git] / src / chanserv.c
index 5a4ae60d8568a69a8dd3b0e6e6f2fece04914ce9..1410843880e3060502caba98a8d3b1372d0797aa 100644 (file)
 #define KEY_REVOKED             "revoked"
 #define KEY_SUSPEND_EXPIRES     "suspend_expires"
 #define KEY_SUSPEND_REASON      "suspend_reason"
+#define KEY_GIVEOWNERSHIP       "giveownership"
+#define KEY_STAFF_ISSUER        "staff_issuer"
+#define KEY_OLD_OWNER           "old_owner"
+#define KEY_TARGET              "target"
+#define KEY_TARGET_ACCESS       "target_access"
 #define KEY_VISITED            "visited"
 #define KEY_TOPIC              "topic"
 #define KEY_GREETING           "greeting"
@@ -349,8 +354,14 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_BANS_REMOVED", "Removed all channel bans from $b%s$b." },
 
 /* Channel userlist */
-    { "CSMSG_ACCESS_ALL_HEADER", "$b%s Users From Level %s To %s$b" },
-    { "CSMSG_ACCESS_SEARCH_HEADER", "$b%s Users From Level %s To %s Matching %s$b" },
+    { "CSMSG_ACCESS_ALL_HEADER_NORMAL",      "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_NORMAL",   "$b%s Users From Level %s To %s Matching %s$b" },
+    /* uncomment if needed to adujust styles (and change code below)
+    { "CSMSG_ACCESS_ALL_HEADER_CLEAN",       "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_CLEAN",    "$b%s Users From Level %s To %s Matching %s$b" },
+    { "CSMSG_ACCESS_ALL_HEADER_ADVANCED",    "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", "$b%s Users From Level %s To %s Matching %s$b" },
+    */
     { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
     { "CSMSG_CHANGED_ACCESS", "%s now has access $b%s$b (%u) in %s." },
     { "CSMSG_LAMERS_HEADER", "$bLamers in %s$b" },
@@ -427,7 +438,12 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_CHANNEL_SUSPENDED_7", " %s ago by %s; revoked %s ago: %s" },
     { "CSMSG_CHANNEL_REGISTERED", "$bRegistered:          $b%s ago." },
     { "CSMSG_CHANNEL_VISITED", "$bVisited:             $b%s ago." },
+    { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for $b%s$b" },
+    { "CSMSG_CHANNEL_OWNERSHIP_NORMAL", "from %s to %s (%d access) on %s" },
+    { "CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", "from %s to %s (%d access) by %s on %s reason: %s" },
+    { "CSMSG_CHANNEL_OWNERSHIP_STAFF", "from %s to %s (%d access) by %s on %s" },
     { "CSMSG_CHANNEL_END",  "---------------End of Info--------------"},
+    { "CSMSG_CHANNEL_END_CLEAN",  "End of Info"},
 
     { "CSMSG_PEEK_INFO", "$bStatus of %s$b" },
     { "CSMSG_PEEK_TOPIC", "$bTopic:          $b%s" },
@@ -1798,7 +1814,8 @@ static CHANSERV_FUNC(cmd_noregister)
         dict_iterator_t it;
 
         reply("CSMSG_DNR_SEARCH_RESULTS");
-        reply("CSMSG_BAR");
+        if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+            reply("CSMSG_BAR");
         matches = 0;
         for(it = dict_first(handle_dnrs); it; it = iter_next(it))
         {
@@ -1857,7 +1874,8 @@ static CHANSERV_FUNC(cmd_noregister)
     }
 
     reply("CSMSG_DNR_SEARCH_RESULTS");
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     if(*target == '*')
         matches = chanserv_show_dnrs(user, cmd, NULL, target + 1);
     else
@@ -2388,8 +2406,20 @@ merge_bans(struct chanData *source, struct chanData *target)
 static void
 merge_data(struct chanData *source, struct chanData *target)
 {
+    /* Use more recent visited and owner-transfer time; use older
+     * registered time.  Bitwise or may_opchan.  Use higher max.
+     * Do not touch last_refresh, ban count or user counts.
+     */
     if(source->visited > target->visited)
        target->visited = source->visited;
+    if(source->registered < target->registered)
+        target->registered = source->registered;
+    if(source->ownerTransfer > target->ownerTransfer)
+        target->ownerTransfer = source->ownerTransfer;
+    if(source->may_opchan)
+        target->may_opchan = 1;
+    if(source->max > target->max)
+        target->max = source->max;
 }
 
 static void
@@ -3002,7 +3032,7 @@ static CHANSERV_FUNC(cmd_devoice)
 }
 
 static int
-bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, int *victimCount, struct modeNode **victims)
+bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
 {
     unsigned int ii;
 
@@ -3841,13 +3871,31 @@ zoot_list(struct listData *list)
 */
 
 static void
-def_list(struct listData *list)
+normal_list(struct listData *list)
+{
+    const char *msg;
+    if(list->search)
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+    else
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
+    if(list->table.length == 1)
+    {
+        msg = user_find_message(list->user, "MSG_NONE");
+        send_message_type(4, list->user, list->bot, "  %s", msg);
+    }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
+}
+
+/* if these need changed, uncomment and customize 
+static void
+clean_list(struct listData *list)
 {
     const char *msg;
     if(list->search)
-        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
     else
-        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
     if(list->table.length == 1)
     {
         msg = user_find_message(list->user, "MSG_NONE");
@@ -3857,6 +3905,25 @@ def_list(struct listData *list)
         table_send(list->bot, list->user->nick, 0, NULL, list->table);
 }
 
+static void
+advanced_list(struct listData *list)
+{
+    const char *msg;
+    if(list->search)
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+    else
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
+    if(list->table.length == 1)
+    {
+        msg = user_find_message(list->user, "MSG_NONE");
+        send_message_type(4, list->user, list->bot, "  %s", msg);
+    }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
+} 
+
+*/
+
 static int
 userData_access_comp(const void *arg_a, const void *arg_b)
 {
@@ -3878,6 +3945,8 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     struct listData lData;
     unsigned int matches;
     const char **ary;
+    int i = 0;
+    int seen_index;
 
     lData.user = user;
     lData.bot = cmd->parent->bot;
@@ -3885,20 +3954,29 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     lData.lowest = lowest;
     lData.highest = highest;
     lData.search = (argc > 1) ? argv[1] : NULL;
-    send_list = def_list;
+    send_list = normal_list;
     /* What does the following line do exactly?? */
     /*(void)zoot_list; ** since it doesn't show user levels */
 
-    /* this does nothing!! -rubin
+    /*
     if(user->handle_info)
     {
-       switch(user->handle_info->userlist_style)
-       {
-       case HI_STYLE_DEF: send_list = def_list; break;
-        case HI_STYLE_ZOOT: send_list = def_list; break;
-       }
+        switch(user->handle_info->userlist_style)
+        {
+            case HI_STYLE_CLEAN: 
+                send_list = clean_list; 
+                break;
+            case HI_STYLE_ADVANCED: 
+                send_list = advanced_list; 
+                break;
+            case HI_STYLE_NORMAL: 
+            default: 
+                send_list = normal_list; 
+                break;
+        }
     }
     */
+    send_list = normal_list;
 
     lData.users = alloca(channel->channel_info->userCount * sizeof(struct userData *));
     matches = 0;
@@ -3913,45 +3991,54 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     qsort(lData.users, matches, sizeof(lData.users[0]), userData_access_comp);
 
     lData.table.length = matches+1;
-    lData.table.width = 5;
     lData.table.flags = TABLE_NO_FREE;
     lData.table.contents = malloc(lData.table.length*sizeof(*lData.table.contents));
+
+    if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
+        lData.table.width = 5; /* with level = 5 */
+    else
+        lData.table.width = 4; /* without = 4 */
     ary = malloc(lData.table.width*sizeof(**lData.table.contents));
     lData.table.contents[0] = ary;
-    ary[0] = "Access";
-    ary[1] = "Level";
-    ary[2] = "Account";
-    ary[3] = "Last Seen";
-    ary[4] = "Status";
+    ary[i++] = "Access";
+    if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
+        ary[i++] = "Level"; /* Only on advanced view */
+    ary[i++] = "Account";
+    ary[i] = "Last Seen";
+    seen_index = i++;
+    ary[i++] = "Status";
     for(matches = 1; matches < lData.table.length; ++matches)
     {
         struct userData *uData = lData.users[matches-1];
         char seen[INTERVALLEN];
 
+        i = 0;
         ary = malloc(lData.table.width*sizeof(**lData.table.contents));
         lData.table.contents[matches] = ary;
-        /* ary[0] = strtab(uData->access);*/
-        ary[0] = user_level_name_from_level(uData->access);
-        ary[1] = strtab(uData->access);
-        ary[2] = uData->handle->handle;
+        ary[i++] = user_level_name_from_level(uData->access);
+        if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
+            ary[i++] = strtab(uData->access);
+        ary[i++] = uData->handle->handle;
         if(uData->present)
-            ary[3] = "Here";
+            ary[i] = "Here";
         else if(!uData->seen)
-            ary[3] = "Never";
+            ary[i] = "Never";
         else
-            ary[3] = intervalString(seen, now - uData->seen, user->handle_info);
-        ary[3] = strdup(ary[3]);
+            ary[i] = intervalString(seen, now - uData->seen, user->handle_info);
+        ary[i] = strdup(ary[i]);
+        i++;
         if(IsUserSuspended(uData))
-            ary[4] = "Suspended";
+            ary[i++] = "Suspended";
         else if(HANDLE_FLAGGED(uData->handle, FROZEN))
-            ary[4] = "Vacation";
+            ary[i++] = "Vacation";
         else
-            ary[4] = "Normal";
+            ary[i++] = "Normal";
     }
     send_list(&lData);
     for(matches = 1; matches < lData.table.length; ++matches)
     {
-        free((char*)lData.table.contents[matches][3]);
+        /* Free strdup above */
+        free((char*)lData.table.contents[matches][seen_index]);
         free(lData.table.contents[matches]);
     }
     free(lData.table.contents[0]);
@@ -3965,7 +4052,8 @@ static CHANSERV_FUNC(cmd_pending)
 {
     struct adduserPending *ap;
     reply("CSMSG_ADDUSER_PENDING_HEADER");
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     for(ap = adduser_pendings;ap;ap = ap->next)
         reply("CSMSG_ADDUSER_PENDING_LIST", ap->channel->name, ap->user->nick);
     reply("CSMSG_ADDUSER_PENDING_FOOTER");
@@ -4170,13 +4258,18 @@ static CHANSERV_FUNC(cmd_topic)
 {
     struct chanData *cData;
     char *topic;
+    int p10 = 0;
+
+#ifdef WITH_PROTOCOL_P10
+    p10 = 1;
+#endif
 
     cData = channel->channel_info;
     if(argc < 2)
     {
         if(cData->topic)
         {
-            SetChannelTopic(channel, chanserv, cData->topic, 1);
+            SetChannelTopic(channel, chanserv, p10 ? user : chanserv, cData->topic, 1);
             reply("CSMSG_TOPIC_SET", cData->topic);
             return 1;
         }
@@ -4212,10 +4305,10 @@ static CHANSERV_FUNC(cmd_topic)
                 reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
                 return 0;
             }
-            SetChannelTopic(channel, chanserv, new_topic, 1);
+            SetChannelTopic(channel, chanserv, p10 ? user : chanserv, new_topic, 1);
         }
         else /* No mask set, just set the topic */
-            SetChannelTopic(channel, chanserv, topic, 1);
+            SetChannelTopic(channel, chanserv, p10 ? user : chanserv, topic, 1);
     }
 
     if(check_user_level(channel, user, lvlTopicSnarf, 1, 0))
@@ -4378,6 +4471,31 @@ show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended
     }
 }
 
+static void
+show_giveownership_info(struct svccmd *cmd, struct userNode *user, struct giveownership *giveownership)
+{
+    char buf[MAXLEN];
+    const char *fmt = "%a %b %d %H:%M %Y";
+    strftime(buf, sizeof(buf), fmt, localtime(&giveownership->issued));
+
+    if(giveownership->staff_issuer)
+    {
+        if(giveownership->reason)
+            reply("CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", giveownership->old_owner,
+                  giveownership->target, giveownership->target_access,
+                  giveownership->staff_issuer, buf, giveownership->reason);
+        else
+            reply("CSMSG_CHANNEL_OWNERSHIP_STAFF", giveownership->old_owner,
+                  giveownership->target, giveownership->target_access,
+                  giveownership->staff_issuer, buf);
+    }
+    else
+    {
+        reply("CSMSG_CHANNEL_OWNERSHIP_NORMAL", giveownership->old_owner, giveownership->target, giveownership->target_access, buf);
+    }
+}
+
+
 static CHANSERV_FUNC(cmd_info)
 {
     char modes[MAXLEN], buffer[INTERVALLEN];
@@ -4390,7 +4508,8 @@ static CHANSERV_FUNC(cmd_info)
 
     cData = channel->channel_info;
     reply("CSMSG_CHANNEL_INFO", channel->name);
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
 
     uData = GetChannelUser(cData, user->handle_info);
     if(uData && (uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/))
@@ -4419,9 +4538,10 @@ static CHANSERV_FUNC(cmd_info)
     reply("CSMSG_CHANNEL_USERS", cData->userCount);
     reply("CSMSG_CHANNEL_LAMERS", cData->banCount);
     reply("CSMSG_CHANNEL_VISITED", intervalString(buffer, now - cData->visited, user->handle_info));
-    reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
 
     privileged = IsStaff(user);
+    if(privileged)
+        reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
     if(((uData && uData->access >= UL_COOWNER) || privileged) && cData->registrar)
         reply("CSMSG_CHANNEL_REGISTRAR", cData->registrar);
 
@@ -4440,7 +4560,17 @@ static CHANSERV_FUNC(cmd_info)
         reply("CSMSG_CHANNEL_SUSPENDED", channel->name);
         show_suspension_info(cmd, user, cData->suspended);
     }
-    reply("CSMSG_CHANNEL_END");
+    if(cData->giveownership && ((uData && (uData->access >= UL_COOWNER)) || IsStaff(user)))
+    {
+        struct giveownership *giveownership;
+        reply("CSMSG_CHANNEL_OWNERSHIP_HISTORY", channel->name);
+        for(giveownership = cData->giveownership; giveownership; giveownership = giveownership->previous)
+            show_giveownership_info(cmd, user, giveownership);
+    }
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_CHANNEL_END");
+    else
+        reply("CSMSG_CHANNEL_END_CLEAN");
     return 1;
 }
 
@@ -4527,7 +4657,8 @@ static CHANSERV_FUNC(cmd_peek)
     irc_make_chanmode(channel, modes);
 
     reply("CSMSG_PEEK_INFO", channel->name);
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     reply("CSMSG_PEEK_TOPIC", channel->topic);
     reply("CSMSG_PEEK_MODES", modes);
     reply("CSMSG_PEEK_USERS", channel->members.used);
@@ -4817,16 +4948,16 @@ note_type_settable_by_user(struct chanNode *channel, struct note_type *ntype, st
 
     switch(ntype->set_access_type)
     {
-    case NOTE_SET_CHANNEL_ACCESS:
-        if(!user->handle_info)
-            return 0;
-        if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)))
-            return 0;
-        return uData->access >= ntype->set_access.min_ulevel;
-    case NOTE_SET_CHANNEL_SETTER:
-        return check_user_level(channel, user, lvlSetters, 1, 0);
-    case NOTE_SET_PRIVILEGED: default:
-        return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv);
+        case NOTE_SET_CHANNEL_ACCESS:
+            if(!user->handle_info)
+                return 0;
+            if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)))
+                return 0;
+            return uData->access >= ntype->set_access.min_ulevel;
+        case NOTE_SET_CHANNEL_SETTER:
+            return check_user_level(channel, user, lvlSetters, 1, 0);
+        case NOTE_SET_PRIVILEGED: default:
+            return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv);
     }
 }
 
@@ -4972,7 +5103,8 @@ static CHANSERV_FUNC(cmd_events)
     report.reporter = chanserv;
     report.user = user;
     reply("CSMSG_EVENT_SEARCH_RESULTS", channel->name);
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     matches = log_entry_search(&discrim, log_report_entry, &report);
     if(matches)
        reply("MSG_MATCH_COUNT", matches);
@@ -5308,8 +5440,9 @@ static CHANSERV_FUNC(cmd_search)
 
     if(action == search_print)
     {
-       reply("CSMSG_CHANNEL_SEARCH_RESULTS");
-        reply("CSMSG_BAR");
+           reply("CSMSG_CHANNEL_SEARCH_RESULTS");
+        if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+            reply("CSMSG_BAR");
     }
 
     matches = chanserv_channel_search(search, action, user);
@@ -5379,7 +5512,7 @@ static MODCMD_FUNC(chan_opt_defaulttopic)
                && !match_ircglob(channel->channel_info->topic, channel->channel_info->topic_mask))
                 reply("CSMSG_TOPIC_MISMATCH", channel->name);
        }
-        SetChannelTopic(channel, chanserv, topic ? topic : "", 1);
+        SetChannelTopic(channel, chanserv, chanserv, topic ? topic : "", 1);
     }
 
     if(channel->channel_info->topic)
@@ -5860,8 +5993,9 @@ static CHANSERV_FUNC(cmd_set)
 
     if(argc < 2)
     {
-       reply("CSMSG_CHANNEL_OPTIONS", channel->name);
-        reply("CSMSG_BAR");
+           reply("CSMSG_CHANNEL_OPTIONS", channel->name);
+        if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+            reply("CSMSG_BAR");
         for(ii = 0; ii < set_shows_list.used; ii++)
         {
             subcmd = set_shows_list.list[ii];
@@ -6049,13 +6183,22 @@ static CHANSERV_FUNC(cmd_giveownership)
     struct userData *new_owner, *curr_user;
     struct chanData *cData = channel->channel_info;
     struct do_not_register *dnr;
-    unsigned int force;
-    unsigned short co_access;
-    char reason[MAXLEN];
+    struct giveownership *giveownership;
+    unsigned int force, override;
+    unsigned short co_access, new_owner_old_access;
+    char reason[MAXLEN], transfer_reason[MAXLEN];
 
     REQUIRE_PARAMS(2);
     curr_user = GetChannelAccess(cData, user->handle_info);
     force = IsHelping(user) && (argc > 2) && !irccasecmp(argv[2], "force");
+
+    struct userData *uData = _GetChannelUser(channel->channel_info, user->handle_info, 1, 0);
+    override = ((cmd->effective_flags & MODCMD_REQUIRE_CHANUSER)
+                && (uData->access > 500)
+                && (!(uData = _GetChannelUser(channel->channel_info, user->handle_info, 0, 0))
+                    || uData->access < 500));
+
+
     if(!curr_user || (curr_user->access != UL_OWNER))
     {
         struct userData *owner = NULL;
@@ -6113,6 +6256,8 @@ static CHANSERV_FUNC(cmd_giveownership)
             chanserv_show_dnrs(user, cmd, NULL, new_owner_hi->handle);
         return 0;
     }
+
+    new_owner_old_access = new_owner->access;
     if(new_owner->access >= UL_COOWNER)
         co_access = new_owner->access;
     else
@@ -6121,18 +6266,47 @@ static CHANSERV_FUNC(cmd_giveownership)
     if(curr_user)
         curr_user->access = co_access;
     cData->ownerTransfer = now;
+
+    giveownership = calloc(1, sizeof(*giveownership));
+    giveownership->issued = now;
+    giveownership->old_owner = curr_user->handle->handle;
+    giveownership->target = new_owner_hi->handle;
+    giveownership->target_access = new_owner_old_access;
+    if(override)
+    {
+        if(argc > (2 + force))
+        {
+            unsplit_string(argv + 2 + force, argc - 2 - force, transfer_reason);
+            giveownership->reason = strdup(transfer_reason);
+        }
+        giveownership->staff_issuer = strdup(user->handle_info->handle);
+    }
+
+    giveownership->previous = channel->channel_info->giveownership;
+    channel->channel_info->giveownership = giveownership;
+
     reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
     sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
     global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
     return 1;
 }
 
+static void
+chanserv_expire_user_suspension(void *data)
+{
+    struct userData *target = data;
+
+       target->expires = 0;
+       target->flags &= ~USER_SUSPENDED;
+}
+
 static CHANSERV_FUNC(cmd_suspend)
 {
     struct handle_info *hi;
     struct userData *self, *target;
+    time_t expiry;
 
-    REQUIRE_PARAMS(2);
+    REQUIRE_PARAMS(3);
     if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
     self = GetChannelUser(channel->channel_info, user->handle_info);
     if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
@@ -6155,6 +6329,24 @@ static CHANSERV_FUNC(cmd_suspend)
         target->present = 0;
         target->seen = now;
     }
+    if(!strcmp(argv[2], "0"))
+        expiry = 0;
+    else
+    {
+        unsigned int duration;
+        if(!(duration = ParseInterval(argv[2])))
+        {
+            reply("MSG_INVALID_DURATION", argv[2]);
+            return 0;
+        }
+        expiry = now + duration;
+    }
+
+        target->expires = expiry;
+
+        if(target->expires)
+            timeq_add(target->expires, chanserv_expire_user_suspension, target);
+
     target->flags |= USER_SUSPENDED;
     reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
     return 1;
@@ -6185,6 +6377,7 @@ static CHANSERV_FUNC(cmd_unsuspend)
     }
     target->flags &= ~USER_SUSPENDED;
     scan_user_presence(target, NULL);
+    timeq_del(target->expires, chanserv_expire_user_suspension, target, 0);
     reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
     return 1;
 }
@@ -6239,7 +6432,7 @@ chanserv_refresh_topics(UNUSED_ARG(void *data))
         if((refresh_num - cData->last_refresh) < (unsigned int)(1 << (opt - '1')))
             continue;
         if(cData->topic)
-            SetChannelTopic(cData->channel, chanserv, cData->topic, 1);
+            SetChannelTopic(cData->channel, chanserv, chanserv, cData->topic, 1);
         cData->last_refresh = refresh_num;
     }
     timeq_add(now + chanserv_conf.refresh_period, chanserv_refresh_topics, NULL);
@@ -6374,13 +6567,13 @@ void eightball(char *outcome, int method, unsigned int seed)
         case 4: strcpy(tmp, "A gross kind of mucky %s.");
                 break;
         case 5: strcpy(tmp, "Brilliant whiteish %s.");
-               break;
-       case 6: case 7: case 8: case 9: strcpy(tmp, "%s.");
-               break;
-       case 10: strcpy(tmp, "Solid %s.");
-               break;
-       case 11: strcpy(tmp, "Transparent %s.");
-                break;
+                       break;
+        case 6: case 7: case 8: case 9: strcpy(tmp, "%s.");
+                break;
+        case 10: strcpy(tmp, "Solid %s.");
+                break;
+        case 11: strcpy(tmp, "Transparent %s.");
+                       break;
         default: strcpy(outcome, "An invalid random number was generated.");
                 return;
       }
@@ -6597,7 +6790,7 @@ handle_new_channel(struct chanNode *channel)
         mod_chanmode_announce(chanserv, cData->channel, &cData->modes);
 
     if(self->uplink && !self->uplink->burst && channel->channel_info->topic)
-        SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1);
+        SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1);
 }
 
 /* Welcome to my worst nightmare. Warning: Read (or modify)
@@ -6927,7 +7120,7 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
     if(bad_topic(channel, user, channel->topic))
     {   /* User doesnt have privs to set topics. Undo it */
         send_message(user, chanserv, "CSMSG_TOPIC_LOCKED", channel->name);
-        SetChannelTopic(channel, chanserv, old_topic, 1);
+        SetChannelTopic(channel, chanserv, chanserv, old_topic, 1);
         return 1;
     }
     /* If there is a topic mask set, and the new topic doesnt match,
@@ -6938,12 +7131,12 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
         conform_topic(cData->topic_mask, channel->topic, new_topic);
         if(*new_topic)
         {
-           SetChannelTopic(channel, chanserv, new_topic, 1);
+           SetChannelTopic(channel, chanserv, chanserv, new_topic, 1);
            /* and fall through to topicsnarf code below.. */
         }
         else /* Topic couldnt fit into mask, was too long */
         {
-            SetChannelTopic(channel, chanserv, old_topic, 1);
+            SetChannelTopic(channel, chanserv, chanserv, old_topic, 1);
             send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT1", channel->name, cData->topic_mask);
             send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
             return 1;
@@ -7335,7 +7528,7 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
     struct handle_info *handle;
     struct userData *uData;
-    char *seen, *inf, *flags;
+    char *seen, *inf, *flags, *expires;
     time_t last_seen;
     unsigned short access;
 
@@ -7356,6 +7549,7 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
     last_seen = seen ? (signed)strtoul(seen, NULL, 0) : now;
     flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
+    expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
     handle = get_handle_info(key);
     if(!handle)
     {
@@ -7365,6 +7559,15 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 
     uData = add_channel_user(chan, handle, access, last_seen, inf);
     uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+    uData->expires = expires ? strtoul(expires, NULL, 0) : 0;
+
+    if((uData->flags & USER_SUSPENDED) && uData->expires)
+    {
+        if(uData->expires > now)
+            timeq_add(uData->expires, chanserv_expire_user_suspension, uData);
+        else
+            uData->flags &= ~USER_SUSPENDED;
+    }
 
     /* Upgrade: set autoop to the inverse of noautoop */
     if(chanserv_read_version < 2)
@@ -7436,10 +7639,36 @@ chanserv_read_suspended(dict_t obj)
     return suspended;
 }
 
+static struct giveownership *
+chanserv_read_giveownership(dict_t obj)
+{
+    struct giveownership *giveownership = calloc(1, sizeof(*giveownership));
+    char *str;
+    dict_t previous;
+
+    str = database_get_data(obj, KEY_STAFF_ISSUER, RECDB_QSTRING);
+    giveownership->staff_issuer = str ? strdup(str) : NULL;
+
+    giveownership->old_owner = strdup(database_get_data(obj, KEY_OLD_OWNER, RECDB_QSTRING));
+
+    giveownership->target = strdup(database_get_data(obj, KEY_TARGET, RECDB_QSTRING));
+    giveownership->target_access = atoi(database_get_data(obj, KEY_TARGET_ACCESS, RECDB_QSTRING));
+
+    str = database_get_data(obj, KEY_REASON, RECDB_QSTRING);
+    giveownership->reason = str ? strdup(str) : NULL;
+    str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
+    giveownership->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
+
+    previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
+    giveownership->previous = previous ? chanserv_read_giveownership(previous) : NULL;
+    return giveownership;
+}
+
 static int
 chanserv_channel_read(const char *key, struct record_data *hir)
 {
     struct suspended *suspended;
+    struct giveownership *giveownership;
     struct mod_chanmode *modes;
     struct chanNode *cNode;
     struct chanData *cData;
@@ -7565,6 +7794,12 @@ chanserv_channel_read(const char *key, struct record_data *hir)
             cData->flags &= ~CHANNEL_SUSPENDED;
     }
 
+    if((obj = database_get_data(hir->d.object, KEY_GIVEOWNERSHIP, RECDB_OBJECT)))
+    {
+        giveownership = chanserv_read_giveownership(obj);
+        cData->giveownership = giveownership;
+    }
+
     if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) {
         struct mod_chanmode change;
         mod_chanmode_init(&change);
@@ -7726,6 +7961,8 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
         saxdb_write_int(ctx, KEY_SEEN, uData->seen);
         if(uData->flags)
             saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
+        if(uData->expires)
+            saxdb_write_int(ctx, KEY_EXPIRES, uData->expires);
        if(uData->info)
             saxdb_write_string(ctx, KEY_INFO, uData->info);
         saxdb_end_record(ctx);
@@ -7774,6 +8011,27 @@ chanserv_write_suspended(struct saxdb_context *ctx, const char *name, struct sus
     saxdb_end_record(ctx);
 }
 
+static void
+chanserv_write_giveownership(struct saxdb_context *ctx, const char *name, struct giveownership *giveownership)
+{
+    saxdb_start_record(ctx, name, 0);
+    if(giveownership->staff_issuer)
+      saxdb_write_string(ctx, KEY_STAFF_ISSUER, giveownership->staff_issuer);
+    if(giveownership->old_owner)
+      saxdb_write_string(ctx, KEY_OLD_OWNER, giveownership->old_owner);
+    if(giveownership->target)
+      saxdb_write_string(ctx, KEY_TARGET, giveownership->target);
+    if(giveownership->target_access)
+      saxdb_write_int(ctx, KEY_TARGET_ACCESS, giveownership->target_access);
+    if(giveownership->reason)
+      saxdb_write_string(ctx, KEY_REASON, giveownership->reason);
+    if(giveownership->issued)
+        saxdb_write_int(ctx, KEY_ISSUED, giveownership->issued);
+    if(giveownership->previous)
+        chanserv_write_giveownership(ctx, KEY_PREVIOUS, giveownership->previous);
+    saxdb_end_record(ctx);
+}
+
 static void
 chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
 {
@@ -7798,6 +8056,8 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
         saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask);
     if(channel->suspended)
         chanserv_write_suspended(ctx, "suspended", channel->suspended);
+    if(channel->giveownership)
+        chanserv_write_giveownership(ctx, "giveownership", channel->giveownership);
 
     saxdb_start_record(ctx, KEY_OPTIONS, 0);
     saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
@@ -7850,21 +8110,22 @@ chanserv_write_note_type(struct saxdb_context *ctx, struct note_type *ntype)
     saxdb_start_record(ctx, ntype->name, 0);
     switch(ntype->set_access_type)
     {
-    case NOTE_SET_CHANNEL_ACCESS:
-        saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel);
-        break;
-    case NOTE_SET_CHANNEL_SETTER:
-        saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1);
-        break;
-    case NOTE_SET_PRIVILEGED: default:
-        saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv);
-        break;
+        case NOTE_SET_CHANNEL_ACCESS:
+            saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel);
+            break;
+        case NOTE_SET_CHANNEL_SETTER:
+            saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1);
+            break;
+        case NOTE_SET_PRIVILEGED: default:
+            saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv);
+            break;
     }
     switch(ntype->visible_type)
     {
-    case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break;
-    case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break;
-    case NOTE_VIS_PRIVILEGED: default: str = KEY_NOTE_VIS_PRIVILEGED; break;
+        case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break;
+        case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break;
+        case NOTE_VIS_PRIVILEGED: 
+        default: str = KEY_NOTE_VIS_PRIVILEGED; break;
     }
     saxdb_write_string(ctx, KEY_NOTE_VISIBILITY, str);
     saxdb_write_int(ctx, KEY_NOTE_MAX_LENGTH, ntype->max_length);