#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
#define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
#define KEY_INVITED_INTERVAL "invite_timeout"
+#define KEY_REVOKE_MODE_A "revoke_mode_a"
#define KEY_NEW_CHANNEL_AUTHED "new_channel_authed_join"
#define KEY_NEW_CHANNEL_UNAUTHED "new_channel_unauthed_join"
#define KEY_NEW_CHANNEL_MSG "new_channel_message"
#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"
#define KEY_MAX_TIME "max_time"
#define KEY_NOTES "notes"
#define KEY_TOPIC_MASK "topic_mask"
+#define KEY_ADVTOPIC_ENTRIES "adv_topic"
#define KEY_OWNER_TRANSFER "owner_transfer"
#define KEY_EXPIRE "expire"
{ "CSMSG_TOPIC_LOCKED", "The %s topic is locked." },
{ "CSMSG_MASK_BUT_NO_TOPIC", "Warning: $b%s$b does not have a default topic, but you just set the topic mask." },
{ "CSMSG_TOPIC_MISMATCH", "Warning: The default topic for $b%s$b does not match the topic mask; changing it anyway." },
+ { "CSMSG_ADVTOPIC_INVALID_ID", "%d is an invalid advtopic id." },
{ "CSMSG_MODES_SET", "Channel modes are now $b%s$b." },
{ "CSMSG_DEFAULTED_MODES", "Channel modes for $b%s$b are set to their defaults." },
{ "CSMSG_INVALID_NUMERIC", "$b%d$b is not a valid choice. Choose one:" },
{ "CSMSG_SET_DEFAULT_TOPIC", "$bDefaultTopic$b %s" },
{ "CSMSG_SET_TOPICMASK", "$bTopicMask $b %s" },
+ { "CSMSG_SET_ADVTOPIC", "$bAdvTopic $b %s" },
{ "CSMSG_SET_GREETING", "$bGreeting $b %s" },
{ "CSMSG_SET_USERGREETING", "$bUserGreeting$b %s" },
{ "CSMSG_SET_MODES", "$bModes $b %s" },
{ "CSMSG_CHANNEL_REGISTERED", "$bRegistered: $b%s ago." },
{ "CSMSG_CHANNEL_VISITED", "$bVisited: $b%s ago." },
+ { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for %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_OWNERSHIP_NORMAL", " from %s to %s (%d access) on %s" },
+
{ "CSMSG_PEEK_INFO", "$b%s$b Status:" },
{ "CSMSG_PEEK_TOPIC", "$bTopic: $b%s" },
{ "CSMSG_PEEK_MODES", "$bModes: $b%s" },
unsigned int max_chan_users;
unsigned int max_chan_bans;
unsigned int max_userinfo_length;
+
+ unsigned int revoke_mode_a;
struct string_list *set_shows;
struct string_list *eightball;
struct chanData *channel;
enum levelOption lvlOpt;
enum charOption chOpt;
+ int i;
channel = calloc(1, sizeof(struct chanData));
channel->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
channel->chOpts[chOpt] = charOptions[chOpt].default_value;
+ for(i = 0; i < MAXADVTOPICENTRIES; i++)
+ channel->advtopic[i] = NULL;
channel->prev = NULL;
channel->next = channelList;
{
struct mod_chanmode change;
char msgbuf[MAXLEN];
+ int i;
/* After channel unregistration, the following must be cleaned
up:
timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
- if(off_channel > 0)
- {
- mod_chanmode_init(&change);
- change.modes_clear |= MODE_REGISTERED;
- mod_chanmode_announce(chanserv, channel->channel, &change);
+ if(off_channel > 0 || chanserv_conf.revoke_mode_a) {
+ mod_chanmode_init(&change);
+ if(off_channel > 0)
+ change.modes_clear |= MODE_REGISTERED;
+ if(chanserv_conf.revoke_mode_a)
+ change.modes_clear |= MODE_ACCESS;
+ mod_chanmode_announce(chanserv, channel->channel, &change);
}
while(channel->users)
free(channel->greeting);
free(channel->user_greeting);
free(channel->topic_mask);
+
+ for(i = 0; i < MAXADVTOPICENTRIES; i++) {
+ if(channel->advtopic[i])
+ free(channel->advtopic[i]);
+ }
if(channel->prev)
channel->prev->next = channel->next;
struct chanData *cData = channel->channel_info;
if(check_user_level(channel, user, lvlEnfTopic, 1, 0))
return 0;
- if(cData->topic_mask)
- return !match_ircglob(new_topic, cData->topic_mask);
+ if(cData->topic_mask)
+ {
+ if(cData->flags & CHANNEL_ADVTOPIC)
+ {
+ //this implementation is a little bit dirty but i haven't found a better, faster solution, yet...
+ char topicmask[TOPICLEN];
+ int skipnum, topicpos = 0;
+ char *ptr = cData->topic_mask;
+ for(;*ptr;ptr++) { //replace all the %[0-9]* variables with *
+ switch(*ptr) {
+ case '%':
+ for(skipnum = 0; isdigit(ptr[1]) && skipnum < 3; ptr++, skipnum++) {} //skip up to 3 numbers
+ if(skipnum)
+ topicmask[topicpos++] = '*';
+ else
+ topicmask[topicpos++] = *ptr;
+ break;
+ default:
+ topicmask[topicpos++] = *ptr;
+ break;
+ }
+ }
+ topicmask[topicpos] = 0;
+ return !match_ircglob(new_topic, topicmask);
+ }
+ else
+ return !match_ircglob(new_topic, cData->topic_mask);
+ }
else if(cData->topic)
return irccasecmp(new_topic, cData->topic);
else
char new_topic[TOPICLEN+1], tchar;
int pos=0, starpos=-1, dpos=0, len;
- while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
+ if(cData->flags & CHANNEL_ADVTOPIC)
{
- switch(tchar)
+ //first check if there is a leading 'modifier id'
+ int advtopic_index = 0;
+ char numbuf[4];
+ int numpos;
+ for(; topic[pos]; pos++)
{
- case '*':
- if(starpos != -1)
- goto bad_mask;
- len = strlen(topic);
- if((dpos + len) > TOPICLEN)
- len = TOPICLEN + 1 - dpos;
- memcpy(new_topic+dpos, topic, len);
- dpos += len;
- starpos = pos;
- break;
- case '\\': tchar = topic_mask[pos++]; /* and fall through */
- default: new_topic[dpos++] = tchar; break;
+ if(topic[pos] == ' ')
+ {
+ //leading number found, cut off and store value in advtopic_index
+ topic[pos] = 0;
+ advtopic_index = atoi(topic) - 1; //no zerobase
+ topic = &topic[pos+1];
+ /* If they say "!topic 2 *", unset advtopic id 2. */
+ if((topic[0] == '*') && (topic[1] == 0))
+ topic[0] = 0;
+ }
+ if(!isdigit(topic[pos]))
+ break;
+ }
+ if(advtopic_index < 0 || advtopic_index >= MAXADVTOPICENTRIES)
+ {
+ //invalid id!
+ reply("CSMSG_ADVTOPIC_INVALID_ID", advtopic_index+1);
+ return 0;
+ }
+ if(cData->advtopic[advtopic_index])
+ free(cData->advtopic[advtopic_index]);
+ cData->advtopic[advtopic_index] = (topic[0] ? strdup(topic) : NULL);
+ char *ptr = topic_mask;
+ while(*ptr && (dpos <= TOPICLEN))
+ {
+ switch(*ptr)
+ {
+ case '%':
+ ptr++;
+ for(numpos = 0; isdigit(*ptr) && numpos < 3; ptr++) {
+ numbuf[numpos++] = *ptr;
+ }
+ numbuf[numpos] = 0;
+ if(!numpos || (advtopic_index = atoi(numbuf)) <= 0 || advtopic_index > MAXADVTOPICENTRIES) {
+ ptr -= numpos+1;
+ new_topic[dpos++] = *ptr; //is % again
+ break;
+ }
+ advtopic_index--; //no zero base
+ if(!cData->advtopic[advtopic_index])
+ break; //just leave it empty
+ len = strlen(cData->advtopic[advtopic_index]);
+ if((dpos + len) > TOPICLEN)
+ len = TOPICLEN + 1 - dpos;
+ memcpy(new_topic+dpos, cData->advtopic[advtopic_index], len);
+ dpos += len;
+ break;
+ case '\\':
+ ptr++; /* and fall through */
+ if(!*ptr) break;
+ default:
+ new_topic[dpos++] = *ptr;
+ ptr++;
+ break;
+ }
+ }
+ } else {
+ while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
+ {
+ switch(tchar)
+ {
+ case '*':
+ if(starpos != -1)
+ goto bad_mask;
+ len = strlen(topic);
+ if((dpos + len) > TOPICLEN)
+ len = TOPICLEN + 1 - dpos;
+ memcpy(new_topic+dpos, topic, len);
+ dpos += len;
+ starpos = pos;
+ break;
+ case '\\': tchar = topic_mask[pos++]; /* and fall through */
+ default: new_topic[dpos++] = tchar; break;
+ }
+ }
+ if((dpos > TOPICLEN) || tchar)
+ {
+ bad_mask:
+ reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
+ reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
+ return 0;
}
- }
- if((dpos > TOPICLEN) || tchar)
- {
- bad_mask:
- reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
- reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
- return 0;
}
new_topic[dpos] = 0;
SetChannelTopic(channel, chanserv, new_topic, 1);
}
}
+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];
reply("CSMSG_CHANNEL_SUSPENDED", channel->name);
show_suspension_info(cmd, user, cData->suspended);
}
+
+ 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);
+ }
return 1;
}
CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
}
+static MODCMD_FUNC(chan_opt_advtopic)
+{
+ CHANNEL_BINARY_OPTION("CSMSG_SET_ADVTOPIC", CHANNEL_ADVTOPIC);
+}
+
static MODCMD_FUNC(chan_opt_offchannel)
{
struct chanData *cData = channel->channel_info;
struct chanData *cData = channel->channel_info;
struct do_not_register *dnr;
const char *confirm;
- 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;
return 0;
}
}
+ new_owner_old_access = new_owner->access;
if(new_owner->access >= UL_COOWNER)
co_access = new_owner->access;
else
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);
chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600;
str = database_get_data(conf_node, KEY_INVITED_INTERVAL, RECDB_QSTRING);
chanserv_conf.invited_timeout = str ? ParseInterval(str) : 600*2;
+ str = database_get_data(conf_node, KEY_REVOKE_MODE_A, RECDB_QSTRING);
+ chanserv_conf.revoke_mode_a = str ? atoi(str) : 1;
str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING);
chanserv_conf.nodelete_level = str ? atoi(str) : 1;
str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
str = database_get_data(conf_node, KEY_SUPPORT_HELPER_EPITHET, RECDB_QSTRING);
chanserv_conf.support_helper_epithet = str ? str : "a wannabe tyrant";
str = database_get_data(conf_node, KEY_NEW_CHANNEL_AUTHED, RECDB_QSTRING);
- chanserv_conf.new_channel_authed = str ? str : NULL;
+ chanserv_conf.new_channel_authed = (str && *str) ? str : NULL;
str = database_get_data(conf_node, KEY_NEW_CHANNEL_UNAUTHED, RECDB_QSTRING);
- chanserv_conf.new_channel_unauthed = str ? str : NULL;
+ chanserv_conf.new_channel_unauthed = (str && *str) ? str : NULL;
str = database_get_data(conf_node, KEY_NEW_CHANNEL_MSG, RECDB_QSTRING);
- chanserv_conf.new_channel_msg = str ? str : NULL;
+ chanserv_conf.new_channel_msg = (str && *str) ? str : NULL;
str = database_get_data(conf_node, "default_modes", RECDB_QSTRING);
if(!str)
str = "+nt";
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;
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);
}
}
+ obj = database_get_data(channel, KEY_ADVTOPIC_ENTRIES, RECDB_OBJECT);
+ for(it = dict_first(obj); it; it = iter_next(it))
+ {
+ struct record_data *rd = iter_data(it);
+ if(rd->type != RECDB_QSTRING) continue;
+ int advtopic_index = atoi(iter_key(it));
+ if(advtopic_index < 0 || advtopic_index >= MAXADVTOPICENTRIES) continue;
+ cData->advtopic[advtopic_index] = (rd ? strdup(rd->d.qstring) : NULL);
+ }
+
if(!IsSuspended(cData)
&& (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
&& (argc = split_line(str, 0, ArrayLength(argv), argv))
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)
{
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);
if(channel->expiry)
saxdb_write_int(ctx, KEY_EXPIRE, channel->expiry);
high_present = chanserv_write_users(ctx, channel->users);
chanserv_write_bans(ctx, channel->bans);
+ if(channel->flags & CHANNEL_ADVTOPIC) {
+ saxdb_start_record(ctx, KEY_ADVTOPIC_ENTRIES, 0);
+ int advtopic_index;
+ for(advtopic_index = 0; advtopic_index < MAXADVTOPICENTRIES; advtopic_index++) {
+ if(channel->advtopic[advtopic_index])
+ saxdb_write_string(ctx, strtab(advtopic_index), channel->advtopic[advtopic_index]);
+ }
+ saxdb_end_record(ctx);
+ }
+
if(dict_size(channel->notes))
{
dict_iterator_t it;
DEFINE_CHANNEL_OPTION(ctcpusers);
DEFINE_CHANNEL_OPTION(ctcpreaction);
DEFINE_CHANNEL_OPTION(inviteme);
+ DEFINE_CHANNEL_OPTION(advtopic);
DEFINE_CHANNEL_OPTION(unreviewed);
modcmd_register(chanserv_module, "set expire", chan_opt_expire, 1, 0, "flags", "+helping", NULL);
modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL);