supports ISO C99 varadic macros. gcc is the compiler we use for
almost all our testing, and we recommend it for use with srvx.
+PreInstall:
+----------
+$ aclocal
+$ autoconf
+$ autoreconf -f -i -Wall,no-obsolete
+$ cp /usr/share/libtool/* ./
+$ automake
+
Quick Install:
----------
$ ./configure
dnl General initialization.
AC_PREREQ(2.64)
AC_INIT([srvx],[1.4.0-rc3],[srvx-bugs@lists.sourceforge.net])
-CODENAME=surge
+CODENAME=wgn
AC_CONFIG_HEADERS(src/config.h)
AC_CONFIG_SRCDIR(src/opserv.c)
dnl AM_CANONICAL_TARGET must be before AM_INIT_AUTOMAKE() or autoconf whines
AC_PROG_LN_S
AC_PROG_MAKE_SET
AC_PROG_GCC_TRADITIONAL
+AC_PROG_RANLIB
dnl Look for a git client
AC_CHECK_PROGS(GIT, [git])
opserv.help \
saxdb.help \
mail.help \
+ spamserv.help \
mod-helpserv.help \
+ mod-watchdog.help \
mod-memoserv.help \
mod-qserver.help \
mod-snoop.help \
proto-p10.c \
mod-blacklist.c \
mod-helpserv.c \
+ mod-watchdog.c \
mod-memoserv.c \
mod-qserver.c \
mod-snoop.c \
recdb.c recdb.h \
sar.c sar.h \
saxdb.c saxdb.h \
+ spamserv.c spamserv.h \
timeq.c timeq.h \
tools.c
#include "conf.h"
#include "global.h"
#include "modcmd.h"
-#include "opserv.h" /* for opserv_bad_channel() */
+#include "opserv.h" /* for opserv_bad_channel() and devnull management */
#include "nickserv.h" /* for oper_outranks() */
#include "saxdb.h"
+#include "spamserv.h"
#include "timeq.h"
#define CHANSERV_CONF_NAME "services/chanserv"
#define KEY_NODELETE_LEVEL "nodelete_level"
#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
#define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
+#define KEY_INVITED_INTERVAL "invite_timeout"
+#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"
/* ChanServ database */
#define KEY_CHANNELS "channels"
#define KEY_USERS "users"
#define KEY_BANS "bans"
#define KEY_MAX "max"
+#define KEY_MAX_TIME "max_time"
#define KEY_NOTES "notes"
#define KEY_TOPIC_MASK "topic_mask"
#define KEY_OWNER_TRANSFER "owner_transfer"
+#define KEY_EXPIRE "expire"
/* User data */
#define KEY_LEVEL "level"
#define KEY_INFO "info"
#define KEY_SEEN "seen"
+/* Votes */
+#define KEY_VOTE "vote"
+#define KEY_VOTE_START "votestart"
+#define KEY_VOTE_OPTIONS "voptions"
+#define KEY_VOTE_OPTION_NAME "voptionname"
+#define KEY_VOTE_VOTED "vvoted"
+#define KEY_VOTE_VOTEDFOR "vvotefor"
+#define KEY_VOTE_OPTION_ID "voptionid"
+#define KEY_VOTE_OPTION_VOTED "voptionvoted"
+
/* Ban data */
#define KEY_OWNER "owner"
#define KEY_REASON "reason"
/* User management */
{ "CSMSG_ADDED_USER", "Added %s to the %s user list with access %d." },
{ "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." },
- { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." },
+ { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be lower than maximum (%d)." },
{ "CSMSG_DELETED_USERS", "Deleted accounts matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list." },
{ "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." },
{ "CSMSG_INCORRECT_ACCESS", "%s has access $b%d$b, not %s." },
{ "CSMSG_SET_USERINFO", "$bUserInfo $b %d" },
{ "CSMSG_SET_GIVE_VOICE", "$bGiveVoice $b %d" },
{ "CSMSG_SET_TOPICSNARF", "$bTopicSnarf $b %d" },
+ { "CSMSG_SET_VOTE", "$bVote $b %d" },
{ "CSMSG_SET_INVITEME", "$bInviteMe $b %d" },
{ "CSMSG_SET_ENFOPS", "$bEnfOps $b %d" },
{ "CSMSG_SET_GIVE_OPS", "$bGiveOps $b %d" },
{ "CSMSG_SET_CTCPREACTION", "$bCTCPReaction$b %d - %s" },
{ "CSMSG_SET_TOPICREFRESH", "$bTopicRefresh$b %d - %s" },
{ "CSMSG_SET_UNREVIEWED", "$bUnreviewed $b %s" },
+ { "CSMSG_SET_EXPIRE", "$bExpire $b %s" },
+ { "CSMSG_SET_EXPIRE_OFF", "$bExpire $b off" },
{ "CSMSG_USET_NOAUTOOP", "$bNoAutoOp $b %s" },
{ "CSMSG_USET_NOAUTOVOICE", "$bNoAutoVoice $b %s" },
{ "CSMSG_USET_AUTOINVITE", "$bAutoInvite $b %s" },
{ "CSMSG_ACCESS_SEARCH_HEADER", "%s users from level %d to %d matching %s:" },
{ "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
{ "CSMSG_CHANGED_ACCESS", "%s now has access $b%d$b in %s." },
+ { "CSMSG_TOTAL_USERS", "There are $b%d$b users in %s." },
/* Channel note list */
{ "CSMSG_NOTELIST_HEADER", "Notes for $b%s$b:" },
{ "CSMSG_UC_H_TITLE", "network helper" },
{ "CSMSG_LC_H_TITLE", "support helper" },
{ "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." },
+ { "CSMSG_MYACCESS_COUNT", "%s has access in $b%d$b channels and is owner of $b%d$b channel(s)." },
+ { "CSMSG_MYACCESS_COUNT_1", "%s has access in $b%d$b channel and is owner of $b%d$b channel(s)." },
+
/* Seen information */
{ "CSMSG_NEVER_SEEN", "%s has never been seen in $b%s$b." },
{ "CSMSG_CHANNEL_MODES", "$bMode Lock: $b%s" },
{ "CSMSG_CHANNEL_NOTE", "$b%s:%*s$b%s" },
{ "CSMSG_CHANNEL_MAX", "$bRecord Visitors: $b%i" },
+ { "CSMSG_CHANNEL_MAX_TIME", "$bRecord Visitors: $b%i (%s ago.)" },
{ "CSMSG_CHANNEL_OWNER", "$bOwner: $b%s" },
{ "CSMSG_CHANNEL_BANS", "$bBan Count: $b%i" },
{ "CSMSG_CHANNEL_USERS", "$bTotal User Count: $b%i" },
{ "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
{ "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
+/* Vote */
+ { "CSMSG_ADDVOTE_DONE", "Vote added. Use $baddoption$b to add at least 2 vote options and then $bstartvote$b to start the voting." },
+ { "CSMSG_ADDVOTE_FULL", "There is already a vote in this channel. Use $bdelvote$b to delete it." },
+ { "CSMSG_DELVOTE_DONE", "Vote deleted." },
+ { "CSMSG_NO_VOTE", "There is no vote in this channel." },
+ { "CSMSG_ADDOPTION_DONE", "Vote option added with id %i (%i - %i)." },
+ { "CSMSG_DELOPTION_DONE", "Vote option deleted." },
+ { "CSMSG_DELOPTION_NONE", "Vote option does not exist." },
+ { "CSMSG_VOTE_NEED_OPTIONS", "There must be at least 2 options in a vote." },
+ { "CSMSG_VOTE_NOT_STARTED", "The vote is not started. Use $bstartvote$b to allow voting." },
+ { "CSMSG_VOTE_QUESTION", "Question: %s" },
+ { "CSMSG_VOTE_OPTION", "$b%i$b: %s ($b%i$b votes)" },
+ { "CSMSG_VOTERES_QUESTION", "Question: %s" },
+ { "CSMSG_VOTERES_OPTION", "\ 2%i\ 2: %s (\ 2%i\ 2 votes)" },
+ { "CSMSG_STARTVOTE_TOP", "\ 2%s\ 2 has started a voting:" },
+ { "CSMSG_STARTVOTE_QUESTION", "\ 2Question:\ 2 %s" },
+ { "CSMSG_STARTVOTE_OPTION", "\ 2%i:\ 2 %s" },
+ { "CSMSG_STARTVOTE_ACCESS", "All channel users with at least \ 2%i\ 2 access can vote." },
+ { "CSMSG_STARTVOTE_HOWTO", "To vote for an option, use \ 2vote ID\ 2. To see the available options and the current votes, use \ 2vote\ 2 without parameters." },
+ { "CSMSG_STARTVOTE_RUNNING", "The vote is already running." },
+ { "CSMSG_VOTE_VOTED", "You have already voted." },
+ { "CSMSG_VOTE_INVALID", "Vote option does not exist." },
+ { "CSMSG_VOTE_DONE", "You voted for $b%s$b." },
+ { "CSMSG_ENDVOTE_DONE", "The vote has been finished." },
+ { "CSMSG_ENDVOTE_STOPPED", "The vote is not running." },
+
/* Other things */
{ "CSMSG_EVENT_SEARCH_RESULTS", "The following channel events were found:" },
{ NULL, NULL }
unsigned long db_backup_frequency;
unsigned long channel_expire_frequency;
unsigned long dnr_expire_frequency;
+
+ unsigned long invited_timeout;
unsigned long info_delay;
unsigned long adjust_delay;
const char *irc_operator_epithet;
const char *network_helper_epithet;
const char *support_helper_epithet;
+
+ const char *new_channel_authed;
+ const char *new_channel_unauthed;
+ const char *new_channel_msg;
} chanserv_conf;
struct listData
struct helpfile_table table;
};
+struct ChanUser
+{
+ struct userNode *user;
+ struct chanNode *chan;
+};
+
enum note_access_type
{
NOTE_SET_CHANNEL_ACCESS,
{ "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9, 0, 0 },
{ "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 },
{ "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 },
- { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }
+ { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 },
+ { "CSMSG_SET_VOTE", "vote", 100, ~0, 0, 0 }
};
struct charOptionValues {
static struct module *chanserv_module;
static unsigned int userCount;
-#define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0)
#define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0)
#define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1)
+static void unregister_channel(struct chanData *channel, const char *reason);
unsigned short
user_level_from_name(const char *name, unsigned short clamp_level)
return ntype;
}
+static void
+free_vote_options(void *data)
+{
+ struct vote_option *vOpt = data;
+ free(vOpt->name);
+ free(vOpt->option_str);
+ free(vOpt);
+}
+
static void
chanserv_deref_note_type(void *data)
{
return 1;
}
+static void
+chanserv_expire_channel(void *data)
+{
+ struct chanData *channel = data;
+ char reason[MAXLEN];
+ sprintf(reason, "channel expired.");
+ channel->expiry = 0;
+ spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
+ unregister_channel(channel, reason);
+}
+
+static MODCMD_FUNC(chan_opt_expire)
+{
+ struct chanData *cData = channel->channel_info;
+ unsigned long value = cData->expiry;
+
+ if(argc > 1)
+ {
+ if((!IsOper(user) || !user->handle_info || (user->handle_info->opserv_level < chanserv_conf.nodelete_level)))
+ {
+ reply("MSG_SETTING_PRIVILEGED", argv[0]);
+ return 0;
+ }
+ unsigned long expiry,duration;
+
+ /* The two directions can have different ACLs. */
+ if(!strcmp(argv[1], "0"))
+ expiry = 0;
+ else if((duration = ParseInterval(argv[1])))
+ expiry = now + duration;
+ else
+ {
+ reply("MSG_INVALID_DURATION", argv[1]);
+ return 0;
+ }
+
+ if (expiry != value)
+ {
+ if(value) {
+ //unset old timer
+ timeq_del(value, chanserv_expire_channel, cData, 0);
+ }
+ value = expiry;
+ cData->expiry = value;
+ if(value > 0) {
+ //New timer!
+ timeq_add(expiry, chanserv_expire_channel, cData);
+ }
+ }
+ }
+
+ if(cData->expiry > now) {
+ char expirestr[INTERVALLEN];
+ reply("CSMSG_SET_EXPIRE", intervalString(expirestr, cData->expiry - now, user->handle_info));
+ } else
+ reply("CSMSG_SET_EXPIRE_OFF");
+ return 1;
+}
+
static int
mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *change)
{
channel->channel = cNode;
LockChannel(cNode);
cNode->channel_info = channel;
+
+ channel->vote = NULL;
return channel;
}
return ud;
}
-static void unregister_channel(struct chanData *channel, const char *reason);
-
void
del_channel_user(struct userData *user, int do_gc)
{
free(user->info);
free(user);
- if(do_gc && !channel->users && !IsProtected(channel))
+ if(do_gc && !channel->users && !IsProtected(channel)) {
+ spamserv_cs_unregister(NULL, channel->channel, lost_all_users, NULL);
unregister_channel(channel, "lost all users.");
+ }
}
static void expire_ban(void *data);
-static struct banData*
+struct banData*
add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason)
{
struct banData *bd;
if(cNode)
cNode->channel_info = NULL;
}
+ if(channel->expiry)
+ timeq_del(channel->expiry, chanserv_expire_channel, channel, 0);
channel->channel->channel_info = NULL;
dict_delete(channel->notes);
/* Unregister the channel */
log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name);
+ spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
unregister_channel(channel, "registration expired.");
}
/* Initialize the channel's max user record. */
cData->max = channel->members.used;
+ cData->max_time = 0;
if(handle != user->handle_info)
reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name);
return 0;
}
- if(IsProtected(cData))
+ if(IsProtected(cData) && !IsOper(user))
{
reply("CSMSG_UNREG_NODELETE", channel->name);
return 0;
sprintf(reason, "unregistered by %s.", user->handle_info->handle);
name = strdup(channel->name);
unregister_channel(cData, reason);
+ spamserv_cs_unregister(user, channel, manually, "unregistered");
reply("CSMSG_UNREG_SUCCESS", name);
free(name);
return 1;
}
+static void
+ss_cs_join_channel(struct chanNode *channel, int spamserv_join)
+{
+ extern struct userNode *spamserv;
+ struct mod_chanmode *change;
+
+ if(spamserv && spamserv_join && get_chanInfo(channel->name))
+ {
+ change = mod_chanmode_alloc(2);
+ change->argc = 2;
+ change->args[0].mode = MODE_CHANOP;
+ change->args[0].u.member = AddChannelUser(chanserv, channel);
+ change->args[1].mode = MODE_CHANOP;
+ change->args[1].u.member = AddChannelUser(spamserv, channel);
+ }
+ else
+ {
+ change = mod_chanmode_alloc(1);
+ change->argc = 1;
+ change->args[0].mode = MODE_CHANOP;
+ change->args[0].u.member = AddChannelUser(chanserv, channel);
+ }
+
+ mod_chanmode_announce(chanserv, channel, change);
+ mod_chanmode_free(change);
+}
+
static CHANSERV_FUNC(cmd_move)
{
struct mod_chanmode change;
struct userData *uData;
char reason[MAXLEN];
struct do_not_register *dnr;
+ int chanserv_join = 0, spamserv_join;
REQUIRE_PARAMS(2);
{
target = AddChannel(argv[1], now, NULL, NULL);
if(!IsSuspended(channel->channel_info))
- AddChannelUser(chanserv, target);
+ chanserv_join = 1;
}
else if(target->channel_info)
{
return 0;
}
else if(!IsSuspended(channel->channel_info))
- {
- change.argc = 1;
- change.args[0].mode = MODE_CHANOP;
- change.args[0].u.member = AddChannelUser(chanserv, target);
- mod_chanmode_announce(chanserv, target, &change);
- }
+ chanserv_join = 1;
if(off_channel > 0)
{
for(uData = target->channel_info->users; uData; uData = uData->next)
scan_user_presence(uData, NULL);
- reply("CSMSG_MOVE_SUCCESS", target->name);
+ spamserv_join = spamserv_cs_move_merge(user, channel, target, 1);
+
+ if(chanserv_join)
+ ss_cs_join_channel(target, spamserv_join);
sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
if(!IsSuspended(target->channel_info))
UnlockChannel(channel);
LockChannel(target);
global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+ reply("CSMSG_MOVE_SUCCESS", target->name);
return 1;
}
target->ownerTransfer = source->ownerTransfer;
if(source->may_opchan)
target->may_opchan = 1;
- if(source->max > target->max)
+ if(source->max > target->max) {
target->max = source->max;
+ target->max_time = source->max_time;
+ }
}
static void
/* Merge the channel structures and associated data. */
merge_channel(channel->channel_info, target->channel_info);
+ spamserv_cs_move_merge(user, channel, target, 0);
sprintf(reason, "merged into %s by %s.", target->name, user->handle_info->handle);
unregister_channel(channel->channel_info, reason);
reply("CSMSG_MERGE_SUCCESS", target->name);
return modify_users(CSFUNC_ARGS, NULL, MODE_REMOVE|MODE_VOICE, "CSMSG_DEVOICED_USERS");
}
+static CHANSERV_FUNC(cmd_opme)
+{
+ struct mod_chanmode change;
+ const char *errmsg;
+
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].u.member = GetUserMode(channel, user);
+ if(!change.args[0].u.member)
+ {
+ if(argc)
+ reply("MSG_CHANNEL_ABSENT", channel->name);
+ return 0;
+ }
+
+ struct devnull_class *devnull;
+ if(user->handle_info->devnull && (devnull = devnull_get(user->handle_info->devnull)) && (devnull->modes & DEVNULL_MODE_OPME))
+ {
+ change.args[0].mode = MODE_CHANOP;
+ errmsg = "CSMSG_ALREADY_OPPED";
+ }
+ else
+ {
+ if(argc)
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ change.args[0].mode &= ~change.args[0].u.member->modes;
+ if(!change.args[0].mode)
+ {
+ if(argc)
+ reply(errmsg, channel->name);
+ return 0;
+ }
+ modcmd_chanmode_announce(&change);
+ return 1;
+}
+
static int
bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
{
offset = (action & ACTION_ADD_TIMED_BAN) ? 3 : 2;
REQUIRE_PARAMS(offset);
+ if(argc > offset && IsNetServ(user))
+ {
+ if(*argv[offset] == '$') {
+ struct userNode *hib;
+ const char *accountnameb = argv[offset] + 1;
+ if(!(hib = GetUserH(accountnameb)))
+ {
+ reply("MSG_HANDLE_UNKNOWN", accountnameb);
+ return 0;
+ }
+ user=hib;
+ offset++;
+ }
+ }
if(argc > offset)
{
reason = unsplit_string(argv + offset, argc - offset, NULL);
return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_BAN | ACTION_ADD_TIMED_BAN);
}
-static struct mod_chanmode *
+struct mod_chanmode *
find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask)
{
struct mod_chanmode *change;
static struct string_buffer sbuf;
struct handle_info *target_handle;
struct userData *uData;
+ int ccount = 0;
+ int ocount = 0;
if(argc < 2)
target_handle = user->handle_info;
for(uData = target_handle->channels; uData; uData = uData->u_next)
{
struct chanData *cData = uData->channel;
+ ccount++;
unsigned int base_len;
if(uData->access > UL_OWNER)
continue;
+ if(uData->access == UL_OWNER)
+ ocount++;
+
if(IsProtected(cData)
&& (target_handle != user->handle_info)
- && !GetTrueChannelAccess(cData, user->handle_info))
+ && !GetTrueChannelAccess(cData, user->handle_info)
+ && !IsNetworkHelper(user))
continue;
sbuf.used = 0;
string_buffer_append_printf(&sbuf, "[%s (%d,", cData->channel->name, uData->access);
send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list);
}
+ if(ccount == 1) {
+ reply("CSMSG_MYACCESS_COUNT_1", target_handle->handle, ccount, ocount);
+ } else {
+ reply("CSMSG_MYACCESS_COUNT", target_handle->handle, ccount, ocount);
+ }
+
return 1;
}
}
free(lData.table.contents[0]);
free(lData.table.contents);
+ reply("CSMSG_TOTAL_USERS",(lData.table.length - 1),channel->name);
return 1;
}
base_oplevel = 1;
else
base_oplevel = 1 + UL_OWNER - uData->access;
- change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, base_oplevel);
+ change = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS, base_oplevel);
if(!change)
{
reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
return 1;
}
+static void
+chanserv_del_invite_mark(void *data)
+{
+ struct ChanUser *chanuser = data;
+ struct chanNode *channel = chanuser->chan;
+ unsigned int i;
+ if(!channel) return;
+ for(i = 0; i < channel->invited.used; i++)
+ {
+ if(channel->invited.list[i] == chanuser->user) {
+ userList_remove(&channel->invited, chanuser->user);
+ }
+ }
+ free(chanuser);
+}
+
static CHANSERV_FUNC(cmd_invite)
{
struct userNode *invite;
+ struct ChanUser *chanuser;
+ unsigned int i;
if(argc > 1)
{
reply("CSMSG_ALREADY_PRESENT", invite->nick, channel->name);
return 0;
}
+
+ for(i = 0; i < channel->invited.used; i++)
+ {
+ if(channel->invited.list[i] == invite) {
+ reply("CSMSG_ALREADY_INVITED", invite->nick, channel->name);
+ return 0;
+ }
+ }
if(user != invite)
{
if(argc > 1)
reply("CSMSG_INVITED_USER", argv[1], channel->name);
+ userList_append(&channel->invited, invite);
+ chanuser = calloc(1, sizeof(*chanuser));
+ chanuser->user=invite;
+ chanuser->chan=channel;
+ timeq_add(now + chanserv_conf.invited_timeout, chanserv_del_invite_mark, chanuser);
+
return 1;
}
return 1;
}
+static CHANSERV_FUNC(cmd_invitemeall)
+{
+ struct handle_info *target = user->handle_info;
+ struct userData *uData;
+
+ if(!target->channels)
+ {
+ reply("CSMSG_SQUAT_ACCESS", target->handle);
+ return 1;
+ }
+
+ for(uData = target->channels; uData; uData = uData->u_next)
+ {
+ struct chanData *cData = uData->channel;
+ if(uData->access >= cData->lvlOpts[lvlInviteMe])
+ {
+ irc_invite(cmd->parent->bot, user, cData->channel);
+ }
+ }
+ return 1;
+}
+
static void
show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended *suspended)
{
reply("CSMSG_CHANNEL_NOTE", iter_key(it), padding > 0 ? padding : 1, "", note->note);
}
- reply("CSMSG_CHANNEL_MAX", cData->max);
+ if(cData->max_time) {
+ reply("CSMSG_CHANNEL_MAX_TIME", cData->max, intervalString(buffer, now - cData->max_time, user->handle_info));
+ } else {
+ reply("CSMSG_CHANNEL_MAX", cData->max);
+ }
for(owner = cData->users; owner; owner = owner->next)
if(owner->access == UL_OWNER)
reply("CSMSG_CHANNEL_OWNER", owner->handle->handle);
continue;
if(IsBot(user))
continue;
+ if(IsInvi(user))
+ continue;
table.contents[table.length] = alloca(table.width*sizeof(**table.contents));
if(IsAway(user))
{
{
if(!(mn->modes & MODE_CHANOP))
{
- changes->args[used].mode = MODE_CHANOP;
- changes->args[used++].u.member = mn;
+ if(!uData || IsUserAutoOp(uData))
+ {
+ changes->args[used].mode = MODE_CHANOP;
+ changes->args[used++].u.member = mn;
+ if(!(mn->modes & MODE_VOICE))
+ {
+ changes->args[used].mode = MODE_VOICE;
+ changes->args[used++].u.member = mn;
+ }
+ }
}
}
else if(!cData->lvlOpts[lvlGiveVoice]
changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
changes->args[used++].u.member = mn;
}
- if(!(mn->modes & MODE_VOICE))
+ if(!(mn->modes & MODE_VOICE) && (!uData || IsUserAutoOp(uData)))
{
changes->args[used].mode = MODE_VOICE;
changes->args[used++].u.member = mn;
/* If appropriate, re-join ChanServ to the channel. */
if(!IsOffChannel(suspended->cData))
{
- struct mod_chanmode change;
- mod_chanmode_init(&change);
- change.argc = 1;
- change.args[0].mode = MODE_CHANOP;
- change.args[0].u.member = AddChannelUser(chanserv, channel);
- mod_chanmode_announce(chanserv, channel, &change);
+ spamserv_cs_suspend(channel, 0, 0, NULL);
+ ss_cs_join_channel(channel, 1);
}
/* Mark everyone currently in the channel as present. */
/* Mark the channel as suspended, then part. */
channel->channel_info->flags |= CHANNEL_SUSPENDED;
+ spamserv_cs_suspend(channel, expiry, 1, suspended->reason);
DelChannelUser(chanserv, channel, suspended->reason, 0);
reply("CSMSG_SUSPENDED", channel->name);
sprintf(reason, "%s suspended by %s.", channel->name, suspended->suspender);
{
memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
}
- else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, 0)))
+ else if(!(new_modes = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS|(IsOper(user) && IsHelping(user) ? MCP_OPERMODE : 0), 0)))
{
reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
return 0;
return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS);
}
+static MODCMD_FUNC(chan_opt_vote)
+{
+ return channel_level_option(lvlVote, CSFUNC_ARGS);
+}
+
static MODCMD_FUNC(chan_opt_inviteme)
{
return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
return 1;
}
+static CHANSERV_FUNC(cmd_addvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *uData, *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ REQUIRE_PARAMS(2);
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (cData->vote) {
+ reply("CSMSG_ADDVOTE_FULL");
+ return 0;
+ }
+ char *msg;
+ msg = unsplit_string(argv + 1, argc - 1, NULL);
+ cData->vote = strdup(msg);
+ cData->vote_start=0;
+ dict_delete(cData->vote_options);
+ cData->vote_options = dict_new();
+ dict_set_free_data(cData->vote_options, free_vote_options);
+ for(uData = channel->channel_info->users; uData; uData = uData->next)
+ {
+ uData->voted = 0;
+ uData->votefor = 0;
+ }
+ reply("CSMSG_ADDVOTE_DONE");
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_delvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ free(cData->vote);
+ cData->vote = NULL;
+ reply("CSMSG_DELVOTE_DONE");
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_addoption)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ REQUIRE_PARAMS(2);
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+
+ char *msg;
+
+ msg = unsplit_string(argv + 1, argc - 1, NULL);
+
+ dict_iterator_t it;
+ unsigned int lastid = 1;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *cvOpt = iter_data(it);
+ if(cvOpt->option_id > lastid)
+ lastid = cvOpt->option_id;
+ }
+ struct vote_option *vOpt;
+ vOpt = calloc(1, sizeof(*vOpt));
+ vOpt->name = strdup(msg);
+ vOpt->option_id = (lastid + 1);
+ char str[50];
+ sprintf(str,"%i",(lastid + 1));
+ vOpt->option_str = strdup(str);
+ vOpt->voted = 0;
+ dict_insert(cData->vote_options,vOpt->option_str,vOpt);
+
+ reply("CSMSG_ADDOPTION_DONE",dict_size(cData->vote_options),lastid,(lastid + 1));
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_deloption)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *uData, *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ REQUIRE_PARAMS(2);
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(cData->vote_start) {
+ if(dict_size(cData->vote_options) < 3) {
+ reply("CSMSG_VOTE_NEED_OPTIONS");
+ return 0;
+ }
+ }
+
+ int find_id = atoi(argv[1]);
+ int ii = 0;
+ unsigned int found = 0;
+ dict_iterator_t it;
+
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ ii++;
+ if (find_id == ii) {
+ struct vote_option *vOpt = iter_data(it);
+ found = vOpt->option_id;
+ char str[50];
+ sprintf(str,"%i",vOpt->option_id);
+ dict_remove(cData->vote_options, str);
+ }
+ }
+
+ if(found > 0) {
+ for(uData = channel->channel_info->users; uData; uData = uData->next) {
+ if(uData->votefor == found) {
+ uData->voted = 0;
+ uData->votefor = 0;
+ }
+ }
+ reply("CSMSG_DELOPTION_DONE");
+ return 1;
+ } else {
+ reply("CSMSG_DELOPTION_NONE");
+ return 0;
+ }
+}
+
+static CHANSERV_FUNC(cmd_vote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ unsigned int votedfor = 0;
+ char *votedfor_str = NULL;
+
+ if (!cData || !cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(argc > 1 && cData->vote_start) {
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(!check_user_level(channel, user, lvlVote, 1, 0)) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if(target->voted) {
+ reply("CSMSG_VOTE_VOTED");
+ return 0;
+ }
+ int find_id = atoi(argv[1]);
+ int ii = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ ii++;
+ if (find_id == ii) {
+ struct vote_option *vOpt = iter_data(it);
+ vOpt->voted++;
+ target->voted = 1;
+ target->votefor = vOpt->option_id;
+ votedfor = vOpt->option_id;
+ votedfor_str = vOpt->name;
+ }
+ }
+ if(votedfor == 0) {
+ reply("CSMSG_VOTE_INVALID");
+ return 0;
+ }
+ }
+ if (!cData->vote_start) {
+ reply("CSMSG_VOTE_NOT_STARTED");
+ }
+ reply("CSMSG_VOTE_QUESTION",cData->vote);
+
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
+ }
+ if(argc > 1 && cData->vote_start && votedfor_str) {
+ reply("CSMSG_VOTE_DONE",votedfor_str);
+ }
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_startvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(cData->vote_start) {
+ reply("CSMSG_STARTVOTE_RUNNING");
+ return 0;
+ }
+ if(dict_size(cData->vote_options) < 2) {
+ reply("CSMSG_VOTE_NEED_OPTIONS");
+ return 0;
+ }
+ cData->vote_start = 1;
+ char response[MAXLEN];
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_TOP"), user->nick);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_QUESTION"), cData->vote);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_OPTION"), voteid, vOpt->name);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ }
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_ACCESS"), cData->lvlOpts[lvlVote]); //Todo
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_HOWTO")); //Todo
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_endvote)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if(!cData->vote_start) {
+ reply("CSMSG_ENDVOTE_STOPPED");
+ return 0;
+ }
+ cData->vote_start = 0;
+ reply("CSMSG_ENDVOTE_DONE");
+ return 1;
+}
+
+static CHANSERV_FUNC(cmd_voteresults)
+{
+ struct chanData *cData = channel->channel_info;
+ struct userData *target;
+ struct handle_info *hi;
+ if (!cData) return 0;
+ if (!cData->vote) {
+ reply("CSMSG_NO_VOTE");
+ return 0;
+ }
+ if (argc > 1 && !irccasecmp(argv[1], "*")) {
+ hi = user->handle_info;
+ if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+ {
+ reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+ return 0;
+ }
+ if(target->access < 300) {
+ reply("CSMSG_NO_ACCESS");
+ return 0;
+ }
+ char response[MAXLEN];
+ sprintf(response, user_find_message(user, "CSMSG_VOTERES_QUESTION"), cData->vote);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ sprintf(response, user_find_message(user, "CSMSG_VOTERES_OPTION"), voteid, vOpt->name, vOpt->voted);
+ irc_privmsg(cmd->parent->bot, channel->name, response);
+ }
+ } else {
+ reply("CSMSG_VOTE_QUESTION",cData->vote);
+ unsigned int voteid = 0;
+ dict_iterator_t it;
+ for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ voteid++;
+ reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
+ }
+ }
+ return 1;
+}
+
static void
chanserv_refresh_topics(UNUSED_ARG(void *data))
{
SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1);
}
+void handle_new_channel_created(char *chan, struct userNode *user) {
+ if(user->handle_info && chanserv_conf.new_channel_authed) {
+ send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_authed);
+ } else if(!user->handle_info && chanserv_conf.new_channel_unauthed) {
+ send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_unauthed);
+ }
+ if(chanserv_conf.new_channel_msg)
+ send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_msg);
+}
+
/* Welcome to my worst nightmare. Warning: Read (or modify)
the code below at your own risk. */
static int
struct handle_info *handle;
unsigned int modes = 0, info = 0;
char *greeting;
+ unsigned int i = 0;
if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info))
return 0;
cData = channel->channel_info;
- if(channel->members.used > cData->max)
+ if(channel->members.used > cData->max) {
cData->max = channel->members.used;
+ cData->max_time = now;
+ }
+
+ for(i = 0; i < channel->invited.used; i++)
+ {
+ if(channel->invited.list[i] == user) {
+ userList_remove(&channel->invited, user);
+ }
+ }
/* Check for bans. If they're joining through a ban, one of two
* cases applies:
chanserv_conf.channel_expire_delay = str ? ParseInterval(str) : 86400*30;
str = database_get_data(conf_node, KEY_DNR_EXPIRE_FREQ, RECDB_QSTRING);
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_NODELETE_LEVEL, RECDB_QSTRING);
chanserv_conf.nodelete_level = str ? atoi(str) : 1;
str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
chanserv_conf.network_helper_epithet = str ? str : "a wannabe tyrant";
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) ? str : NULL;
+ str = database_get_data(conf_node, KEY_NEW_CHANNEL_UNAUTHED, RECDB_QSTRING);
+ 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) ? str : NULL;
str = database_get_data(conf_node, "default_modes", RECDB_QSTRING);
if(!str)
str = "+nt";
safestrncpy(mode_line, str, sizeof(mode_line));
ii = split_line(mode_line, 0, ArrayLength(modes), modes);
- if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
+ if((change = mod_chanmode_parse(NULL, NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
&& (change->argc < 2))
{
chanserv_conf.default_modes = *change;
/* multiple choice options */
"CtcpReaction", "Protect", "Toys", "TopicRefresh",
/* binary options */
- "DynLimit", "NoDelete",
+ "DynLimit", "NoDelete", "expire", "Vote",
/* delimiter */
NULL
};
ntype->max_length = str ? strtoul(str, NULL, 0) : 400;
}
+static void
+vote_option_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
+{
+ struct vote_option *vOpt;
+ char *str;
+
+ if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
+ {
+ log_module(CS_LOG, LOG_ERROR, "Invalid vote option in %s.", chan->channel->name);
+ return;
+ }
+
+ vOpt = calloc(1, sizeof(*vOpt));
+ vOpt->name = strdup(database_get_data(rd->d.object, KEY_VOTE_OPTION_NAME, RECDB_QSTRING));
+ str = database_get_data(rd->d.object, KEY_VOTE_OPTION_VOTED, RECDB_QSTRING);
+ vOpt->voted = str ? atoi(str) : 0;
+ vOpt->option_id = str ? atoi(key) : 0;
+ vOpt->option_str = strdup(key);
+ dict_insert(chan->vote_options,vOpt->option_str,vOpt);
+}
+
static void
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, *voted, *votefor;
unsigned long last_seen;
unsigned short access_level;
seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
last_seen = seen ? strtoul(seen, NULL, 0) : now;
flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
+ voted = database_get_data(rd->d.object, KEY_VOTE_VOTED, RECDB_QSTRING);
+ votefor = database_get_data(rd->d.object, KEY_VOTE_VOTEDFOR, RECDB_QSTRING);
handle = get_handle_info(key);
if(!handle)
{
uData = add_channel_user(chan, handle, access_level, last_seen, inf);
uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+ if(chan->vote) {
+ uData->voted = voted ? strtoul(voted, NULL, 0) : 0;
+ uData->votefor = votefor ? strtoul(votefor, NULL, 0) : 0;
+ } else {
+ uData->voted = 0;
+ uData->votefor = 0;
+ }
}
static void
cData->chOpts[chOpt] = ((count <= charOptions[chOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[charOptions[chOpt].old_idx];
}
+ if((str = database_get_data(hir->d.object, KEY_EXPIRE, RECDB_QSTRING)))
+ {
+ cData->expiry = atoi(str);
+ if(cData->expiry > 0) {
+ if(cData->expiry > now) {
+ timeq_add(cData->expiry, chanserv_expire_channel, cData);
+ } else {
+ timeq_add(1, chanserv_expire_channel, cData);
+ }
+ }
+ } else {
+ cData->expiry = 0;
+ }
+
if((obj = database_get_data(hir->d.object, KEY_SUSPENDED, RECDB_OBJECT)))
{
suspended = chanserv_read_suspended(obj);
cData->ownerTransfer = str ? strtoul(str, NULL, 0) : 0;
str = database_get_data(channel, KEY_MAX, RECDB_QSTRING);
cData->max = str ? atoi(str) : 0;
+ str = database_get_data(channel, KEY_MAX_TIME, RECDB_QSTRING);
+ cData->max_time = str ? atoi(str) : 0;
str = database_get_data(channel, KEY_GREETING, RECDB_QSTRING);
cData->greeting = str ? strdup(str) : NULL;
str = database_get_data(channel, KEY_USER_GREETING, RECDB_QSTRING);
str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING);
cData->topic = str ? strdup(str) : NULL;
+ str = database_get_data(channel, KEY_VOTE, RECDB_QSTRING);
+ if(str) {
+ cData->vote = str ? strdup(str) : NULL;
+ dict_delete(cData->vote_options);
+ cData->vote_options = dict_new();
+ dict_set_free_data(cData->vote_options, free_vote_options);
+ str = database_get_data(channel, KEY_VOTE_START, RECDB_QSTRING);
+ cData->vote_start = str ? atoi(str) : 0;
+ obj = database_get_data(channel, KEY_VOTE_OPTIONS, RECDB_OBJECT);
+ for(it = dict_first(obj); it; it = iter_next(it)) {
+ vote_option_read_helper(iter_key(it), iter_data(it), cData);
+ }
+ }
+
if(!IsSuspended(cData)
&& (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
&& (argc = split_line(str, 0, ArrayLength(argv), argv))
- && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
+ && (modes = mod_chanmode_parse(cNode, NULL, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
cData->modes = *modes;
if(off_channel > 0)
cData->modes.modes_set |= MODE_REGISTERED;
saxdb_write_int(ctx, KEY_SEEN, uData->seen);
if(uData->flags)
saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
+ if(uData->channel->vote && uData->voted)
+ saxdb_write_int(ctx, KEY_VOTE_VOTED, uData->voted);
+ if(uData->channel->vote && uData->votefor)
+ saxdb_write_int(ctx, KEY_VOTE_VOTEDFOR, uData->votefor);
if(uData->info)
saxdb_write_string(ctx, KEY_INFO, uData->info);
saxdb_end_record(ctx);
int high_present;
enum levelOption lvlOpt;
enum charOption chOpt;
+ dict_iterator_t it;
saxdb_start_record(ctx, channel->channel->name, 1);
saxdb_write_int(ctx, KEY_REGISTERED, channel->registered);
saxdb_write_int(ctx, KEY_MAX, channel->max);
+ saxdb_write_int(ctx, KEY_MAX_TIME, channel->max_time);
if(channel->topic)
saxdb_write_string(ctx, KEY_TOPIC, channel->topic);
if(channel->registrar)
saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask);
if(channel->suspended)
chanserv_write_suspended(ctx, "suspended", channel->suspended);
+ if(channel->expiry)
+ saxdb_write_int(ctx, KEY_EXPIRE, channel->expiry);
+
+ if(channel->vote) {
+ saxdb_write_string(ctx, KEY_VOTE, channel->vote);
+ if(channel->vote_start)
+ saxdb_write_int(ctx, KEY_VOTE_START, channel->vote_start);
+ if (dict_size(channel->vote_options)) {
+ saxdb_start_record(ctx, KEY_VOTE_OPTIONS, 1);
+ for (it = dict_first(channel->vote_options); it; it = iter_next(it)) {
+ struct vote_option *vOpt = iter_data(it);
+ char str[50];
+ sprintf(str,"%i",vOpt->option_id);
+ saxdb_start_record(ctx, str, 0);
+ if(vOpt->voted)
+ saxdb_write_int(ctx, KEY_VOTE_OPTION_VOTED, vOpt->voted);
+ if(vOpt->name)
+ saxdb_write_string(ctx, KEY_VOTE_OPTION_NAME, vOpt->name);
+ saxdb_end_record(ctx);
+ }
+ saxdb_end_record(ctx);
+ }
+ }
saxdb_start_record(ctx, KEY_OPTIONS, 0);
saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", "flags", "+never_csuspend", NULL);
DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL);
DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL);
+ DEFINE_COMMAND(invitemeall, 1, MODCMD_REQUIRE_AUTHED, NULL);
DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "master", NULL);
DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL);
DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
DEFINE_COMMAND(8ball, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
DEFINE_COMMAND(d, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-
+
+ DEFINE_COMMAND(addvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(delvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(addoption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(deloption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(vote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(startvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(endvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+ DEFINE_COMMAND(voteresults, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+
+ DEFINE_COMMAND(opme, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+
/* Channel options */
DEFINE_CHANNEL_OPTION(defaulttopic);
DEFINE_CHANNEL_OPTION(topicmask);
DEFINE_CHANNEL_OPTION(userinfo);
DEFINE_CHANNEL_OPTION(dynlimit);
DEFINE_CHANNEL_OPTION(topicsnarf);
+ DEFINE_CHANNEL_OPTION(vote);
DEFINE_CHANNEL_OPTION(nodelete);
DEFINE_CHANNEL_OPTION(toys);
DEFINE_CHANNEL_OPTION(setters);
DEFINE_CHANNEL_OPTION(ctcpreaction);
DEFINE_CHANNEL_OPTION(inviteme);
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);
modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, "flags", "+oper", NULL);
if(off_channel > 1)
lvlUserInfo,
lvlInviteMe,
lvlTopicSnarf,
+ lvlVote,
NUM_LEVEL_OPTIONS
};
unsigned long visited;
unsigned long limitAdjusted;
unsigned long ownerTransfer;
+ unsigned long expiry;
char *topic;
char *greeting;
char *registrar;
char *topic_mask;
+ char *vote;
+ unsigned int vote_start;
+ dict_t vote_options;
+
unsigned int flags : 30;
unsigned int may_opchan : 1;
unsigned int max;
+ unsigned int max_time;
unsigned int last_refresh;
unsigned short banCount;
unsigned short userCount;
unsigned int present : 1;
unsigned int flags : USER_FLAGS_SIZE;
+ unsigned short voted;
+ unsigned int votefor;
+
/* linked list of userDatas for a chanData */
struct userData *prev;
struct userData *next;
struct suspended *previous;
};
+struct vote_option
+{
+ char *name;
+ unsigned int option_id;
+ char *option_str;
+ unsigned int voted;
+};
+
struct do_not_register
{
char chan_name[CHANNELLEN+1];
char reason[1];
};
+#define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0)
+struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
+struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason);
void init_chanserv(const char *nick);
void del_channel_user(struct userData *user, int do_gc);
struct channelList *chanserv_support_channels(void);
unsigned short user_level_from_name(const char *name, unsigned short clamp_level);
struct do_not_register *chanserv_is_dnr(const char *chan_name, struct handle_info *handle);
int check_user_level(struct chanNode *channel, struct userNode *user, enum levelOption opt, int allow_override, int exempt_owner);
+struct mod_chanmode *find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask);
+void handle_new_channel_created(char *chan, struct userNode *user);
#endif
strcpy(cNode->name, name);
banList_init(&cNode->banlist);
modeList_init(&cNode->members);
+ userList_init(&cNode->invited);
mod_chanmode(NULL, cNode, argv, nn, MCP_FROM_SERVER);
dict_insert(channels, cNode->name, cNode);
cNode->timestamp = time_;
modeList_clean(&channel->members);
banList_clean(&channel->banlist);
+ userList_clean(&channel->invited);
free(channel);
}
unsigned int n;
struct modeNode *mn;
- if (!victim || !channel || IsService(victim) || !GetUserMode(channel, victim))
+ if (!victim || !channel || !GetUserMode(channel, victim))
return;
-
+
/* Update the kicker's idle time (kicker may be null if it was a server) */
if (kicker && (mn = GetUserMode(channel, kicker)))
mn->idle_since = now;
#define MODE_REGISTERED 0x8000 /* Bahamut +r */
#define MODE_APASS 0x10000 /* +A adminpass */
#define MODE_UPASS 0x20000 /* +U userpass */
+#define MODE_NONOTICES 0x40000 /* +N */
+#define MODE_NOAMSGS 0x80000 /* +M */
+#define MODE_ALTCHAN 0x100000 /* +F */
+#define MODE_ACCESS 0x200000 /* +a */
+#define MODE_NOFLOOD 0x400000 /* +f */
+#define MODE_AUDITORIUM 0x800000 /* +u */
+#define MODE_SSLCHAN 0x1000000 /* +S */
#define MODE_REMOVE 0x80000000
-#define FLAGS_OPER 0x0001 /* global operator +o */
-#define FLAGS_INVISIBLE 0x0004 /* invisible +i */
-#define FLAGS_WALLOP 0x0008 /* receives wallops +w */
-#define FLAGS_DEAF 0x0020 /* deaf +d */
-#define FLAGS_SERVICE 0x0040 /* cannot be kicked, killed or deoped +k */
-#define FLAGS_GLOBAL 0x0080 /* receives global messages +g */
-#define FLAGS_NOCHAN 0x0100 /* hide channels in whois +n */
+#define FLAGS_OPER 0x0001 /* +o global operator */
+#define FLAGS_INVISIBLE 0x0004 /* +i invisible */
+#define FLAGS_WALLOP 0x0008 /* +w receives wallops */
+#define FLAGS_DEAF 0x0020 /* +d deaf */
+#define FLAGS_SERVICE 0x0040 /* +k cannot be kicked, killed or deoped */
+#define FLAGS_GLOBAL 0x0080 /* +g receives global messages */
+#define FLAGS_NOCHAN 0x0100 /* +n hide channels in whois */
#define FLAGS_PERSISTENT 0x0200 /* for reserved nicks, this isn't just one-shot */
#define FLAGS_GAGGED 0x0400 /* for gagged users */
#define FLAGS_AWAY 0x0800 /* for away users */
#define FLAGS_STAMPED 0x1000 /* for users who have been stamped */
-#define FLAGS_HIDDEN_HOST 0x2000 /* user's host is masked by their account */
-#define FLAGS_REGNICK 0x4000 /* user owns their current nick */
+#define FLAGS_HIDDEN_HOST 0x2000 /* +x user's host is masked by their account */
+#define FLAGS_REGNICK 0x4000 /* +r user owns their current nick */
#define FLAGS_REGISTERING 0x8000 /* user has issued account register command, is waiting for email cookie */
#define FLAGS_DUMMY 0x10000 /* user is not announced to other servers */
-#define FLAGS_NOIDLE 0x20000 /* hide idle time in whois +I */
+#define FLAGS_NOIDLE 0x20000 /* +I hide idle time in whois */
+#define FLAGS_NETSERV 0x40000 /* +S */
+#define FLAGS_SECURITYSERV 0x80000 /* +D */
+#define FLAGS_XTRAOP 0x100000 /* +X */
+#define FLAGS_HIDDENOPER 0x200000 /* +H */
+#define FLAGS_SERVERNOTICE 0x400000 /* +s */
+#define FLAGS_SEENOIDLE 0x800000 /* +t */
#define IsOper(x) ((x)->modes & FLAGS_OPER)
#define IsService(x) ((x)->modes & FLAGS_SERVICE)
#define IsRegistering(x) ((x)->modes & FLAGS_REGISTERING)
#define IsDummy(x) ((x)->modes & FLAGS_DUMMY)
#define IsNoIdle(x) ((x)->modes & FLAGS_NOIDLE)
+#define IsSecurityServ(x) ((x)->modes & FLAGS_SECURITYSERV)
+#define IsNetServ(x) ((x)->modes & FLAGS_NETSERV)
+#define IsXtraOp(x) ((x)->modes & FLAGS_XTRAOP)
+#define IsServerNotice(x) ((x)->modes & FLAGS_SERVERNOTICE)
+#define IsHiddenOper(x) ((x)->modes & FLAGS_HIDDENOPER)
+#define IsSeeNoIdle(x) ((x)->modes & FLAGS_SEENOIDLE)
#define IsFakeHost(x) ((x)->fakehost[0] != '\0')
#define IsFakeIdent(x) ((x)->fakeident[0] != '\0')
#define IsLocal(x) ((x)->uplink == self)
+#define NOFLOODLEN 15
#define NICKLEN 30
#define USERLEN 10
#define HOSTLEN 63
#define REALLEN 50
-#define TOPICLEN 250
+#define TOPICLEN 500
#define CHANNELLEN 200
#define MAXOPLEVEL 999
struct chanNode {
chan_mode_t modes;
unsigned int limit;
+ unsigned int access;
unsigned int locks;
char key[KEYLEN + 1];
+ char altchan[CHANNELLEN + 1];
+ char noflood[NOFLOODLEN + 1];
char upass[KEYLEN + 1];
char apass[KEYLEN + 1];
unsigned long timestamp; /* creation time */
struct modeList members;
struct banList banlist;
struct policer join_policer;
+ struct userList invited;
unsigned int join_flooded : 1;
unsigned int bad_channel : 1;
#include "log.h"
#include "modcmd.h"
#include "nickserv.h"
+#include "spamserv.h"
#if defined(HAVE_DIRENT_H)
#include <dirent.h>
#define DEFAULT_LINE_SIZE MAX_LINE_SIZE
#define DEFAULT_TABLE_SIZE 80
-extern struct userNode *global, *chanserv, *opserv, *nickserv;
+extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv;
struct userNode *message_dest;
struct userNode *message_source;
struct language *lang_C;
case 'N':
value = nickserv ? nickserv->nick : "NickServ";
break;
+ case 'X':
+ value = spamserv ? spamserv->nick : "SpamServ";
+ break;
case 's':
value = self->name;
break;
- case 'H':
+ case 'A':
value = handle ? handle->handle : "Account";
break;
+ case 'U':
+ value = message_dest ? message_dest->nick : "Nick";
+ break;
+ case 'I':
+ value = message_dest ? (IsFakeIdent(message_dest) ? message_dest->fakeident : message_dest->ident) : "Ident";
+ break;
+ case 'H':
+ value = message_dest ? (IsFakeHost(message_dest) ? message_dest->fakehost : message_dest->hostname) : "Hostname";
+ break;
#define SEND_LINE(TRUNCED) do { \
line[pos] = 0; \
if (pos > 0) { \
{ "MSG_STUPID_ACCESS_CHANGE", "Please ask someone $belse$b to demote you." },
{ "MSG_NO_SEARCH_ACCESS", "You do not have enough access to search based on $b%s$b." },
{ "MSG_INVALID_CRITERIA", "$b%s$b is an invalid search criteria." },
+ { "MSG_INVALID_FIELD", "$b%s$b is an invalid search field." },
{ "MSG_MATCH_COUNT", "Found $b%u$b matches." },
{ "MSG_NO_MATCHES", "Nothing matched the criteria of your search." },
{ "MSG_TOPIC_UNKNOWN", "No help on that topic." },
if (info && (info[0] == '.'))
info = NULL;
init_chanserv(info);
+
+ info = conf_get_data("services/spamserv/nick", RECDB_QSTRING);
+ if (info && (info[0] == '.'))
+ info = NULL;
+ init_spamserv(info);
god_policer_params = policer_params_new();
if ((dict = conf_get_data("policers/commands-god", RECDB_OBJECT))) {
#include "global.h"
#include "modules.h"
#include "opserv.h"
+#include "spamserv.h"
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#define KEY_NICK "nick"
#define KEY_DB_BADCHANS "badchans"
#define KEY_HELP_CHANNEL "help_channel"
+#define KEY_PUBLIC_CHANNEL "public_channel"
#define KEY_PAGE_DEST "page_dest"
#define KEY_CMDWORD "cmdword"
#define KEY_PERSIST_LENGTH "persist_length"
#define KEY_PRIVMSG_ONLY "privmsg_only"
#define KEY_REQ_ON_JOIN "req_on_join"
#define KEY_AUTO_VOICE "auto_voice"
+#define KEY_AUTO_JOIN "auto_join"
#define KEY_AUTO_DEVOICE "auto_devoice"
#define KEY_LAST_ACTIVE "last_active"
{ "HSMSG_SET_PRIVMSGONLY", "$bPrivmsgOnly $b %s" },
{ "HSMSG_SET_REQONJOIN", "$bReqOnJoin $b %s" },
{ "HSMSG_SET_AUTOVOICE", "$bAutoVoice $b %s" },
+ { "HSMSG_SET_AUTOJOIN", "$bAutoJoin $b %s" },
{ "HSMSG_SET_AUTODEVOICE", "$bAutoDevoice $b %s" },
+ { "HSMSG_SET_PUBLICCHAN", "$bPublicChan $b %s" },
{ "HSMSG_PAGE_NOTICE", "notice" },
{ "HSMSG_PAGE_PRIVMSG", "privmsg" },
{ "HSMSG_PAGE_ONOTICE", "onotice" },
struct userNode *helpserv;
struct chanNode *helpchan;
+ struct chanNode *publicchan;
struct chanNode *page_targets[PGSRC_COUNT];
enum page_type page_types[PGSRC_COUNT];
unsigned int privmsg_only : 1;
unsigned int req_on_join : 1;
unsigned int auto_voice : 1;
+ unsigned int auto_join : 1;
unsigned int auto_devoice : 1;
unsigned int helpchan_empty : 1;
helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
return;
}
- if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
- helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
+ if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (!hs->publicchan || (hs->publicchan && !GetUserMode(hs->publicchan, user)))) {
+ if(hs->publicchan)
+ helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->publicchan->name);
+ else
+ helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
return;
}
req->helper = GetHSUser(hs, user->handle_info);
assert(req->helper);
req->assigned = now;
-
+
+ if (req->user && hs->auto_join) {
+ irc_svsjoin(hs->helpserv,req->user,hs->helpchan);
+ }
+
if (old_helper) {
helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle);
req->helper->reassigned_to[0]++;
if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user)))
mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
}
-
+
return 1;
}
dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
}
helpserv_botlist_append(botlist, hs);
-
return hs;
}
char *nick, *helpchan, reason[MAXLEN];
struct handle_info *handle;
- REQUIRE_PARMS(4);
+ REQUIRE_PARMS(3);
nick = argv[1];
if (!is_valid_nick(nick)) {
helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick);
}
static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
- char reason[MAXLEN], channame[CHANNELLEN], botname[NICKLEN];
+ char reason[MAXLEN], channame[CHANNELLEN], *botname;
struct helpserv_botlist *botlist;
size_t len;
helpserv_botlist_remove(botlist, bot);
if (!botlist->used)
dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name);
- len = strlen(bot->helpserv->nick) + 1;
- safestrncpy(botname, bot->helpserv->nick, len);
+ botname=bot->helpserv->nick;
len = strlen(bot->helpchan->name) + 1;
safestrncpy(channame, bot->helpchan->name, len);
snprintf(reason, sizeof(reason), quit_fmt, actor);
OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE");
}
+static HELPSERV_OPTION(opt_auto_join) {
+ OPTION_BINARY(hs->auto_join, "HSMSG_SET_AUTOJOIN");
+}
+
static HELPSERV_OPTION(opt_auto_devoice) {
OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
}
+static HELPSERV_OPTION(opt_publicchan) {
+ char *publicchan;
+ int changed=0;
+ if (argc > 0) {
+ publicchan = argv[0];
+ if(strcmp(publicchan, "*")) {
+ if (!IsChannelName(publicchan)) {
+ helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
+ HELPSERV_SYNTAX();
+ return 0;
+ }
+ if (opserv_bad_channel(publicchan)) {
+ helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
+ return 0;
+ }
+ }
+ if (!hs->publicchan || (hs->publicchan && irccasecmp(hs->publicchan->name, publicchan))) {
+ if(hs->publicchan) {
+ //there is another public chan o.O
+ //part
+ DelChannelUser(hs->helpserv, hs->publicchan, "unregistered.", 0);
+ hs->publicchan = NULL;
+ }
+ changed = 1;
+ if(strcmp(publicchan, "*")) {
+ if (!(hs->publicchan = GetChannel(publicchan))) {
+ hs->publicchan = AddChannel(publicchan, now, NULL, NULL);
+ AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
+ } else {
+ struct mod_chanmode change;
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].mode = MODE_CHANOP;
+ change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
+ mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
+ }
+ }
+ }
+ } else {
+ changed = 0;
+ }
+ helpserv_notice(user, "HSMSG_SET_PUBLICCHAN", (hs->publicchan) ? hs->publicchan->name : user_find_message(user,"MSG_NONE")); \
+ return changed;
+}
+
static HELPSERV_FUNC(cmd_set) {
helpserv_option_func_t *opt;
if (argc < 2) {
unsigned int i;
helpserv_option_func_t *display[] = {
- opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
+ opt_publicchan, opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
opt_pagetype, opt_alert_page_type, opt_status_page_type,
opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed,
opt_idle_delay, opt_whine_delay, opt_whine_interval,
opt_empty_interval, opt_stale_delay, opt_request_persistence,
opt_helper_persistence, opt_notification, opt_id_wrap,
- opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice,
+ opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, opt_auto_join,
opt_auto_devoice
};
/* Other settings and state */
saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name);
+ if(hs->publicchan) saxdb_write_string(ctx, KEY_PUBLIC_CHANNEL, hs->publicchan->name);
slist = alloc_string_list(PGSRC_COUNT);
for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
struct chanNode *target = hs->page_targets[pagesrc];
saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only);
saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
+ saxdb_write_int(ctx, KEY_AUTO_JOIN, hs->auto_join);
saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) {
struct record_data *br = data, *raw_record;
struct helpserv_bot *hs;
- char *registrar, *helpchannel_name, *str;
+ char *registrar, *helpchannel_name, *publicchannel_name, *str;
dict_t users, requests;
enum page_source pagesrc;
enum message_type msgtype;
hs = register_helpserv(key, helpchannel_name, registrar);
+ publicchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_PUBLIC_CHANNEL, RECDB_QSTRING);
+ if (publicchannel_name) {
+ if(!IsChannelName(publicchannel_name)) {
+ log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
+ return 0;
+ } else {
+ if (!(hs->publicchan = GetChannel(publicchannel_name))) {
+ hs->publicchan = AddChannel(publicchannel_name, now, NULL, NULL);
+ AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
+ } else {
+ struct mod_chanmode change;
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].mode = MODE_CHANOP;
+ change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
+ mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
+ }
+ }
+ }
raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL);
switch (raw_record ? raw_record->type : RECDB_INVALID) {
case RECDB_QSTRING:
hs->req_on_join = str ? enabled_string(str) : 0;
str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING);
hs->auto_voice = str ? enabled_string(str) : 0;
+ str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_JOIN, RECDB_QSTRING);
+ hs->auto_join = str ? enabled_string(str) : 0;
str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
hs->auto_devoice = str ? enabled_string(str) : 0;
str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
struct helpserv_request *req = iter_data(it);
if (mn->user != req->user)
+ continue;
+ if (GetUserMode(hs->helpchan, mn->user)) //publicchan
continue;
if (req->text->used) {
helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED);
if (!nicknewest || (nicknewest->opened < req->opened))
nicknewest = req;
+
if (hs->auto_voice && req->helper)
{
struct mod_chanmode change;
if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) {
/* Let the user know. Either the user is forced to be greeted, or the
* above has changed which request will get their next message. */
- helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
+ //helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
}
}
helpserv_define_option("PRIVMSGONLY", opt_privmsg_only);
helpserv_define_option("REQONJOIN", opt_req_on_join);
helpserv_define_option("AUTOVOICE", opt_auto_voice);
+ helpserv_define_option("AUTOJOIN", opt_auto_join);
helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
+ helpserv_define_option("PUBLICCHAN", opt_publicchan);
helpserv_usercmd_dict = dict_new();
dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
--- /dev/null
+/* mod-watchdog.c - Watchdog module for srvx
+ * Copyright 2003-2004 Martijn Smit and srvx Development Team
+ *
+ * This file is part of srvx.
+ *
+ * srvx is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with srvx; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+/* Adds new section to srvx.conf:
+ * "modules" {
+ * "watchdog" {
+ * "nick" "Watchdog";
+ * "modes" "+iok";
+ "ban_duration" "2h"; //only if the channel is registered with chanserv
+ "gline_duration" "1h";
+ "punishment_reason" "Your message contained a forbidden word.";
+ * };
+ * };
+ *
+ */
+
+#include "chanserv.h"
+#include "opserv.h"
+#include "conf.h"
+#include "modcmd.h"
+#include "saxdb.h"
+#include "timeq.h"
+#include "gline.h"
+
+#define KEY_BADWORDS "badwords"
+#define KEY_BADWORD_MASK "mask"
+#define KEY_BADWORD_TRIGGERED "count"
+#define KEY_BADWORD_ACTION "action"
+#define KEY_CHANNELS "channel"
+#define KEY_BADWORDID "badwordid"
+
+static const struct message_entry msgtab[] = {
+ { "WDMSG_REGISTER_SUCCESS", "$b%s$b is now registered with %s." },
+ { "WDMSG_NOT_REGISTERED", "$b%s$b is not registered with %s." },
+ { "WDMSG_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" },
+ { "WDMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." },
+ { "WDMSG_BADWORD_NOT_FOUND", "badword with ID %s does not exist." },
+ { "WDMSG_BADWORD_REMOVED", "badword ID $b%s$b has been removed (mask: '%s')" },
+ { "WDMSG_BADWORD_SET_DONE", "Done." },
+ { "WDMSG_BADWORD_SET_INVALID", "Invalid Option for setting %s" },
+ { "OSMSG_BADWORD_SETTING_INVALID", "unknown setting $b%s$b." },
+ { "WDMSG_BADWORD_SET", "Settings for BadWord entry $b%s$b" },
+ { "WDMSG_BADWORD_SET_MASK", "$bMASK$b: %s" },
+ { "WDMSG_BADWORD_SET_ACTION", "$bACTION$b: %s" },
+ { NULL, NULL }
+};
+
+struct badword {
+ char *id;
+ char *badword_mask;
+ unsigned int triggered : 29;
+ unsigned int action : 3;
+};
+
+struct watchdog_channel {
+ struct chanNode *channel;
+ //struct shitList *shitlist;
+};
+
+/* badword.action fields */
+#define BADACTION_KICK 0
+#define BADACTION_BAN 1
+#define BADACTION_KILL 2
+#define BADACTION_GLINE 3
+
+static struct {
+ const char *nick;
+ const char *modes;
+ const char *punishment_reason;
+ unsigned long ban_duration;
+ unsigned long gline_duration;
+} watchdog_conf;
+
+const char *watchdog_module_deps[] = { NULL };
+struct userNode *watchdog;
+static struct module *watchdog_module;
+static struct service *watchdog_service;
+static dict_t shitlist;
+static dict_t chanlist;
+static struct log_type *MS_LOG;
+static unsigned int last_badword_id = 0;
+
+static struct watchdog_channel *add_channel(const char *name);
+static struct badword *add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id);
+#define watchdog_notice(target, format...) send_message(target , watchdog , ## format)
+
+static MODCMD_FUNC(cmd_addbad)
+{
+ dict_iterator_t it;
+ char *mask = unsplit_string(argv + 1, argc - 1, NULL);
+ for (it = dict_first(shitlist); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(match_ircglob(mask,badword->badword_mask)) {
+ reply("WDMSG_ALREADY_ADDED", mask, badword->id);
+ return 1;
+ }
+ }
+
+ struct badword *new_badword = add_badword(mask, 0, BADACTION_KICK, NULL);
+ for (it = dict_first(shitlist); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) {
+ dict_remove(shitlist, badword->id);
+ }
+ }
+
+ reply("WDMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_delbad)
+{
+ unsigned int n;
+
+ for (n=1; n<argc; n++) {
+ struct badword *badword = dict_find(shitlist, argv[n], NULL);
+ if (!badword) {
+ reply("WDMSG_BADWORD_NOT_FOUND", argv[n]);
+ continue;
+ }
+ reply("WDMSG_BADWORD_REMOVED", argv[n], badword->badword_mask);
+ dict_remove(shitlist, argv[n]);
+ }
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_setbad)
+{
+ struct badword *badword;
+ if ((badword = dict_find(shitlist, argv[1], NULL))) {
+ if (argc > 3) {
+ unsigned int ii;
+ char *setting = argv[2];
+ char *value = argv[3];
+ for( ii = 0; setting[ ii ]; ii++)
+ setting[ ii ] = toupper( setting[ ii ] );
+ for( ii = 0; value[ ii ]; ii++)
+ value[ ii ] = toupper( value[ ii ] );
+ if(!strcmp("MASK",setting)) {
+ free(badword->badword_mask);
+ badword->badword_mask = strdup(argv[3]);
+ badword->triggered = 0;
+ reply("WDMSG_BADWORD_SET_DONE");
+ }
+ else if(!strcmp("ACTION",setting)) {
+ if (!strcmp("1",value) || !strcmp("KICK",value)) {
+ badword->action = BADACTION_KICK;
+ reply("WDMSG_BADWORD_SET_DONE");
+ } else if (!strcmp("2",value) || !strcmp("BAN",value)) {
+ badword->action = BADACTION_BAN;
+ reply("WDMSG_BADWORD_SET_DONE");
+ } else if (!strcmp("3",value) || !strcmp("KILL",value)) {
+ badword->action = BADACTION_KILL;
+ reply("WDMSG_BADWORD_SET_DONE");
+ } else if (!strcmp("4",value) || !strcmp("GLINE",value)) {
+ badword->action = BADACTION_GLINE;
+ reply("WDMSG_BADWORD_SET_DONE");
+ } else {
+ reply("WDMSG_BADWORD_SET_INVALID", setting);
+ }
+ } else {
+ reply("WDMSG_BADWORD_SETTING_INVALID", setting);
+ }
+
+ } else {
+ reply("WDMSG_BADWORD_SET", badword->id);
+ reply("WDMSG_BADWORD_SET_MASK", badword->badword_mask);
+ switch(badword->action) {
+ case BADACTION_KICK:
+ reply("WDMSG_BADWORD_SET_ACTION", "KICK");
+ break;
+ case BADACTION_BAN:
+ reply("WDMSG_BADWORD_SET_ACTION", "BAN");
+ break;
+ case BADACTION_KILL:
+ reply("WDMSG_BADWORD_SET_ACTION", "KILL");
+ break;
+ case BADACTION_GLINE:
+ reply("WDMSG_BADWORD_SET_ACTION", "GLINE");
+ break;
+ default:
+ reply("WDMSG_BADWORD_SET_ACTION", "*undef*");
+ }
+ }
+ } else {
+ reply("WDMSG_BADWORD_NOT_FOUND", argv[1]);
+ return 0;
+ }
+ return 1;
+}
+
+int
+badwords_sort(const void *pa, const void *pb)
+{
+ struct badword *a = *(struct badword**)pa;
+ struct badword *b = *(struct badword**)pb;
+
+ return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0);
+}
+
+static MODCMD_FUNC(cmd_listbad)
+{
+ struct helpfile_table tbl;
+ unsigned int count = 0, ii = 0;
+ struct badword **badwords;
+
+ dict_iterator_t it;
+ for (it = dict_first(shitlist); it; it = iter_next(it)) {
+ count++;
+ }
+ tbl.length = count+1;
+ tbl.width = 4;
+ tbl.flags = 0;
+ tbl.flags = TABLE_NO_FREE;
+ tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+ tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[0][0] = "#";
+ tbl.contents[0][1] = "Badword";
+ tbl.contents[0][2] = "Action";
+ tbl.contents[0][3] = "(Triggered)";
+ if(!count)
+ {
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ reply("MSG_NONE");
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ return 0;
+ }
+ badwords = alloca(count * sizeof(badwords[0]));
+ for (it = dict_first(shitlist); it; it = iter_next(it)) {
+ struct badword *bw = iter_data(it);
+ badwords[ii++] = bw;
+ }
+ qsort(badwords, count, sizeof(badwords[0]), badwords_sort);
+ for (ii = 1; ii <= count; ii++) {
+ struct badword *bw = badwords[ii-1];
+ tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[ii][0] = strdup(bw->id);
+ tbl.contents[ii][1] = strdup(bw->badword_mask);
+ switch(bw->action) {
+ case BADACTION_KICK:
+ tbl.contents[ii][2] = "KICK";
+ break;
+ case BADACTION_BAN:
+ tbl.contents[ii][2] = "BAN";
+ break;
+ case BADACTION_KILL:
+ tbl.contents[ii][2] = "KILL";
+ break;
+ case BADACTION_GLINE:
+ tbl.contents[ii][2] = "GLINE";
+ break;
+ default:
+ tbl.contents[ii][2] = "*undef*";
+ }
+ tbl.contents[ii][3] = strtab(bw->triggered);
+ }
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ for(ii = 1; ii < tbl.length; ++ii)
+ {
+ free(tbl.contents[ii]);
+ }
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_register)
+{
+ dict_iterator_t it;
+ struct modeNode *mn;
+
+ if(channel)
+ {
+
+ if(channel->bad_channel)
+ {
+ reply("CSMSG_ILLEGAL_CHANNEL", channel->name);
+ return 0;
+ }
+
+ if(!IsHelping(user)
+ && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
+ {
+ reply("CSMSG_MUST_BE_OPPED", channel->name);
+ return 0;
+ }
+
+ }
+ else
+ {
+
+ if((argc < 2) || !IsChannelName(argv[1]))
+ {
+ reply("MSG_NOT_CHANNEL_NAME");
+ return 0;
+ }
+
+ if(opserv_bad_channel(argv[1]))
+ {
+ reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
+ return 0;
+ }
+
+ channel = AddChannel(argv[1], now, NULL, NULL);
+ }
+
+ for (it = dict_first(chanlist); it; it = iter_next(it)) {
+ struct watchdog_channel *chan = iter_data(it);
+ if(chan->channel == channel) {
+ reply("CSMSG_ALREADY_REGGED", channel->name);
+ return 0;
+ }
+ }
+
+ add_channel(channel->name);
+ reply("WDMSG_REGISTER_SUCCESS", channel->name, watchdog->nick);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_unregister)
+{
+ struct watchdog_channel *chan = NULL;
+ dict_iterator_t it;
+
+ for (it = dict_first(chanlist); it; it = iter_next(it)) {
+ chan = iter_data(it);
+ if(chan->channel == channel)
+ break;
+ }
+
+ if(chan && chan->channel == channel) {
+ //found, unregister it!
+ char reason[MAXLEN];
+ sprintf(reason, "Unregistered by %s.", user->handle_info->handle);
+ DelChannelUser(watchdog, channel, reason, 0);
+ dict_remove(chanlist, channel->name);
+ reply("CSMSG_UNREG_SUCCESS", channel->name);
+ return 1;
+ } else {
+ reply("WDMSG_NOT_REGISTERED", channel->name, watchdog->nick);
+ return 0;
+ }
+
+}
+
+static void
+watchdog_detected_badword(struct userNode *user, struct chanNode *chan, struct badword *badword)
+{
+ char *hostmask;
+ char *reason = watchdog_conf.punishment_reason;
+ char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };
+ switch(badword->action) {
+ case BADACTION_BAN:
+ hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);
+ sanitize_ircmask(hostmask);
+ if(chan->channel_info) {
+ //registered channel
+ add_channel_ban(chan->channel_info, hostmask, watchdog->nick, now, now, now + watchdog_conf.ban_duration, reason);
+ }
+ struct mod_chanmode change;
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].mode = MODE_BAN;
+ change.args[0].u.hostmask = hostmask;
+ mod_chanmode_announce(watchdog, chan, &change);
+ free(hostmask);
+
+ case BADACTION_KICK:
+ if(GetUserMode(chan, user))
+ KickChannelUser(user, chan, watchdog, reason);
+ break;
+ case BADACTION_KILL:
+ DelUser(user, watchdog, 1, reason);
+ break;
+ case BADACTION_GLINE:
+ irc_ntop(mask + 2, sizeof(mask) - 2, &user->ip);
+ gline_add(watchdog->nick, mask, watchdog_conf.gline_duration, reason, now, now, 0, 1);
+ break;
+ default:
+ //error?
+ break;
+ }
+}
+
+static void
+watchdog_channel_message(struct userNode *user, struct chanNode *chan, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice))
+{
+ dict_iterator_t it;
+
+ if(!watchdog || !dict_find(chanlist, chan->name, NULL))
+ return;
+
+ for (it = dict_first(shitlist); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(match_ircglob(text, badword->badword_mask)) {
+ watchdog_detected_badword(user, chan, badword);
+ }
+ }
+}
+
+static struct badword*
+add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id)
+{
+ struct badword *badword;
+
+ badword = calloc(1, sizeof(*badword));
+ if (!badword)
+ return NULL;
+
+ if(!id) {
+ last_badword_id++;
+ badword->id = strtab(last_badword_id);
+ } else
+ badword->id = strdup(id);
+ badword->badword_mask = strdup(badword_mask);
+ badword->triggered = triggered;
+ badword->action = action;
+ dict_insert(shitlist, badword->id, badword);
+ return badword;
+}
+
+static void
+free_shitlist_entry(void *data)
+{
+ struct badword *badword = data;
+ free(badword->id);
+ free(badword->badword_mask);
+ free(badword);
+}
+
+static struct watchdog_channel*
+add_channel(const char *name)
+{
+ struct watchdog_channel *wc;
+ struct mod_chanmode *change;
+
+ if(!watchdog) //module disabled
+ return NULL;
+
+ wc = calloc(1, sizeof(*wc));
+ if (!wc)
+ return NULL;
+
+ wc->channel = AddChannel(name, now, NULL, NULL);
+ change = mod_chanmode_alloc(1);
+ change->argc = 1;
+ change->args[0].mode = MODE_CHANOP;
+ change->args[0].u.member = AddChannelUser(watchdog, wc->channel);
+ mod_chanmode_announce(watchdog, wc->channel, change);
+ mod_chanmode_free(change);
+ dict_insert(chanlist, wc->channel->name, wc);
+ return wc;
+}
+
+static void
+free_chanlist_entry(void *data)
+{
+ struct watchdog_channel *wc = data;
+
+ free(wc);
+}
+
+static void
+watchdog_conf_read(void)
+{
+ dict_t conf_node;
+ const char *str;
+
+ str = "modules/watchdog";
+ if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
+ log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
+ return;
+ }
+
+ str = database_get_data(conf_node, "nick", RECDB_QSTRING);
+ if(watchdog_conf.nick && strcmp(watchdog_conf.nick, str)) {
+ //nick changed
+ }
+ watchdog_conf.nick = str;
+
+ str = database_get_data(conf_node, "modes", RECDB_QSTRING);
+ watchdog_conf.modes = (str ? str : NULL);
+
+ str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING);
+ watchdog_conf.ban_duration = str ? ParseInterval(str) : ParseInterval("2h");
+
+ str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING);
+ watchdog_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h");
+
+ str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING);
+ watchdog_conf.punishment_reason = (str ? str : "Your message contained a forbidden word.");
+
+}
+
+static int
+watchdog_saxdb_read_shitlist(const char *name, void *data, UNUSED_ARG(void *extra))
+{
+ struct record_data *rd = data;
+ char *badword;
+ char *triggered, *action;
+
+ if (rd->type == RECDB_OBJECT) {
+ dict_t obj = GET_RECORD_OBJECT(rd);
+ /* new style structure */
+ badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING);
+ triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING);
+ action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING);
+
+ add_badword(badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), name);
+ }
+ return 0;
+}
+
+static int
+watchdog_saxdb_read_chanlist(const char *name, void *data, UNUSED_ARG(void *extra))
+{
+ struct record_data *rd = data;
+
+ if (rd->type == RECDB_OBJECT) {
+ //dict_t obj = GET_RECORD_OBJECT(rd);
+ /* nothing in here, yet */
+
+ add_channel(name);
+ }
+ return 0;
+}
+
+static int
+watchdog_saxdb_read(struct dict *db)
+{
+ struct dict *object;
+ char *str;
+ str = database_get_data(db, KEY_BADWORDID, RECDB_QSTRING);
+ last_badword_id = str ? strtoul(str, NULL, 0) : 0;
+
+ if ((object = database_get_data(db, KEY_BADWORDS, RECDB_OBJECT)))
+ dict_foreach(object, watchdog_saxdb_read_shitlist, NULL);
+
+ if ((object = database_get_data(db, KEY_CHANNELS, RECDB_OBJECT)))
+ dict_foreach(object, watchdog_saxdb_read_chanlist, NULL);
+
+ return 1;
+}
+
+static int
+watchdog_saxdb_write(struct saxdb_context *ctx)
+{
+ dict_iterator_t it;
+
+ saxdb_write_int(ctx, KEY_BADWORDID, last_badword_id);
+
+ if (dict_size(shitlist)) {
+ saxdb_start_record(ctx, KEY_BADWORDS, 1);
+ for (it = dict_first(shitlist); it; it = iter_next(it)) {
+ struct badword *badword = iter_data(it);
+ if(badword && badword->badword_mask) {
+ saxdb_start_record(ctx, iter_key(it), 0);
+
+ saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask);
+ saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered);
+ saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action);
+
+ saxdb_end_record(ctx);
+ }
+ }
+ saxdb_end_record(ctx);
+ }
+
+ if (dict_size(chanlist)) {
+ saxdb_start_record(ctx, KEY_CHANNELS, 1);
+ for (it = dict_first(chanlist); it; it = iter_next(it)) {
+ struct watchdog_channel *wc = iter_data(it);
+ if(wc && wc->channel && wc->channel->name) {
+ saxdb_start_record(ctx, wc->channel->name, 0);
+ //anything else?
+ saxdb_end_record(ctx);
+ }
+ }
+ saxdb_end_record(ctx);
+ }
+
+ return 0;
+}
+
+static void
+watchdog_cleanup(void)
+{
+ dict_delete(shitlist);
+ dict_delete(chanlist);
+}
+
+int
+watchdog_init(void)
+{
+ MS_LOG = log_register_type("Watchdog", "file:watchdog.log");
+
+ /* set up shitlist dict */
+ dict_delete(shitlist);
+ shitlist = dict_new();
+ dict_set_free_data(shitlist, free_shitlist_entry);
+ /* set up chanlist dict */
+ dict_delete(chanlist);
+ chanlist = dict_new();
+ dict_set_free_data(chanlist, free_chanlist_entry);
+
+ const char *nick, *modes;
+ if((nick = conf_get_data("modules/watchdog/nick", RECDB_QSTRING))) {
+ modes = conf_get_data("modules/watchdog/modes", RECDB_QSTRING);
+ watchdog = AddLocalUser(nick, nick, NULL, "Watchdog Service", modes);
+ watchdog_service = service_register(watchdog);
+ watchdog_service->trigger = ',';
+ reg_allchanmsg_func(watchdog, watchdog_channel_message);
+ }
+
+ conf_register_reload(watchdog_conf_read);
+ reg_exit_func(watchdog_cleanup);
+ saxdb_register("Watchdog", watchdog_saxdb_read, watchdog_saxdb_write);
+
+ watchdog_module = module_register("Watchdog", MS_LOG, "mod-watchdog.help", NULL);
+ modcmd_register(watchdog_module, "addbad", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+ modcmd_register(watchdog_module, "delbad", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+ modcmd_register(watchdog_module, "setbad", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+ modcmd_register(watchdog_module, "listbad", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+ modcmd_register(watchdog_module, "register", cmd_register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
+ modcmd_register(watchdog_module, "unregister", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_CHANNEL, "flags", "+helping", NULL);
+ message_register_table(msgtab);
+
+ return 1;
+}
+
+int
+watchdog_finalize(void) {
+ dict_t conf_node;
+ const char *str;
+
+ str = "modules/watchdog";
+ if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
+ log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
+ return 0;
+ }
+
+ str = database_get_data(conf_node, "nick", RECDB_QSTRING);
+ if (str) watchdog_conf.nick = str;
+
+ str = database_get_data(conf_node, "modes", RECDB_QSTRING);
+ if (str) watchdog_conf.modes = str;
+
+ str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING);
+ if (str) watchdog_conf.ban_duration = ParseInterval(str);
+
+ str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING);
+ if (str) watchdog_conf.gline_duration = ParseInterval(str);
+
+ str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING);
+ if (str) watchdog_conf.punishment_reason = str;
+
+ return 1;
+}
--- /dev/null
+
\ No newline at end of file
service = iter_data(it);
tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0]));
tbl.contents[ii][0] = service->bot->nick;
- tbl.contents[ii][1] = strtab(dict_size(service->commands));
+ tbl.contents[ii][1] = (service->commands && dict_size(service->commands) ? strtab(dict_size(service->commands)) : strtab(0));
tbl.contents[ii][2] = service->privileged ? "yes" : "no";
extra[ii*2] = service->trigger;
tbl.contents[ii][3] = extra+ii*2;
if (argc > 1)
send_message_type(4, user, cmd->parent->bot, "%s", git_version);
else
- send_message_type(12, user, cmd->parent->bot, "The srvx Development Team includes Paul Chang, Adrian Dewhurst, Miles Peterson, Michael Poole and others.\nThe srvx Development Team can be reached at http://sf.net/projects/srvx/ or in #srvx on irc.gamesurge.net.");
+ send_message_type(12, user, cmd->parent->bot, "The srvx Development Team includes Paul Chang, Adrian Dewhurst, Miles Peterson, Michael Poole and others.\nThe srvx Development Team can be reached at http://sf.net/projects/srvx/ or in #srvx on irc.gamesurge.net.\nThis version has been modified by pk910 - visit #srvx @ irc.webgamesnet.net.");
return 1;
}
struct svccmd *svccmd;
svccmd = service_make_alias(service, "stats", "*modcmd.joiner", NULL);
svccmd->min_opserv_level = 101;
+ svccmd = service_make_alias(service, "devnull", "*modcmd.joiner", NULL);
+ svccmd->min_opserv_level = 200;
svccmd = service_make_alias(service, "service", "*modcmd.joiner", NULL);
svccmd->min_opserv_level = 900;
}
#define KEY_OUNREGISTER_INACTIVE "ounregister_inactive"
#define KEY_OUNREGISTER_FLAGS "ounregister_flags"
#define KEY_HANDLE_TS_MODE "account_timestamp_mode"
+#define KEY_MAX_AUTHLOG_LEN "max_authlog_len"
#define KEY_ID "id"
#define KEY_PASSWD "passwd"
#define KEY_REGISTER_ON "register"
#define KEY_LAST_SEEN "lastseen"
#define KEY_INFO "info"
+#define KEY_DEVNULL "devnull"
+#define KEY_WEBSITE "website"
#define KEY_USERLIST_STYLE "user_style"
#define KEY_SCREEN_WIDTH "screen_width"
#define KEY_LAST_AUTHED_HOST "last_authed_host"
#define KEY_NOTE_SETTER "setter"
#define KEY_NOTE_NOTE "note"
#define KEY_KARMA "karma"
+#define KEY_AUTHLOG "authlog"
+#define KEY_AUTHLOG_LOGIN_TIME "login_time"
+#define KEY_AUTHLOG_LOGOUT_TIME "logout_time"
+#define KEY_AUTHLOG_HOSTMASK "hostmask"
+#define KEY_AUTHLOG_QUIT_REASON "quit_reason"
#define NICKSERV_VALID_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
{ "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", " Cookie: There is currently an allowauth cookie issued for this account" },
{ "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", " Cookie: There is currently an unknown cookie issued for this account" },
{ "NSMSG_HANDLEINFO_INFOLINE", " Infoline: %s" },
+ { "NSMSG_HANDLEINFO_DEVNULL", " DevNull Class: %s" },
+ { "NSMSG_HANDLEINFO_WEBSITE", " Website: %s" },
+ { "NSMSG_HANDLEINFO_ACCESS", " Access: %i" },
{ "NSMSG_HANDLEINFO_FLAGS", " Flags: %s" },
{ "NSMSG_HANDLEINFO_EPITHET", " Epithet: %s" },
{ "NSMSG_HANDLEINFO_FAKEIDENT", " Fake ident: %s" },
{ "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
{ "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
{ "NSMSG_SET_INFO", "$bINFO: $b%s" },
+ { "NSMSG_SET_DEVNULL", "$bDEVNULL: $b%s" },
+ { "NSMSG_SET_AUTOHIDE", "$bAUTOHIDE: $b%s" },
+ { "NSMSG_SET_WEBSITE", "$bWEBSITE: $b%s" },
{ "NSMSG_SET_WIDTH", "$bWIDTH: $b%d" },
{ "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH: $b%d" },
{ "NSMSG_SET_COLOR", "$bCOLOR: $b%s" },
unsigned char hard_maxlogins;
unsigned long ounregister_inactive;
unsigned long ounregister_flags;
+ unsigned int max_authlog_len;
} nickserv_conf;
+struct pendingLOCUser {
+ struct handle_info *handle_info;
+ unsigned long time;
+ struct authlogEntry *authlog;
+ struct pendingLOCUser *next;
+};
+
const char *titlehost_suffix = NULL;
+static struct pendingLOCUser *pendingLOCUsers = NULL;
/* We have 2^32 unique account IDs to use. */
unsigned long int highest_id = 0;
dict_insert(nickserv_handle_dict, hi->handle, hi);
hi->id = id;
+ hi->website = NULL;
+ hi->devnull = NULL;
dict_insert(nickserv_id_dict, strdup(id_base64), hi);
return hi;
free(hi->infoline);
free(hi->epithet);
free(hi->fakehost);
+ free(hi->devnull);
+ free(hi->website);
free(hi->fakeident);
if (hi->cookie) {
timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
if (!hil->used)
dict_remove(nickserv_email_dict, hi->email_addr);
}
+ struct authlogEntry *authlog, *next;
+ for(authlog = hi->authlog; authlog; authlog = next) {
+ next = authlog->next;
+ struct pendingLOCUser *pending, *prev_pending = NULL;
+ for(pending = pendingLOCUsers; pending; pending = pending->next) {
+ if(pending->authlog == authlog) {
+ if(prev_pending)
+ prev_pending->next = pending->next;
+ else
+ pendingLOCUsers = pending->next;
+ free(pending);
+ break;
+ }
+ prev_pending = pending;
+ }
+ free((char *) authlog->hostmask);
+ if(authlog->quit_reason)
+ free((char *) authlog->quit_reason);
+ free(authlog);
+ }
free(hi);
}
}
static int
-is_valid_email_addr(const char *email)
+is_valid_email_addr(const char *org_email)
{
- return strchr(email, '@') != NULL;
+ char email[strlen(org_email)+1];
+ strcpy(email, org_email);
+ //validate email address
+ //1st check: there need to be one @
+ char *p1 = strchr(email, '@');
+ if(!p1 || strchr(p1+1, '@')) return 0;
+ *p1 = '\0';
+ //2nd check: username (bevore @) must be at least 1 char long and out of part_chars
+ char *part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._%+-";
+ int i;
+ if(p1 - email == 0) return 0;
+ for(i = 0; i < (p1 - email); i++) {
+ if(!strchr(part_chars, email[i])) return 0;
+ }
+ //3rd check: there need to be at least 1 dot in the domain part and all characters out of part_chars
+ part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
+ char *p2 = NULL;
+ p1++;
+ i = 0;
+ while(*p1) {
+ if(*p1 == '.') {
+ if(!i) return 0;
+ i = 0;
+ p2 = p1;
+ } else if(!strchr(part_chars, *p1))
+ return 0;
+ else
+ i++;
+ p1++;
+ }
+ if(!p2) return 0;
+ //4th check: TLD must be <= 5 chars, no special chars
+ i = 0;
+ p2++;
+ while(*p2) {
+ if(!isalpha(*p2))
+ return 0;
+ else
+ i++;
+ p2++;
+ }
+ if(i > 5) return 0;
+ return 1;
}
static const char *
unsigned int ii;
/* If no hostmasks on the account, allow it. */
- if (!hi->masks->used)
+ if (!hi->masks->used || IsDummy(user))
return 1;
/* If any hostmask matches, allow it. */
for (ii=0; ii<hi->masks->used; ii++)
/* A leading dot indicates the stored value is actually a title. */
snprintf(buffer, sizeof(buffer), "%s.%s.%s", handle->handle, handle->fakehost+1, titlehost_suffix);
return buffer;
+ } else if (handle->fakehost[0] == '$') {
+ /* A leading $ indicates the stored value begins with the user handle. */
+ snprintf(buffer, sizeof(buffer), "%s%s", handle->handle, handle->fakehost+1);
+ return buffer;
}
return handle->fakehost;
}
}
reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
+ if ((oper_has_access(user, cmd->parent->bot, 200, 1)) || IsNetworkHelper(user))
+ reply("NSMSG_HANDLEINFO_DEVNULL", (hi->devnull ? hi->devnull : nsmsg_none));
+ if (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+ reply("NSMSG_HANDLEINFO_WEBSITE", (hi->website ? hi->website : nsmsg_none));
+ if(hi->opserv_level > 0 && user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+ reply("NSMSG_HANDLEINFO_ACCESS", hi->opserv_level);
if (HANDLE_FLAGGED(hi, FROZEN))
reply("NSMSG_HANDLEINFO_VACATION");
reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
free(old_handle);
+ apply_fakehost(hi, NULL);
return 1;
}
failpw_func_list[failpw_func_used++] = func;
}
+static struct authlogEntry *authlog_add(struct handle_info *hi, struct userNode *user, const char *mask) {
+ if(!hi || (!user && !mask)) return NULL;
+ if(!mask)
+ mask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
+ struct authlogEntry *authlog, *next, *prev = NULL;
+ authlog = malloc(sizeof(*authlog));
+ authlog->login_time = now;
+ authlog->logout_time = 0;
+ authlog->hostmask = mask;
+ authlog->quit_reason = NULL;
+ authlog->user = user;
+ authlog->next = hi->authlog;
+ hi->authlog = authlog;
+ unsigned int i = 0;
+ for(authlog = hi->authlog; authlog; authlog = next) {
+ i++;
+ next = authlog->next;
+ if(i > nickserv_conf.max_authlog_len) {
+ struct pendingLOCUser *pending, *prev_pending = NULL;
+ for(pending = pendingLOCUsers; pending; pending = pending->next) {
+ if(pending->authlog == authlog) {
+ if(prev_pending)
+ prev_pending->next = pending->next;
+ else
+ pendingLOCUsers = pending->next;
+ free(pending);
+ break;
+ }
+ prev_pending = pending;
+ }
+ free((char *) authlog->hostmask);
+ if(authlog->quit_reason)
+ free((char *) authlog->quit_reason);
+ if(prev)
+ prev->next = authlog->next;
+ else
+ hi->authlog = authlog->next;
+ free(authlog);
+ } else
+ prev = authlog;
+ }
+ return hi->authlog;
+}
+
static NICKSERV_FUNC(cmd_auth)
{
int pw_arg, used, maxlogins;
return 1;
}
}
+ if (HANDLE_FLAGGED(hi, AUTOHIDE)) {
+ //ok we have a fakehost set... but we need to set mode +x
+ irc_svsmode(nickserv,user,"+x");
+ }
set_user_handle_info(user, hi, 1);
if (nickserv_conf.email_required && !hi->email_addr)
if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname))
string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
}
+ authlog_add(hi, user, NULL);
argv[pw_arg] = "****";
reply("NSMSG_AUTH_SUCCESS");
return 1;
}
+struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask)
+{
+ struct handle_info *hi;
+ unsigned int match = 0, ii = 0;
+ hi = dict_find(nickserv_handle_dict, user, NULL);
+ if(!hi)
+ return NULL;
+ /* If no hostmasks on the account, allow it. */
+ if (hi->masks->used) {
+ /* If any hostmask matches, allow it. */
+ for (ii=0; ii<hi->masks->used; ii++)
+ if (match_ircglob(hostmask, hi->masks->list[ii]) || match_ircglob(ipmask, hi->masks->list[ii])) {
+ match = 1;
+ break;
+ }
+ if(!match)
+ return NULL;
+ }
+ if(!checkpass(pass, hi->passwd))
+ return NULL;
+ if (HANDLE_FLAGGED(hi, SUSPENDED))
+ return NULL;
+ char *ptr = malloc(strlen(hostmask)+1);
+ strcpy(ptr, hostmask);
+ struct authlogEntry *authlog = authlog_add(hi, NULL, ptr);
+ struct pendingLOCUser *pending;
+ if(authlog && (pending = malloc(sizeof(*pending)))) {
+ pending->handle_info = hi;
+ pending->time = now;
+ pending->authlog = authlog;
+ pending->next = pendingLOCUsers;
+ pendingLOCUsers = pending;
+ }
+ return hi;
+}
+
+char *getfakehost(const char *user)
+{
+ struct handle_info *hi;
+ hi = dict_find(nickserv_handle_dict, user, NULL);
+ if(!hi)
+ return 0;
+ return generate_fakehost(hi);
+}
+
static allowauth_func_t *allowauth_func_list;
static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
allowauth_func_list[allowauth_func_used++] = func;
}
+static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi);
+
+static MODCMD_FUNC(cmd_authlog)
+{
+ return cmd_authlog_func(user, cmd, user->handle_info);
+}
+
+static MODCMD_FUNC(cmd_oauthlog) {
+ struct handle_info *hi;
+
+ NICKSERV_MIN_PARMS(1);
+
+ if (!(hi = get_victim_oper(user, argv[1])))
+ return 0;
+
+ return cmd_authlog_func(user, cmd, hi);
+}
+
+static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi) {
+ struct helpfile_table tbl;
+ struct authlogEntry *authlog;
+ int i = 0;
+
+ for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+ i++;
+ }
+
+ tbl.length = i+1;
+ tbl.width = 4;
+ tbl.flags = 0;
+ tbl.flags = TABLE_NO_FREE;
+ tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+ tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[0][0] = "Hostmask";
+ tbl.contents[0][1] = "Login";
+ tbl.contents[0][2] = "Logout";
+ tbl.contents[0][3] = "Quit Reason";
+
+ if(!tbl.length) {
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ reply("MSG_NONE");
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ return 0;
+ }
+
+ char *str, *ptr;
+ char intervalBuf[INTERVALLEN];
+ i = 0;
+ for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+ tbl.contents[++i] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[i][0] = authlog->hostmask;
+ str = intervalString(intervalBuf, now - authlog->login_time, hi);
+ ptr = malloc(strlen(str)+1);
+ strcpy(ptr, str);
+ tbl.contents[i][1] = ptr;
+ if(authlog->logout_time)
+ str = intervalString(intervalBuf, now - authlog->logout_time, hi);
+ else if(!authlog->user)
+ str = "Unknown";
+ else {
+ sprintf(intervalBuf, "Never (%s)", authlog->user->nick);
+ str = intervalBuf;
+ }
+ ptr = malloc(strlen(str)+1);
+ strcpy(ptr, str);
+ tbl.contents[i][2] = ptr;
+ tbl.contents[i][3] = (authlog->quit_reason ? authlog->quit_reason : "-");
+ }
+
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ for(i = 1; i < tbl.length; ++i)
+ {
+ free((char *) tbl.contents[i][1]);
+ free((char *) tbl.contents[i][2]);
+ free(tbl.contents[i]);
+ }
+ free(tbl.contents[0]);
+ free(tbl.contents);
+
+ return 0;
+}
+
static NICKSERV_FUNC(cmd_allowauth)
{
struct userNode *target;
unsigned int i;
char *set_display[] = {
"INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
- "EMAIL", "MAXLOGINS", "LANGUAGE"
+ "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL"
};
send_message(user, nickserv, "NSMSG_SETTING_LIST");
return 1;
}
+static OPTION_FUNC(opt_devnull)
+{
+ const char *devnull;
+
+ if (argc > 1) {
+ if (!override) {
+ send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+ return 0;
+ }
+ if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+ free(hi->devnull);
+ hi->devnull = NULL;
+ } else {
+ devnull = unsplit_string(argv+1, argc-1, NULL);
+ if(devnull_check(devnull) == 1) {
+ if(hi->devnull)
+ free(hi->devnull);
+ hi->devnull = strdup(devnull);
+ }
+ }
+ }
+
+ devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE");
+ send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull);
+ return 1;
+}
+
+void nickserv_devnull_delete(char *name) {
+ dict_iterator_t it;
+ struct handle_info *hi;
+
+ for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
+ hi = iter_data(it);
+ if (hi->devnull && !irccasecmp(name, hi->devnull)) {
+ free(hi->devnull);
+ hi->devnull = NULL;
+ }
+ }
+}
+
+void nickserv_devnull_rename(char *oldname, char *newname) {
+ dict_iterator_t it;
+ struct handle_info *hi;
+
+ for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
+ hi = iter_data(it);
+ if (hi->devnull && !irccasecmp(oldname, hi->devnull)) {
+ hi->devnull = strdup(newname);
+ }
+ }
+}
+
+static OPTION_FUNC(opt_website)
+{
+ const char *website;
+
+ if (argc > 1) {
+ if (!HANDLE_FLAGGED(user->handle_info, BOT)) {
+ send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+ return 0;
+ }
+ if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+ free(hi->website);
+ hi->website = NULL;
+ } else {
+ website = unsplit_string(argv+1, argc-1, NULL);
+ hi->website = strdup(website);
+ }
+ }
+ if (HANDLE_FLAGGED(user->handle_info, BOT)) {
+ website = hi->website ? hi->website : user_find_message(user, "MSG_NONE");
+ send_message(user, nickserv, "NSMSG_SET_WEBSITE", website);
+ }
+ return 1;
+}
+
static OPTION_FUNC(opt_width)
{
if (argc > 1)
return 1;
}
+static OPTION_FUNC(opt_autohide)
+{
+ if (argc > 1) {
+ if (enabled_string(argv[1]))
+ HANDLE_SET_FLAG(hi, AUTOHIDE);
+ else if (disabled_string(argv[1]))
+ HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
+ else {
+ send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
+ return 0;
+ }
+ }
+
+ send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
+ return 1;
+}
+
static OPTION_FUNC(opt_style)
{
char *style;
cryptpass(argv[1], hi->passwd);
send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
+ argv[1] = "****";
+
return 1;
}
saxdb_write_int(ctx, KEY_ID, hi->id);
if (hi->infoline)
saxdb_write_string(ctx, KEY_INFO, hi->infoline);
+ if (hi->devnull)
+ saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull);
+ if (hi->website)
+ saxdb_write_string(ctx, KEY_WEBSITE, hi->website);
if (hi->last_quit_host[0])
saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
flags[0] = hi->userlist_style;
flags[1] = 0;
saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
+ if(hi->authlog) {
+ saxdb_start_record(ctx, KEY_AUTHLOG, 0);
+ struct authlogEntry *authlog;
+ int i = 0;
+ for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+ saxdb_start_record(ctx, strtab(++i), 0);
+ saxdb_write_int(ctx, KEY_AUTHLOG_LOGIN_TIME, authlog->login_time);
+ saxdb_write_int(ctx, KEY_AUTHLOG_LOGOUT_TIME, authlog->logout_time);
+ saxdb_write_string(ctx, KEY_AUTHLOG_HOSTMASK, authlog->hostmask);
+ if(authlog->quit_reason)
+ saxdb_write_string(ctx, KEY_AUTHLOG_QUIT_REASON, authlog->quit_reason);
+ saxdb_end_record(ctx);
+ }
+ saxdb_end_record(ctx); //END KEY_AUTHLOG
+ }
saxdb_end_record(ctx);
}
return 0;
return 1;
}
+#define NICKSERV_DISCRIM_FIELDS_AUTH 0x01
+#define NICKSERV_DISCRIM_FIELDS_EMAIL 0x02
+#define NICKSERV_DISCRIM_FIELDS_SEEN 0x04
+#define NICKSERV_DISCRIM_FIELDS_ACCESS 0x08
+#define NICKSERV_DISCRIM_FIELDS_FAKEHOST 0x10
+#define NICKSERV_DISCRIM_FIELDS_WEBSITE 0x20
+#define NICKSERV_DISCRIM_FIELDS_DEVNULL 0x40
+
+#define NICKSERV_DISCRIM_FIELD_COUNT 7
+
struct nickserv_discrim {
+ unsigned int show_fields;
+ struct helpfile_table *output_table;
+ int output_table_pos;
+ unsigned int output_table_free_fields;
+
unsigned long flags_on, flags_off;
unsigned long min_registered, max_registered;
unsigned long lastseen;
const char *hostmask;
const char *fakehostmask;
const char *fakeidentmask;
+ const char *website;
+ const char *devnullclass;
const char *handlemask;
const char *emailmask;
};
-typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
+typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi, struct nickserv_discrim *discrim);
struct discrim_apply_info {
struct nickserv_discrim *discrim;
discrim->limit = strtoul(argv[++i], NULL, 0);
} else if (!irccasecmp(argv[i], "flags")) {
nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
+ } else if (!irccasecmp(argv[i], "fields")) {
+ char *fields = argv[++i];
+ char *delimiter = strstr(fields, ",");
+ while(1) {
+ if(delimiter)
+ *delimiter = '\0';
+ if(!irccasecmp(fields, "auth"))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_AUTH;
+ else if(!irccasecmp(fields, "email"))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_EMAIL;
+ else if(!irccasecmp(fields, "seen"))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_SEEN;
+ else if(!irccasecmp(fields, "access"))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_ACCESS;
+ else if(!irccasecmp(fields, "fakehost"))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_FAKEHOST;
+ else if(!irccasecmp(fields, "website") && IsBot(user))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_WEBSITE;
+ else if(!irccasecmp(fields, "devnull"))
+ discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_DEVNULL;
+ else {
+ send_message(user, nickserv, "MSG_INVALID_FIELD", fields);
+ goto fail;
+ }
+ if(delimiter) {
+ *delimiter = ',';
+ fields = delimiter+1;
+ if(*fields) {
+ delimiter = strstr(fields, ",");
+ continue;
+ }
+ }
+ break;
+ }
} else if (!irccasecmp(argv[i], "registered")) {
const char *cmp = argv[++i];
if (cmp[0] == '<') {
} else {
discrim->fakeidentmask = argv[i];
}
+ } else if (!irccasecmp(argv[i], "website")) {
+ if (!irccasecmp(argv[++i], "*")) {
+ discrim->website = 0;
+ } else {
+ discrim->website = argv[i];
+ }
+ } else if (!irccasecmp(argv[i], "devnull")) {
+ if (!irccasecmp(argv[++i], "*")) {
+ discrim->devnullclass = 0;
+ } else {
+ discrim->devnullclass = argv[i];
+ }
} else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
if (!irccasecmp(argv[++i], "*")) {
discrim->handlemask = 0;
|| (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
|| (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
|| (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
+ || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website)))
+ || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass)))
|| (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
|| (discrim->min_level > hi->opserv_level)
|| (discrim->max_level < hi->opserv_level)
it = next) {
next = iter_next(it);
if (nickserv_discrim_match(discrim, iter_data(it))) {
- dsf(source, iter_data(it));
+ dsf(source, iter_data(it), discrim);
matched++;
}
}
}
static void
-search_print_func(struct userNode *source, struct handle_info *match)
+search_print_func(struct userNode *source, struct handle_info *match, struct nickserv_discrim *discrim)
{
- send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
+ if(discrim->show_fields) {
+ //custom fields
+ if(discrim->output_table) {
+ discrim->output_table->contents[++discrim->output_table_pos] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
+ int i = 0;
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
+ discrim->output_table->contents[discrim->output_table_pos][i++] = match->handle;
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
+ discrim->output_table->contents[discrim->output_table_pos][i++] = (match->email_addr ? match->email_addr : "*");
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN) {
+ char *seen;
+ char seenBuf[INTERVALLEN];
+ if(match->users) {
+ seen = "Here";
+ } else if(match->lastseen == 0) {
+ seen = "Never";
+ } else {
+ seen = intervalString(seenBuf, now - match->lastseen, source->handle_info);
+ }
+ discrim->output_table_free_fields |= 1 << i;
+ discrim->output_table->contents[discrim->output_table_pos][i++] = strdup(seen);
+ }
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
+ discrim->output_table->contents[discrim->output_table_pos][i++] = strtab(match->opserv_level);
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
+ discrim->output_table->contents[discrim->output_table_pos][i++] = (match->fakehost ? match->fakehost : "*");
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
+ discrim->output_table->contents[discrim->output_table_pos][i++] = (match->website ? match->website : "*");
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
+ discrim->output_table->contents[discrim->output_table_pos][i++] = (match->devnull ? match->devnull : "*");
+
+ }
+ } else
+ send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
}
static void
-search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
+search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match), UNUSED_ARG(struct nickserv_discrim *discrim))
{
}
static void
-search_unregister_func (struct userNode *source, struct handle_info *match)
+search_unregister_func (struct userNode *source, struct handle_info *match, UNUSED_ARG(struct nickserv_discrim *discrim))
{
if (oper_has_access(source, nickserv, match->opserv_level, 0))
nickserv_unregister_handle(match, source);
discrim->limit = INT_MAX;
matches = nickserv_discrim_search(discrim, action, user);
-
+
+ if(discrim->show_fields) {
+ int width = 0;
+ int ii;
+ for(ii = 0; ii < NICKSERV_DISCRIM_FIELD_COUNT; ii++) {
+ if(discrim->show_fields & (1 << ii)) width++;
+ }
+ discrim->output_table = malloc(sizeof(discrim->output_table[0]));
+ discrim->output_table->length = matches+1;
+ discrim->output_table->width = width;
+ discrim->output_table->flags = TABLE_NO_FREE;
+ discrim->output_table->contents = malloc(discrim->output_table->length * sizeof(discrim->output_table->contents[0]));
+ discrim->output_table->contents[0] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
+
+ ii = 0;
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
+ discrim->output_table->contents[0][ii++] = "Auth";
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
+ discrim->output_table->contents[0][ii++] = "EMail";
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN)
+ discrim->output_table->contents[0][ii++] = "Seen";
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
+ discrim->output_table->contents[0][ii++] = "Access";
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
+ discrim->output_table->contents[0][ii++] = "Fakehost";
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
+ discrim->output_table->contents[0][ii++] = "Website";
+ if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
+ discrim->output_table->contents[0][ii++] = "DevNull";
+
+ nickserv_discrim_search(discrim, action, user);
+
+ table_send(nickserv, user->nick, 0, NULL, *discrim->output_table);
+
+ for(ii = 1; ii < discrim->output_table->length; ++ii) {
+ int ij;
+ for(ij = 0; ij < NICKSERV_DISCRIM_FIELD_COUNT; ij++) {
+ if(discrim->output_table_free_fields & (1 << ij))
+ free((char*)discrim->output_table->contents[ii][ij]);
+ }
+ free(discrim->output_table->contents[ii]);
+ }
+ free(discrim->output_table->contents[0]);
+ free(discrim->output_table->contents);
+ free(discrim->output_table);
+ }
if (matches)
reply("MSG_MATCH_COUNT", matches);
else
reply("MSG_NO_MATCHES");
+
free(discrim);
return 0;
}
return 1;
}
+static int
+nickserv_db_read_authlog(UNUSED_ARG(const char *key), void *data, void *extra)
+{
+ struct record_data *rd = data;
+ struct handle_info *hi = extra;
+ const char *str;
+ struct authlogEntry *authlog;
+ authlog = malloc(sizeof(*authlog));
+
+ str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGIN_TIME, RECDB_QSTRING);
+ authlog->login_time = str ? strtoul(str, NULL, 0) : 0;
+
+ str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGOUT_TIME, RECDB_QSTRING);
+ authlog->logout_time = str ? strtoul(str, NULL, 0) : 0;
+
+ str = database_get_data(rd->d.object, KEY_AUTHLOG_HOSTMASK, RECDB_QSTRING);
+ authlog->hostmask = str ? strdup(str) : NULL;
+
+ str = database_get_data(rd->d.object, KEY_AUTHLOG_QUIT_REASON, RECDB_QSTRING);
+ authlog->quit_reason = str ? strdup(str) : NULL;
+
+ authlog->user = NULL;
+
+ authlog->next = NULL;
+
+ //append it to the end of the list...
+ struct authlogEntry *authlog_entry;
+ if(!hi->authlog) {
+ hi->authlog = authlog;
+ } else {
+ for(authlog_entry = hi->authlog; authlog_entry; authlog_entry = authlog_entry->next) {
+ if(!authlog_entry->next) {
+ authlog_entry->next = authlog;
+ break;
+ }
+ }
+ }
+ return 0;
+}
static void
nickserv_db_read_handle(const char *handle, dict_t obj)
str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
if (str)
hi->infoline = strdup(str);
+ str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING);
+ if (str)
+ hi->website = strdup(str);
+ str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING);
+ if (str)
+ hi->devnull = strdup(str);
str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
hi->registered = str ? strtoul(str, NULL, 0) : now;
str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
last_note = note;
}
}
+ if ((subdb = database_get_data(obj, KEY_AUTHLOG, RECDB_OBJECT)))
+ dict_foreach(subdb, nickserv_db_read_authlog, hi);
}
static int
nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
+ str = database_get_data(conf_node, KEY_MAX_AUTHLOG_LEN, RECDB_QSTRING);
+ nickserv_conf.max_authlog_len = str ? strtoul(str, NULL, 0) : 30;
str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
static void
check_user_nick(struct userNode *user) {
+ //check if this user is a pending LOC user
+ if(pendingLOCUsers) {
+ struct pendingLOCUser *pending, *next, *prev = NULL;
+ for(pending = pendingLOCUsers; pending; pending = next) {
+ next = pending->next;
+ if(user->handle_info == pending->handle_info) {
+ pending->authlog->user = user;
+ free((char*) pending->authlog->hostmask);
+ pending->authlog->hostmask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
+ if(prev)
+ prev->next = next;
+ else
+ pendingLOCUsers = next;
+ free(pending);
+ }
+ if(now - pending->time > 10) {
+ if(prev)
+ prev->next = next;
+ else
+ pendingLOCUsers = next;
+ free(pending);
+ }
+ }
+ }
struct nick_info *ni;
user->modes &= ~FLAGS_REGNICK;
if (!(ni = get_nick_info(user->nick)))
}
void
-nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
+nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), const char *why)
{
+ if(user->handle_info) {
+ //check if theres an open authlog entry
+ struct authlogEntry *authlog;
+ for(authlog = user->handle_info->authlog; authlog; authlog = authlog->next) {
+ if(authlog->user == user) {
+ authlog->user = NULL;
+ authlog->logout_time = now;
+ authlog->quit_reason = strdup(why);
+ break;
+ }
+ }
+ }
dict_remove(nickserv_allow_auth_dict, user->nick);
timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
set_user_handle_info(user, NULL, 0);
regfree(&nickserv_conf.valid_handle_regex);
if (nickserv_conf.valid_nick_regex_set)
regfree(&nickserv_conf.valid_nick_regex);
+ struct pendingLOCUser *pending, *next;
+ for(pending = pendingLOCUsers; pending; pending = next) {
+ next = pending->next;
+ free(pending);
+ }
+ pendingLOCUsers = NULL;
}
void
nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
+ nickserv_define_func("AUTHLOG", cmd_authlog, -1, 1, 0);
+ nickserv_define_func("OAUTHLOG", cmd_oauthlog, 0, 1, 0);
/* other options */
dict_insert(nickserv_opt_dict, "INFO", opt_info);
dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
dict_insert(nickserv_opt_dict, "COLOR", opt_color);
dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
dict_insert(nickserv_opt_dict, "STYLE", opt_style);
+ dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
dict_insert(nickserv_opt_dict, "PASS", opt_password);
dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
+ dict_insert(nickserv_opt_dict, "WEBSITE", opt_website);
+ dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull);
dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
#define HI_FLAG_NODELETE 0x00000080
#define HI_FLAG_NETWORK_HELPER 0x00000100
#define HI_FLAG_BOT 0x00000200
+#define HI_FLAG_AUTOHIDE 0x00000400
+#define HI_FLAG_INVI 0x00000800
+
/* Flag characters for the above. First char is LSB, etc. */
-#define HANDLE_FLAGS "SphgscfnHb"
+#define HANDLE_FLAGS "SphgscfnHbxI"
/* HI_STYLE_* go into handle_info.userlist_style */
#define HI_STYLE_DEF 'd'
#define IsHelping(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, HELPING))
#define IsStaff(user) (IsOper(user) || IsSupportHelper(user) || IsNetworkHelper(user))
#define IsBot(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+#define IsInvi(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, INVI))
enum cookie_type {
ACTIVATION,
char note[1];
};
+struct authlogEntry {
+ unsigned long login_time;
+ unsigned long logout_time;
+ const char *hostmask;
+ const char *quit_reason;
+ struct userNode *user;
+ struct authlogEntry *next;
+};
+
struct handle_info {
struct nick_info *nicks;
struct string_list *masks;
struct handle_cookie *cookie;
struct handle_note *notes;
struct language *language;
+ struct authlogEntry *authlog;
+ char *website;
+ char *devnull;
char *email_addr;
char *epithet;
char *infoline;
int nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *add, unsigned long *remove);
int oper_has_access(struct userNode *user, struct userNode *bot, unsigned int min_level, unsigned int quiet);
void nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd);
+struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask);
+char *getfakehost(const char *user);
+void nickserv_devnull_delete(char *name);
+void nickserv_devnull_rename(char *oldname, char *newname);
/* auth_funcs are called when a user gets a new handle_info. They are
* called *after* user->handle_info has been updated. */
"$bc$b Use mIRC color codes in responses",
"$bf$b Account frozen/on vacation (will not be unregistered for inactivity; cleared when account is authenticated against)",
"$bn$b No-delete (will never be unregistered for inactivity)",
+ "$bb$b Bot",
+ "$bI$b Invisible (user will not be shown in staff list)",
"$uSee Also:$u accountinfo, set");
"OADDMASK" ("/msg $N OADDMASK <nick|*account> <user@host>",
"Adds a hostmask to the specified account.",
#include "gline.h"
#include "global.h"
#include "nickserv.h"
+#include "chanserv.h"
#include "modcmd.h"
#include "opserv.h"
#include "timeq.h"
#define KEY_EXPIRES "expires"
#define KEY_STAFF_AUTH_CHANNEL "staff_auth_channel"
#define KEY_STAFF_AUTH_CHANNEL_MODES "staff_auth_channel_modes"
+#define KEY_STAFF_AUTH_FORCE_OPS "staff_auth_force_opers"
#define KEY_CLONE_GLINE_DURATION "clone_gline_duration"
#define KEY_BLOCK_GLINE_DURATION "block_gline_duration"
#define KEY_ISSUER "issuer"
#define KEY_ISSUED "issued"
+#define KEY_DEVNULL_CLASSES "classes"
+#define KEY_DEVNULL_NAME "class"
+#define KEY_DEVNULL_MODE "modes"
+#define KEY_DEVNULL_MAXCHAN "chanlimit"
+#define KEY_DEVNULL_MAXSENDQ "sendq"
#define IDENT_FORMAT "%s [%s@%s/%s]"
#define IDENT_DATA(user) user->nick, user->ident, user->hostname, irc_ntoa(&user->ip)
{ "OSMSG_CSEARCH_CHANNEL_INFO", "%s [%d users] %s %s" },
{ "OSMSG_TRACE_MAX_CHANNELS", "You may not use the 'channel' criterion more than %d times." },
{ "OSMSG_FORCEKICK_LOCAL", "You cannot kick $b%s$b forcefully." },
+ { "OSMSG_SVSNONICK", "$b%s$b is not a valid nick." },
+ { "OSMSG_SVSNICKUSED", "$b%s$b is an already used nickname." },
+ { "OSMSG_SVSNICK", "You have renamed $b%s$b to $b%s$b." },
+ { "OSMSG_SVSJOIN", "$b%s$b joined $b%s$b." },
+ { "OSMSG_SVSMODE", "You have set mode $b%s$b for $b%s$b." },
+ { "OSMSG_SIMUL", "You have simuled $b%s$b: %s" },
+ { "OSMSG_DEVNULL_USER" , "[%s] %s %s" },
+ { "OSMSG_DEVNULL_MATCH" , "%d Users found." },
+ { "OSMSG_DEVNULL_CLASS" , "%s is not a valid DevNull class." },
+ { "OSMSG_DEVNULL_ADDED", "Added %s to DevNull list (class: %s)" },
+ { "OSMSG_DEVNULL_DELETED", "Deleted %s from DevNull list (class: %s)" },
+ { "OSMSG_DEVNULL_NOTADDED", "User %s is not listed on DevNull list." },
+ { "OSMSG_DEVNULL_ACTION", "Unrecognized trace action $b%s$b" },
+ { "OSMSG_DEVNULL_FOUND", "DevNull Class %s is already existing." },
+ { "OSMSG_DEVNULL_NOTFOUND", "can't find DevNull class %s." },
+ { "OSMSG_DEVNULL_ADDED", "DevNull Class %s added." },
+ { "OSMSG_DEVNULL_REMOVED", "DevNull Class %s removed." },
+ { "OSMSG_DEVNULL_SET", "Settings for DevNull Class %s" },
+ { "OSMSG_DEVNULL_SET_A", "ChanLimit: %s" },
+ { "OSMSG_DEVNULL_SET_A_i", "ChanLimit: %i" },
+ { "OSMSG_DEVNULL_SET_B", "UnlimitTarget: %s" },
+ { "OSMSG_DEVNULL_SET_C", "Flood: %s" },
+ { "OSMSG_DEVNULL_SET_E", "ChanHide: %s" },
+ { "OSMSG_DEVNULL_SET_F", "IdleHide: %s" },
+ { "OSMSG_DEVNULL_SET_G", "ChServMode: %s" },
+ { "OSMSG_DEVNULL_SET_H", "XtraOpMode: %s" },
+ { "OSMSG_DEVNULL_SET_I", "NetServMode: %s" },
+ { "OSMSG_DEVNULL_SET_J", "SeeIdle: %s" },
+ { "OSMSG_DEVNULL_SET_K", "ForceIdleHide: %s" },
+ { "OSMSG_DEVNULL_SET_L", "OverrideCC: %s" },
+ { "OSMSG_DEVNULL_SET_M", "OverrideNoAmsg: %s" },
+ { "OSMSG_DEVNULL_SET_N", "MaxSendQ: %s" },
+ { "OSMSG_DEVNULL_SET_N_i", "MaxSendQ: %i" },
+ { "OSMSG_DEVNULL_SET_OPME", "OpMe: %s" },
+ { "OSMSG_DEVNULL_SET_DONE", "Done." },
+ { "OSMSG_DEVNULL_RENAMED", "Devnull class %s renamed to %s" },
+ { "OSMSG_DEVNULL_SET_INVALID", "Invalid Option for setting %s" },
{ NULL, NULL }
};
static struct string_list *opserv_bad_words;
static dict_t opserv_exempt_channels; /* data is not used */
static dict_t opserv_trusted_hosts; /* data is struct trusted_host* */
+static dict_t opserv_devnull_classes; /* data is struct devnull_class* */
static dict_t opserv_hostinfo_dict; /* data is struct opserv_hostinfo* */
static dict_t opserv_user_alerts; /* data is struct opserv_user_alert* */
static dict_t opserv_nick_based_alerts; /* data is struct opserv_user_alert* */
static struct module *opserv_module;
static struct log_type *OS_LOG;
static unsigned int new_user_flood;
+const char *devnull_modes = DEVNULL_MODES;
static char *level_strings[1001];
+static char devnull_inverse_modes[256];
static struct {
struct chanNode *debug_channel;
struct chanNode *alert_channel;
struct chanNode *staff_auth_channel;
+ int staff_auth_force;
struct policer_params *join_policer_params;
struct policer new_user_policer;
unsigned long untrusted_max;
static MODCMD_FUNC(cmd_mode)
{
- if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE)) {
+ if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE|MCP_OPERMODE)) {
reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
return 0;
}
reply("OSMSG_WHOIS_ACCOUNT", (target->handle_info ? target->handle_info->handle : "Not authenticated"));
intervalString(buffer, now - target->timestamp, user->handle_info);
reply("OSMSG_WHOIS_NICK_AGE", buffer);
- if (target->channels.used <= MAX_CHANNELS_WHOIS)
+ if (target->channels.used <= MAX_CHANNELS_WHOIS || HANDLE_FLAGGED(user->handle_info, BOT))
opserv_ison(user, target, "OSMSG_WHOIS_CHANNELS");
else
reply("OSMSG_WHOIS_HIDECHANS");
return 0;
}
+static void
+opserv_add_devnull_class(const char *name, unsigned long modes, unsigned long maxchan, unsigned long maxsendq)
+{
+ struct devnull_class *th;
+ th = calloc(1, sizeof(*th));
+ if (!th)
+ return;
+ th->name = strdup(name);
+ th->modes = modes;
+ th->maxchan = maxchan;
+ th->maxsendq = maxsendq;
+ dict_insert(opserv_devnull_classes, th->name, th);
+}
+
+static void
+free_devnull_class(void *data)
+{
+ struct devnull_class *th = data;
+ free(th->name);
+ free(th);
+}
+
+static int
+devnull_class_read(const char *name, void *data, UNUSED_ARG(void *extra))
+{
+ struct record_data *rd = data;
+ const char *nameb = name, *str;
+ unsigned long modes, maxchan, maxsendq;
+ unsigned int ii;
+
+ if (rd->type == RECDB_OBJECT) {
+ dict_t obj = GET_RECORD_OBJECT(rd);
+ /* new style structure */
+ nameb = database_get_data(obj, KEY_DEVNULL_NAME, RECDB_QSTRING);
+ str = database_get_data(obj, KEY_DEVNULL_MODE, RECDB_QSTRING);
+ modes = 0;
+ if (str) {
+ for (ii=0; str[ii]; ii++)
+ modes |= 1 << (devnull_inverse_modes[(unsigned char)str[ii]] - 1);
+ }
+ str = database_get_data(obj, KEY_DEVNULL_MAXCHAN, RECDB_QSTRING);
+ maxchan = str ? ParseInterval(str) : 0;
+ str = database_get_data(obj, KEY_DEVNULL_MAXSENDQ, RECDB_QSTRING);
+ maxsendq = str ? ParseInterval(str) : 0;
+ } else
+ return 0;
+
+ opserv_add_devnull_class(nameb, modes, maxchan, maxsendq);
+ return 0;
+}
+
static int
opserv_saxdb_read(struct dict *conf_db)
{
}
if ((object = database_get_data(conf_db, KEY_TRUSTED_HOSTS, RECDB_OBJECT)))
dict_foreach(object, trusted_host_read, opserv_trusted_hosts);
+ if ((object = database_get_data(conf_db, KEY_DEVNULL_CLASSES, RECDB_OBJECT)))
+ dict_foreach(object, devnull_class_read, opserv_devnull_classes);
if ((object = database_get_data(conf_db, KEY_GAGS, RECDB_OBJECT)))
dict_foreach(object, add_gag_helper, NULL);
if ((object = database_get_data(conf_db, KEY_ALERTS, RECDB_OBJECT)))
}
saxdb_end_record(ctx);
}
+ /* devnull_classes */
+ if (dict_size(opserv_devnull_classes)) {
+ saxdb_start_record(ctx, KEY_DEVNULL_CLASSES, 1);
+ for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) {
+ struct devnull_class *th = iter_data(it);
+ saxdb_start_record(ctx, iter_key(it), 0);
+ if (th->name) saxdb_write_string(ctx, KEY_DEVNULL_NAME, th->name);
+ if (th->modes) {
+ int ii, flen;
+ char flags[50];
+ for (ii=flen=0; devnull_modes[ii]; ++ii)
+ if (th->modes & (1 << ii))
+ flags[flen++] = devnull_modes[ii];
+ flags[flen] = 0;
+ saxdb_write_string(ctx, KEY_DEVNULL_MODE, flags);
+ }
+ if (th->maxchan) saxdb_write_int(ctx, KEY_DEVNULL_MAXCHAN, th->maxchan);
+ if (th->maxsendq) saxdb_write_int(ctx, KEY_DEVNULL_MAXSENDQ, th->maxsendq);
+ saxdb_end_record(ctx);
+ }
+ saxdb_end_record(ctx);
+ }
/* gags */
if (gagList) {
struct gag_entry *gag;
|| user->uplink->burst
|| !user->handle_info)
return;
+ else if (IsBot(user))
+ return;
else if (user->handle_info->opserv_level)
type = "OPER";
else if (IsNetworkHelper(user))
return;
if (irc_in_addr_is_valid(user->ip))
- send_channel_notice(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle);
+ send_channel_message(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle);
else
- send_channel_notice(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle);
+ send_channel_message(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle);
}
static void
return 1;
}
+static MODCMD_FUNC(cmd_listdevnull)
+{
+ struct helpfile_table tbl;
+ unsigned int count = 0, ii = 0;
+ char *on,*off,*half;
+ on = "X";
+ off = "-";
+ half = "1/2";
+
+ dict_iterator_t it;
+ for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) {
+ count++;
+ }
+ tbl.length = count+1;
+ tbl.width = 15;
+ tbl.flags = 0;
+ tbl.flags = TABLE_NO_FREE;
+ tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+ tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[0][0] = "Name";
+ tbl.contents[0][1] = "MC";
+ tbl.contents[0][2] = "UT";
+ tbl.contents[0][3] = "FL";
+ tbl.contents[0][4] = "CH";
+ tbl.contents[0][5] = "IH";
+ tbl.contents[0][6] = "SI";
+ tbl.contents[0][7] = "IH²";
+ tbl.contents[0][8] = "oC";
+ tbl.contents[0][9] = "oM";
+ tbl.contents[0][10] = "+k";
+ tbl.contents[0][11] = "+S";
+ tbl.contents[0][12] = "+X";
+ tbl.contents[0][13] = "MaxQ";
+ tbl.contents[0][14] = "OpMe";
+ if(!count)
+ {
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ reply("MSG_NONE");
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ return 0;
+ }
+ for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) {
+ struct devnull_class *th = iter_data(it);
+ tbl.contents[++ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+ tbl.contents[ii][0] = th->name;
+ if(DEVNULL_FLAGGED(th, MODE_A)) {
+ tbl.contents[ii][1] = strtab(th->maxchan);
+ } else {
+ tbl.contents[ii][1] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_B)) {
+ tbl.contents[ii][2] = on;
+ } else {
+ tbl.contents[ii][2] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_D)) {
+ tbl.contents[ii][3] = on;
+ } else if(DEVNULL_FLAGGED(th, MODE_C)) {
+ tbl.contents[ii][3] = half;
+ } else {
+ tbl.contents[ii][3] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_E)) {
+ tbl.contents[ii][4] = on;
+ } else {
+ tbl.contents[ii][4] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_F)) {
+ tbl.contents[ii][5] = on;
+ } else {
+ tbl.contents[ii][5] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_J)) {
+ tbl.contents[ii][6] = on;
+ } else {
+ tbl.contents[ii][6] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_K)) {
+ tbl.contents[ii][7] = on;
+ } else {
+ tbl.contents[ii][7] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_L)) {
+ tbl.contents[ii][8] = on;
+ } else {
+ tbl.contents[ii][8] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_M)) {
+ tbl.contents[ii][9] = on;
+ } else {
+ tbl.contents[ii][9] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_G)) {
+ tbl.contents[ii][10] = on;
+ } else {
+ tbl.contents[ii][10] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_I)) {
+ tbl.contents[ii][11] = on;
+ } else {
+ tbl.contents[ii][11] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_H)) {
+ tbl.contents[ii][12] = on;
+ } else {
+ tbl.contents[ii][12] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_N)) {
+ tbl.contents[ii][13] = on;
+ } else {
+ tbl.contents[ii][13] = off;
+ }
+ if(DEVNULL_FLAGGED(th, MODE_OPME)) {
+ tbl.contents[ii][14] = on;
+ } else {
+ tbl.contents[ii][14] = off;
+ }
+ }
+ table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+ for(ii = 1; ii < tbl.length; ++ii)
+ {
+ free(tbl.contents[ii]);
+ }
+ free(tbl.contents[0]);
+ free(tbl.contents);
+ send_message_type(4, user, opserv,"-");
+ /*
+ tbl.contents[0][1] = "MC";
+ tbl.contents[0][2] = "UT";
+ tbl.contents[0][3] = "FL";
+ tbl.contents[0][4] = "CH";
+ tbl.contents[0][5] = "IH";
+ tbl.contents[0][6] = "SI";
+ tbl.contents[0][7] = "IH²";
+ tbl.contents[0][8] = "oC";
+ tbl.contents[0][9] = "oM";
+ tbl.contents[0][10] = "+k";
+ tbl.contents[0][11] = "+S";
+ tbl.contents[0][12] = "+X";
+ tbl.contents[0][13] = "MaxQ";
+ */
+ send_message_type(4, user, opserv,"MC = MaxChan");
+ send_message_type(4, user, opserv,"UT = UnlimitTargets");
+ send_message_type(4, user, opserv,"FL = Flood (1 = 1/2 flood, X = unlimited)");
+ send_message_type(4, user, opserv,"CH = Channel Hide (mode +n)");
+ send_message_type(4, user, opserv,"IH = Idle Hide (mode +I)");
+ send_message_type(4, user, opserv,"SI = See Idle");
+ send_message_type(4, user, opserv,"IH² = Idle Hide (override SI)");
+ send_message_type(4, user, opserv,"oC = Color override (mode +c)");
+ send_message_type(4, user, opserv,"oM = AMsg override");
+ send_message_type(4, user, opserv,"+k = ChanServ Mode (mode +k)");
+ send_message_type(4, user, opserv,"+S = NetServ Mode (mode +S)");
+ send_message_type(4, user, opserv,"+X = XtraOp Mode (mode +X)");
+ send_message_type(4, user, opserv,"OpMe = ChanServ opme command");
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_adddevnull)
+{
+
+ if (dict_find(opserv_devnull_classes, argv[1], NULL)) {
+ reply("OSMSG_DEVNULL_FOUND", argv[1]);
+ return 0;
+ }
+
+ opserv_add_devnull_class(argv[1], 0, 0, 0);
+ reply("OSMSG_DEVNULL_ADDED",argv[1]);
+ return 1;
+}
+
+
+static MODCMD_FUNC(cmd_deldevnull)
+{
+ unsigned int n;
+
+ for (n=1; n<argc; n++) {
+ struct devnull_class *th = dict_find(opserv_devnull_classes, argv[n], NULL);
+ if (!th)
+ continue;
+ nickserv_devnull_delete(th->name);
+ dict_remove(opserv_devnull_classes, argv[n]);
+ reply("OSMSG_DEVNULL_REMOVED",argv[n]);
+ }
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_renamedevnull)
+{
+ struct devnull_class *th;
+ if (dict_find(opserv_devnull_classes, argv[2], NULL)) {
+ reply("OSMSG_DEVNULL_FOUND", argv[2]);
+ return 0;
+ }
+ if ((th = dict_find(opserv_devnull_classes, argv[1], NULL))) {
+ opserv_add_devnull_class(argv[2], th->modes, th->maxchan, th->maxsendq);
+ nickserv_devnull_rename(th->name,argv[2]);
+ dict_remove(opserv_devnull_classes, argv[1]);
+ reply("OSMSG_DEVNULL_RENAMED",argv[1],argv[2]);
+ } else {
+ reply("OSMSG_DEVNULL_NOTFOUND", argv[1]);
+ return 0;
+ }
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_setdevnull)
+{
+ struct devnull_class *th;
+ unsigned int offset=0;
+ if ((th = dict_find(opserv_devnull_classes, argv[offset+1], NULL))) {
+ if (argc > 3) {
+ unsigned int ii;
+ for( ii = 0; argv[offset+2][ ii ]; ii++)
+ argv[offset+2][ ii ] = toupper( argv[offset+2][ ii ] );
+ for( ii = 0; argv[offset+3][ ii ]; ii++)
+ argv[offset+3][ ii ] = toupper( argv[offset+3][ ii ] );
+ if(!strcmp("CHANLIMIT",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_A);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ DEVNULL_SET_FLAG(th, MODE_A);
+ th->maxchan = strtoul(argv[offset+3], NULL, 0);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ }
+ }
+ else if(!strcmp("UNLIMITTARGET",argv[offset+2]) || !strcmp("UNLIMITEDTARGET",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_B);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_B);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("FLOOD",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_C);
+ DEVNULL_CLEAR_FLAG(th, MODE_D);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_D);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("HALF",argv[offset+3]) || !strcmp("2",argv[offset+3]) || !strcmp("1/2",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_C);
+ DEVNULL_CLEAR_FLAG(th, MODE_D);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("CHANHIDE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_E);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_E);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("IDLEHIDE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_F);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_F);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("CHSERVMODE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_G);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_G);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("XTRAOPMODE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_H);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_H);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("NETSERVMODE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_I);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_I);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("SEEIDLE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_J);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_J);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("FORCEIDLEHIDE",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_K);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_K);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("OVERRIDECC",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_L);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_L);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("OVERRIDENOAMSG",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_M);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_M);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ }
+ else if(!strcmp("MAXSENDQ",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_N);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ DEVNULL_SET_FLAG(th, MODE_N);
+ th->maxsendq = strtoul(argv[offset+3], NULL, 0);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ }
+ }
+ else if(!strcmp("OPME",argv[offset+2])) {
+ if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+ DEVNULL_CLEAR_FLAG(th, MODE_OPME);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+ DEVNULL_SET_FLAG(th, MODE_OPME);
+ reply("OSMSG_DEVNULL_SET_DONE");
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+ }
+ } else {
+ reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+2]);
+ }
+
+ } else {
+ reply("OSMSG_DEVNULL_SET", th->name);
+ if(DEVNULL_FLAGGED(th, MODE_A)) {
+ reply("OSMSG_DEVNULL_SET_A_i", th->maxchan);
+ } else {
+ reply("OSMSG_DEVNULL_SET_A", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_B)) {
+ reply("OSMSG_DEVNULL_SET_B", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_B", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_D)) {
+ reply("OSMSG_DEVNULL_SET_C", "on");
+ } else if(DEVNULL_FLAGGED(th, MODE_C)) {
+ reply("OSMSG_DEVNULL_SET_C", "half");
+ } else {
+ reply("OSMSG_DEVNULL_SET_C", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_E)) {
+ reply("OSMSG_DEVNULL_SET_E", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_E", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_F)) {
+ reply("OSMSG_DEVNULL_SET_F", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_F", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_G)) {
+ reply("OSMSG_DEVNULL_SET_G", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_G", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_H)) {
+ reply("OSMSG_DEVNULL_SET_H", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_H", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_I)) {
+ reply("OSMSG_DEVNULL_SET_I", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_I", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_J)) {
+ reply("OSMSG_DEVNULL_SET_J", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_J", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_K)) {
+ reply("OSMSG_DEVNULL_SET_K", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_K", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_L)) {
+ reply("OSMSG_DEVNULL_SET_L", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_L", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_M)) {
+ reply("OSMSG_DEVNULL_SET_M", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_M", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_N)) {
+ reply("OSMSG_DEVNULL_SET_N_i", th->maxsendq);
+ } else {
+ reply("OSMSG_DEVNULL_SET_N", "off");
+ }
+ if(DEVNULL_FLAGGED(th, MODE_OPME)) {
+ reply("OSMSG_DEVNULL_SET_OPME", "on");
+ } else {
+ reply("OSMSG_DEVNULL_SET_OPME", "off");
+ }
+ }
+ } else {
+ reply("OSMSG_DEVNULL_NOTFOUND", argv[offset+1]);
+ return 0;
+ }
+ return 1;
+}
+
+int devnull_check(const char *name) {
+ if (dict_find(opserv_devnull_classes, name, NULL)) {
+ return 1;
+ }
+ return 0;
+}
+
+struct devnull_class*
+ devnull_get(const char *name) {
+ return dict_find(opserv_devnull_classes, name, NULL);
+}
+
+void operpart(struct chanNode *chan, struct userNode *user)
+{
+ if(opserv_conf.alert_channel && opserv_conf.staff_auth_force > 0 &&
+ !(irccasecmp(chan->name,opserv_conf.alert_channel->name))) {
+ struct mod_chanmode *change;
+ change = find_matching_bans(&chan->banlist, user, NULL); //don't join them if they're banned (exceptions from forced join)
+ if(change)
+ return;
+ irc_svsjoin(opserv,user,chan);
+ }
+}
+
+void operadd(struct userNode *user)
+{
+ if(opserv_conf.alert_channel && opserv_conf.staff_auth_force > 0)
+ irc_svsjoin(opserv,user,opserv_conf.alert_channel);
+}
+
+void operdel(struct userNode *user)
+{
+ if(opserv_conf.alert_channel && opserv_conf.staff_auth_force == 2)
+ irc_kick(opserv, user, opserv_conf.alert_channel, "mode -o");
+}
+
+static MODCMD_FUNC(cmd_svsjoin)
+{
+ struct userNode *target;
+ if(!(target=GetUserH(argv[1]))) {
+ reply("OSMSG_SVSNONICK", argv[1]);
+ return 0;
+ }
+ if(!IsChannelName(argv[2]))
+ {
+ reply("MSG_NOT_CHANNEL_NAME");
+ return 0;
+ }
+ irc_svsjoinchan(opserv,target,argv[2]);
+ reply("OSMSG_SVSJOIN",target->nick,argv[2]);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_svsnick)
+{
+ struct userNode *target;
+ if(!(target=GetUserH(argv[1]))) {
+ reply("OSMSG_SVSNONICK", argv[1]);
+ return 0;
+ }
+ if(GetUserH(argv[2]))
+ {
+ reply("OSMSG_SVSNICKUSED",argv[2]);
+ return 0;
+ }
+ irc_svsnick(opserv,target,argv[2]);
+ reply("OSMSG_SVSNICK",target->nick,argv[2]);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_svsmode)
+{
+ struct userNode *target;
+ char *modestr;
+ if(!(target=GetUserH(argv[1]))) {
+ reply("OSMSG_SVSNONICK", argv[1]);
+ return 0;
+ }
+ modestr = unsplit_string(argv + 2, argc - 2, NULL);
+ irc_svsmode(opserv,target,modestr);
+ reply("OSMSG_SVSMODE",modestr,target->nick);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_simul)
+{
+ struct userNode *target;
+ char *line;
+ if(!(target=GetUserH(argv[1]))) {
+ reply("OSMSG_SVSNONICK", argv[1]);
+ return 0;
+ }
+ line = unsplit_string(argv + 2, argc - 2, NULL);
+ irc_simul(target,line);
+ reply("OSMSG_SIMUL",target->nick,line);
+ return 1;
+}
+
+static MODCMD_FUNC(cmd_relay)
+{
+ struct userNode *target;
+ char *line;
+ if(!(target=GetUserH(argv[1]))) {
+ reply("OSMSG_SVSNONICK", argv[1]);
+ return 0;
+ }
+ line = unsplit_string(argv + 2, argc - 2, NULL);
+ char sendline[512];
+ if(channel)
+ sprintf(sendline, "relay %s %s :%s",user->nick,channel->name,line);
+ else
+ sprintf(sendline, "relay %s query :%s",user->nick,line);
+ irc_privmsg(opserv,target->numeric,sendline);
+ return 1;
+}
+
static void
opserv_conf_read(void)
{
str2 = "+timns";
opserv_conf.staff_auth_channel = AddChannel(str, now, str2, NULL);
AddChannelUser(opserv, opserv_conf.staff_auth_channel)->modes |= MODE_CHANOP;
+ str2 = database_get_data(conf_node, KEY_STAFF_AUTH_FORCE_OPS, RECDB_QSTRING);
+ opserv_conf.staff_auth_force = str2 ? atoi(str2) : 0;
} else {
opserv_conf.staff_auth_channel = NULL;
+ opserv_conf.staff_auth_force = 0;
}
str = database_get_data(conf_node, KEY_UNTRUSTED_MAX, RECDB_QSTRING);
opserv_conf.untrusted_max = str ? strtoul(str, NULL, 0) : 5;
dict_delete(opserv_trusted_hosts);
opserv_trusted_hosts = dict_new();
dict_set_free_data(opserv_trusted_hosts, free_trusted_host);
+ /* set up opserv_devnull_classes dict */
+ dict_delete(opserv_devnull_classes);
+ opserv_devnull_classes = dict_new();
+ dict_set_free_data(opserv_devnull_classes, free_devnull_class);
/* set up opserv_chan_warn dict */
dict_delete(opserv_chan_warn);
opserv_chan_warn = dict_new();
free_string_list(opserv_bad_words);
dict_delete(opserv_exempt_channels);
dict_delete(opserv_trusted_hosts);
+ dict_delete(opserv_devnull_classes);
unreg_del_user_func(opserv_user_cleanup);
dict_delete(opserv_hostinfo_dict);
dict_delete(opserv_nick_based_alerts);
}
conf_register_reload(opserv_conf_read);
+ unsigned int i;
+ /* set up handle_inverse_flags */
+ memset(devnull_inverse_modes, 0, sizeof(devnull_inverse_modes));
+ for (i=0; devnull_modes[i]; i++) {
+ devnull_inverse_modes[(unsigned char)devnull_modes[i]] = i + 1;
+ }
+
memset(level_strings, 0, sizeof(level_strings));
opserv_module = module_register("OpServ", OS_LOG, "opserv.help", opserv_help_expand);
opserv_define_func("ACCESS", cmd_access, 0, 0, 0);
#if defined(WITH_MALLOC_SRVX) || defined(WITH_MALLOC_SLAB)
opserv_define_func("STATS MEMORY", cmd_stats_memory, 0, 0, 0);
#endif
+ opserv_define_func("DEVNULL ADD", cmd_adddevnull, 200, 0, 2);
+ opserv_define_func("DEVNULL DEL", cmd_deldevnull, 200, 0, 2);
+ opserv_define_func("DEVNULL RENAME", cmd_renamedevnull, 200, 0, 3);
+ opserv_define_func("DEVNULL SET", cmd_setdevnull, 200, 0, 2);
+ opserv_define_func("DEVNULL LIST", cmd_listdevnull, 200, 0, 0);
+ opserv_define_func("SVSJOIN", cmd_svsjoin, 800, 0, 3);
+ opserv_define_func("SVSMODE", cmd_svsmode, 800, 0, 3);
+ opserv_define_func("SVSNICK", cmd_svsnick, 800, 0, 3);
+ opserv_define_func("RELAY", cmd_relay, 800, 0, 0);
+ opserv_define_func("SIMUL", cmd_simul, 999, 0, 2);
opserv_define_func("TRACE", cmd_trace, 100, 0, 3);
opserv_define_func("TRACE PRINT", NULL, 0, 0, 0);
opserv_define_func("TRACE COUNT", NULL, 0, 0, 0);
#ifndef _opserv_h
#define _opserv_h
+/* DEVNULL_MODE_* go into devnull_class.modes */
+#define DEVNULL_MODE_A 0x00000001
+#define DEVNULL_MODE_B 0x00000002
+#define DEVNULL_MODE_C 0x00000004
+#define DEVNULL_MODE_D 0x00000008
+#define DEVNULL_MODE_E 0x00000010
+#define DEVNULL_MODE_F 0x00000020
+#define DEVNULL_MODE_G 0x00000040
+#define DEVNULL_MODE_H 0x00000080
+#define DEVNULL_MODE_I 0x00000100
+#define DEVNULL_MODE_J 0x00000200
+#define DEVNULL_MODE_K 0x00000400
+#define DEVNULL_MODE_L 0x00000800
+#define DEVNULL_MODE_M 0x00001000
+#define DEVNULL_MODE_N 0x00002000
+#define DEVNULL_MODE_OPME 0x00004000
+
+#define DEVNULL_MODES "abcdefghijklmno"
+
+#define DEVNULL_FLAGGED(hi, tok) ((hi)->modes & DEVNULL_##tok)
+#define DEVNULL_SET_FLAG(hi, tok) ((hi)->modes |= DEVNULL_##tok)
+#define DEVNULL_CLEAR_FLAG(hi, tok) ((hi)->modes &= ~DEVNULL_##tok)
+
+struct devnull_class {
+ char *name;
+ unsigned long modes;
+ unsigned long maxchan;
+ unsigned long maxsendq;
+};
+
void init_opserv(const char *nick);
unsigned int gag_create(const char *mask, const char *owner, const char *reason, unsigned long expires);
int opserv_bad_channel(const char *name);
+void devnull_delete(const char *auth);
+void devnull_rename(const char *oldauth, const char *newauth);
+int devnull_check(const char *name);
+struct devnull_class* devnull_get(const char *name);
+struct userNode* GetOpServ(void);
+void operpart(struct chanNode *chan, struct userNode *user);
+void operadd(struct userNode *user);
+void operdel(struct userNode *user);
#endif
}
struct mod_chanmode *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
+mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
{
struct mod_chanmode *change;
unsigned int ii, in_arg, ch_arg, add;
#include "ioset.h"
#include "log.h"
#include "nickserv.h"
+#include "opserv.h"
+#include "chanserv.h"
+#include "spamserv.h"
#include "timeq.h"
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
&& (offchannel_allowed[(unsigned char)pd->text[0]]
|| (GetUserMode(cn, cf->service) && !IsDeaf(cf->service))))
cf->func(pd->user, cn, pd->text+1, cf->service, pd->is_notice);
+ else
+ spamserv_channel_message(cn, pd->user, pd->text);
/* This catches *all* text sent to the channel that the services server sees */
for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
{
struct part_desc *desc = data;
DelChannelUser(desc->user, cn, desc->text, false);
+ if (IsOper(desc->user))
+ operpart(cn, desc->user);
}
static CMD_FUNC(cmd_part)
res->modes_set = orig->modes_set;
res->modes_clear = orig->modes_clear;
res->new_limit = orig->new_limit;
+ res->new_access = orig->new_access;
+ memcpy(res->new_altchan, orig->new_altchan, sizeof(res->new_altchan));
+ memcpy(res->new_noflood, orig->new_noflood, sizeof(res->new_noflood));
memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
memcpy(res->new_upass, orig->new_upass, sizeof(res->new_upass));
memcpy(res->new_apass, orig->new_apass, sizeof(res->new_apass));
channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
if (change->modes_set & MODE_LIMIT)
channel->limit = change->new_limit;
+ if (change->modes_set & MODE_ACCESS)
+ channel->access = change->new_access;
if (change->modes_set & MODE_KEY)
strcpy(channel->key, change->new_key);
+ if (change->modes_set & MODE_ALTCHAN)
+ strcpy(channel->altchan, change->new_altchan);
+ if (change->modes_set & MODE_NOFLOOD)
+ strcpy(channel->noflood, change->new_noflood);
if (change->modes_set & MODE_UPASS)
strcpy(channel->upass, change->new_upass);
if (change->modes_set & MODE_APASS)
base_oplevel = member->oplevel;
else
base_oplevel = MAXOPLEVEL;
- if (!(change = mod_chanmode_parse(channel, modes, argc, flags, base_oplevel)))
+ if (!(change = mod_chanmode_parse(channel, who, modes, argc, flags, base_oplevel)))
return 0;
if (flags & MC_ANNOUNCE)
mod_chanmode_announce(who, channel, change);
mod_chanmode_init(&change);
change.modes_set = chan->modes;
change.new_limit = chan->limit;
+ change.new_access = chan->access;
+ safestrncpy(change.new_altchan, chan->altchan, sizeof(change.new_altchan));
+ safestrncpy(change.new_noflood, chan->noflood, sizeof(change.new_noflood));
safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
safestrncpy(change.new_upass, chan->upass, sizeof(change.new_upass));
safestrncpy(change.new_apass, chan->apass, sizeof(change.new_apass));
}
hostname = user->hostname;
if (IsFakeHost(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING)) {
- hostname = user->fakehost;
+ if(user->fakehost && user->fakehost[0] == '$') {
+ hostname = alloca(strlen(user->handle_info->handle) + strlen(user->fakehost));
+ sprintf(hostname, "%s%s", user->handle_info->handle, user->fakehost+1);
+ } else {
+ hostname = user->fakehost;
+ }
} else if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
#define CMD_EOB_ACK "EOB_ACK"
#define CMD_ERROR "ERROR"
#define CMD_FAKEHOST "FAKE"
+#define CMD_FAKEHOST2 "FAKE2"
#define CMD_GET "GET"
#define CMD_GLINE "GLINE"
#define CMD_HASH "HASH"
#define CMD_PROTO "PROTO"
#define CMD_QUIT "QUIT"
#define CMD_REHASH "REHASH"
+#define CMD_RELAY "RELAY"
#define CMD_RESET "RESET"
#define CMD_RESTART "RESTART"
#define CMD_RPING "RPING"
#define CMD_SQUIT "SQUIT"
#define CMD_STATS "STATS"
#define CMD_SVSNICK "SVSNICK"
+#define CMD_SVSMODE "SVSMODE"
+#define CMD_SVSJOIN "SVSJOIN"
#define CMD_TIME "TIME"
#define CMD_TOPIC "TOPIC"
#define CMD_TRACE "TRACE"
#define TOK_EOB_ACK "EA"
#define TOK_ERROR "Y"
#define TOK_FAKEHOST "FA"
+#define TOK_FAKEHOST2 "NFH"
#define TOK_GET "GET"
#define TOK_GLINE "GL"
#define TOK_HASH "HASH"
#define TOK_PROTO "PROTO"
#define TOK_QUIT "Q"
#define TOK_REHASH "REHASH"
+#define TOK_RELAY "RL"
#define TOK_RESET "RESET"
#define TOK_RESTART "RESTART"
#define TOK_RPING "RI"
#define TOK_SQUIT "SQ"
#define TOK_STATS "R"
#define TOK_SVSNICK "SN"
+#define TOK_SVSMODE "SM"
+#define TOK_SVSJOIN "SJ"
#define TOK_TIME "TI"
#define TOK_TOPIC "T"
#define TOK_TRACE "TR"
#define P10_EOB_ACK TYPE(EOB_ACK)
#define P10_ERROR TYPE(ERROR)
#define P10_FAKEHOST TYPE(FAKEHOST)
+#define P10_FAKEHOST2 TYPE(FAKEHOST2)
#define P10_GET TYPE(GET)
#define P10_GLINE TYPE(GLINE)
#define P10_HASH TYPE(HASH)
#define P10_PROTO TYPE(PROTO)
#define P10_QUIT TYPE(QUIT)
#define P10_REHASH TYPE(REHASH)
+#define P10_RELAY TYPE(RELAY)
#define P10_RESET TYPE(RESET)
#define P10_RESTART TYPE(RESTART)
#define P10_RPING TYPE(RPING)
#define P10_SQUIT TYPE(SQUIT)
#define P10_STATS TYPE(STATS)
#define P10_SVSNICK TYPE(SVSNICK)
+#define P10_SVSMODE TYPE(SVSMODE)
+#define P10_SVSJOIN TYPE(SVSJOIN)
#define P10_TIME TYPE(TIME)
#define P10_TOPIC TYPE(TOPIC)
#define P10_TRACE TYPE(TRACE)
static const char *his_servercomment;
static struct channelList dead_channels;
-/* These correspond to 1 << X: 012345678901234567 */
-const char irc_user_mode_chars[] = "o iw dkgn x I";
+/* These correspond to 1 << X: 012345678901234567890123 */
+const char irc_user_mode_chars[] = "o iw dkgn x ISDXHst";
static struct userNode *AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip);
void
irc_fakehost(struct userNode *user, const char *host, const char *ident, int force)
{
- putsock("%s " P10_FAKEHOST " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
+ /* SRVX added the possibility for FAKE IDENTS
+ * but this is currently *NOT* supported by our IRCu
+ *
+ * edit 24.11.11:
+ * NFH (P10_FAKEHOST2) is now supported by our IRCu (git-65-21592a4)
+ */
+ putsock("%s " P10_FAKEHOST2 " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
+}
+
+void
+irc_relay(char *message)
+{
+ putsock("%s " P10_RELAY " %s", self->numeric, message);
+}
+
+void
+irc_simul(struct userNode *target, char *command)
+{
+ putsock("%s " P10_RELAY " %s SI %s :%s", self->numeric, target->numeric, target->numeric, command);
}
void
putsock("%s " P10_PRIVMSG " %s :%s", from->numeric, to, message);
}
+void
+irc_svsmode(struct userNode *from, struct userNode *user, const char *modes)
+{
+putsock("%s " P10_SVSMODE " %s %s", from->numeric, user->numeric, modes);
+}
+
+void
+irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan)
+{
+putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan->name);
+}
+
+void
+irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan)
+{
+putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan);
+}
+
void
irc_eob(void)
{
void
irc_gline(struct server *srv, struct gline *gline)
{
+ //<prefix> GL <target> [!][+|-|>|<]<mask> [<expiration>] [<lastmod>] [<lifetime>] [:<reason>]
+ //expiration = relative time (seconds)
+ //lastmod = timestamp
+ //livetime = timestamp
if (gline->lastmod)
- putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s",
- self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->lastmod, gline->lifetime, gline->reason);
+ putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s", self->numeric, (srv ? srv->numeric : "*"),
+ gline->target, gline->expires-now, gline->lastmod, gline->lifetime, gline->reason);
else
- putsock("%s " P10_GLINE " %s +%s %lu :%s",
- self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->reason);
+ putsock("%s " P10_GLINE " %s +%s %lu :%s", self->numeric, (srv ? srv->numeric : "*"),
+ gline->target, gline->expires-now, gline->reason);
}
void
irc_ungline(const char *mask)
{
putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now);
+ //putsock("%s " P10_GLINE " * %s * %lu", self->numeric, mask, now);
}
/* Return negative if *(struct modeNode**)pa is "less than" pb,
return;
}
+ handle_new_channel_created(name, cd->user);
+
AddChannelUser(cd->user, AddChannel(name, cd->when, NULL, NULL));
}
int n_modes;
for (pos=argv[next], n_modes = 1; *pos; pos++)
if ((*pos == 'k') || (*pos == 'l') || (*pos == 'A')
- || (*pos == 'U'))
+ || (*pos == 'U') || (*pos == 'a') || (*pos == 'F'))
n_modes++;
if (next + n_modes > argc)
n_modes = argc - next;
{
if (argc < 3)
return 0;
+ if (GetUserN(argv[2]) && IsOper(GetUserN(argv[2])))
+ operpart(GetChannel(argv[1]), GetUserN(argv[2]));
ChannelUserKicked(GetUserH(origin), GetUserN(argv[2]), GetChannel(argv[1]));
return 1;
}
return 1;
}
+static CMD_FUNC(cmd_relay)
+{
+ struct server *sNode;
+ unsigned int len;
+ char buf[3];
+ //<sender> RELAY <destination> <command>
+ len = strlen(argv[1]);
+ buf[2] = 0;
+ switch(len) {
+ case 2:
+ sNode = GetServerN(argv[1]);
+ break;
+ case 5:
+ buf[0] = argv[1][0];
+ buf[1] = argv[1][1];
+ sNode = GetServerN(buf);
+ break;
+ case 6:
+ buf[0] = argv[1][1];
+ buf[1] = argv[1][2];
+ sNode = GetServerN(buf);
+ break;
+ default:
+ /* Invalid destination. Ignore. */
+ return 0;
+ }
+ if(sNode->numeric == self->numeric) {
+ //ok someone relayed something to us!
+ if(strcmp("LQ", argv[2]) == 0) {
+ //oooh thats exciting - we've got a LOC Query! :D
+ //LQ !ABADE pk910 80.153.5.212 server.zoelle1.de ~watchcat :test
+ //ok let's check the login datas
+ struct handle_info *hi;
+ char tmp[MAXLEN], tmp2[MAXLEN];
+ sprintf(tmp, "%s@%s",argv[7],argv[6]);
+ sprintf(tmp2, "%s@%s",argv[7],argv[5]);
+ if((hi = checklogin(argv[4],argv[argc-1],&argv[3][1],tmp,tmp2))) {
+ //login ok
+ struct devnull_class *th;
+ char devnull[512];
+ if(hi->devnull && (th = devnull_get(hi->devnull))) {
+ const char *devnull_modes = DEVNULL_MODES;
+ int ii, flen;
+ char flags[50];
+ for (ii=flen=0; devnull_modes[ii]; ++ii)
+ if (th->modes & (1 << ii))
+ flags[flen++] = devnull_modes[ii];
+ flags[flen] = 0;
+ sprintf(devnull, "+%s %s %lu %lu",flags,th->name,th->maxchan,th->maxsendq);
+ } else {
+ devnull[0] = 0;
+ }
+ if(!HANDLE_FLAGGED(hi, AUTOHIDE)) {
+ sprintf(tmp,"%s LA %s 0 %s\n",argv[3],hi->handle,devnull);
+ } else if(getfakehost(argv[4])) {
+ sprintf(tmp,"%s LA %s %s %s\n",argv[3],hi->handle,getfakehost(argv[4]),devnull);
+ } else {
+ extern const char *hidden_host_suffix;
+ sprintf(tmp,"%s LA %s %s.%s %s\n",argv[3],hi->handle,hi->handle,hidden_host_suffix,devnull);
+ }
+ irc_relay(tmp);
+ } else {
+ //login rejected
+ sprintf(tmp,"%s LR\n",argv[3]);
+ irc_relay(tmp);
+ }
+ } else if(strcmp("UC", argv[2]) == 0) {
+ char tmp[MAXLEN];
+ sprintf(tmp,"%s UC %s %s",argv[3],argv[3],argv[4]);
+ irc_relay(tmp);
+ } else if(strcmp("JA", argv[2]) == 0) {
+ struct userData *uData;
+ struct chanNode *cn;
+ struct userNode *user;
+ char tmp[MAXLEN];
+ cn = GetChannel(argv[4]);
+ if (!cn) return 0;
+ if (!(user = GetUserN(argv[3]))) return 0;
+ if(!cn->channel_info) {
+ //channel not registered
+ sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
+ } else if((uData = GetChannelUser(cn->channel_info, user->handle_info))) {
+ if(uData->access >= atoi(argv[5])) {
+ //we can join
+ sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
+ } else {
+ //access too low
+ sprintf(tmp,"%s JAR %s %i %i\n",argv[3],cn->name,uData->access,uData->access);
+ }
+ } else {
+ //0 access
+ sprintf(tmp,"%s JAR %s %s %s\n",argv[3],cn->name,"0","0");
+ }
+ irc_relay(tmp);
+ }
+ }
+ return 1;
+}
+
static CMD_FUNC(cmd_xquery)
{
struct server *source;
dict_insert(irc_func_dict, TOK_STATS, cmd_stats);
dict_insert(irc_func_dict, CMD_SVSNICK, cmd_svsnick);
dict_insert(irc_func_dict, TOK_SVSNICK, cmd_svsnick);
+ dict_insert(irc_func_dict, CMD_RELAY, cmd_relay);
+ dict_insert(irc_func_dict, TOK_RELAY, cmd_relay);
dict_insert(irc_func_dict, CMD_WHOIS, cmd_whois);
dict_insert(irc_func_dict, TOK_WHOIS, cmd_whois);
dict_insert(irc_func_dict, CMD_GLINE, cmd_gline);
case 'o':
do_user_mode(FLAGS_OPER);
if (!add) {
+ operdel(user);
userList_remove(&curr_opers, user);
} else if (!userList_contains(&curr_opers, user)) {
+ operadd(user);
userList_append(&curr_opers, user);
call_oper_funcs(user);
}
case 'g': do_user_mode(FLAGS_GLOBAL); break;
case 'n': do_user_mode(FLAGS_NOCHAN); break;
case 'I': do_user_mode(FLAGS_NOIDLE); break;
+ case 'S': do_user_mode(FLAGS_NETSERV); break;
+ case 'D': do_user_mode(FLAGS_SECURITYSERV); break;
+ case 'X': do_user_mode(FLAGS_XTRAOP); break;
+ case 's': do_user_mode(FLAGS_SERVERNOTICE); break;
+ case 'H': do_user_mode(FLAGS_HIDDENOPER); break;
+ case 't': do_user_mode(FLAGS_SEENOIDLE); break;
case 'x': do_user_mode(FLAGS_HIDDEN_HOST); break;
case 'r':
if (*word) {
}
struct mod_chanmode *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
+mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
{
struct mod_chanmode *change;
unsigned int ii, in_arg, ch_arg, add;
case 'C': do_chan_mode(MODE_NOCTCPS); break;
case 'D': do_chan_mode(MODE_DELAYJOINS); break;
case 'c': do_chan_mode(MODE_NOCOLORS); break;
+ case 'M': do_chan_mode(MODE_NOAMSGS); break;
+ case 'N': do_chan_mode(MODE_NONOTICES); break;
+ case 'u': do_chan_mode(MODE_AUDITORIUM); break;
+ case 'S': do_chan_mode(MODE_SSLCHAN); break;
case 'i': do_chan_mode(MODE_INVITEONLY); break;
case 'm': do_chan_mode(MODE_MODERATED); break;
case 'n': do_chan_mode(MODE_NOPRIVMSGS); break;
case 's': do_chan_mode(MODE_SECRET); break;
case 't': do_chan_mode(MODE_TOPICLIMIT); break;
case 'z':
- if (!(flags & MCP_REGISTERED))
- do_chan_mode(MODE_REGISTERED);
+ if (!(flags & MCP_REGISTERED) && (!(flags & MCP_IGN_REGISTERED) || add)) {
+ do_chan_mode(MODE_REGISTERED);
+ } else if (flags & MCP_IGN_REGISTERED) {
+ /* ignore the modechange but continue parsing */
+ } else {
+ mod_chanmode_free(change);
+ return NULL;
+ }
break;
#undef do_chan_mode
case 'l':
change->modes_clear |= MODE_LIMIT;
}
break;
+ case 'a':
+ if (add) {
+ if (in_arg >= argc)
+ goto error;
+ change->modes_set |= MODE_ACCESS;
+ change->new_access = atoi(modes[in_arg++]);
+ } else {
+ change->modes_set &= ~MODE_ACCESS;
+ change->modes_clear |= MODE_ACCESS;
+ }
+ break;
case 'k':
if (add) {
if ((in_arg >= argc)
}
}
break;
+ case 'f':
+ if (add) {
+ if (in_arg >= argc)
+ goto error;
+ char *mode = modes[in_arg++];
+ if(mode[0] == '!' && !(flags & MCP_OPERMODE)) //noflood flag also for overriders
+ goto error;//only allow opers
+ else if(mode[0] == '!')
+ mode++;
+
+ if(mode[0] == '+' || mode[0] == '@') {
+ mode++;
+ }
+ char *p;
+ int count = 0, time = 0;
+ for(p = mode; p[0]; p++) {
+ if(p[0] == ':') {
+ char tmpchar = p[0];
+ p[0] = '\0';
+ count = strtoul(mode,0,10);
+ p[0] = tmpchar;
+ p++;
+ time = strtoul(p,0,10);
+ break;
+ }
+ }
+ if(count <= 0 || time <= 0 || count > 100 || time > 600)
+ goto error;
+ change->modes_set |= MODE_NOFLOOD;
+ safestrncpy(change->new_noflood, modes[in_arg - 1], sizeof(change->new_noflood));
+ } else {
+ change->modes_clear |= MODE_NOFLOOD;
+ }
+ break;
+ case 'F':
+ if (add) {
+ if (in_arg >= argc)
+ goto error;
+ char *altchan = modes[in_arg++];
+ struct chanNode *target;
+ if(!IsChannelName(altchan) || !(target = GetChannel(altchan)))
+ goto error;
+ if(!(flags & MCP_OPERMODE)) {
+ //check if the user has the permissions to use this channel as target
+ struct modeNode *mn;
+ struct userData *uData;
+ struct chanData *cData;
+ if(user && (mn = GetUserMode(target, user)) && (mn->modes & MODE_CHANOP)) {
+ //allow - user is opped on target channel
+ } else if(user && user->handle_info &&
+ (uData = GetChannelUser(channel->channel_info, user->handle_info)) &&
+ (cData = uData->channel) &&
+ uData->access >= cData->lvlOpts[lvlGiveOps]
+ ) {
+ //allow - user has access to get op on the channel
+ } else
+ goto error;
+ }
+ change->modes_set |= MODE_ALTCHAN;
+ safestrncpy(change->new_altchan, altchan, sizeof(change->new_altchan));
+ } else {
+ change->modes_clear |= MODE_ALTCHAN;
+ }
+ break;
case 'U':
if (flags & MCP_NO_APASS)
goto error;
DO_MODE_CHAR(INVITEONLY, 'i');
DO_MODE_CHAR(NOPRIVMSGS, 'n');
DO_MODE_CHAR(LIMIT, 'l');
+ DO_MODE_CHAR(ACCESS, 'a');
+ DO_MODE_CHAR(ALTCHAN, 'F');
+ DO_MODE_CHAR(NOFLOOD, 'f');
DO_MODE_CHAR(DELAYJOINS, 'D');
DO_MODE_CHAR(REGONLY, 'r');
DO_MODE_CHAR(NOCOLORS, 'c');
DO_MODE_CHAR(NOCTCPS, 'C');
+ DO_MODE_CHAR(NONOTICES, 'N');
+ DO_MODE_CHAR(NOAMSGS, 'M');
+ DO_MODE_CHAR(AUDITORIUM, 'u');
+ DO_MODE_CHAR(SSLCHAN, 'S');
DO_MODE_CHAR(REGISTERED, 'z');
#undef DO_MODE_CHAR
if (change->modes_clear & channel->modes & MODE_KEY)
DO_MODE_CHAR(REGONLY, 'r');
DO_MODE_CHAR(NOCOLORS, 'c');
DO_MODE_CHAR(NOCTCPS, 'C');
+ DO_MODE_CHAR(NONOTICES, 'N');
+ DO_MODE_CHAR(NOAMSGS, 'M');
+ DO_MODE_CHAR(AUDITORIUM, 'u');
+ DO_MODE_CHAR(SSLCHAN, 'S');
DO_MODE_CHAR(REGISTERED, 'z');
#undef DO_MODE_CHAR
if(change->modes_set & MODE_KEY)
sprintf(int_buff, "%d", change->new_limit);
mod_chanmode_append(&chbuf, 'l', int_buff);
}
+ if(change->modes_set & MODE_ACCESS) {
+ sprintf(int_buff, "%d", change->new_access);
+ mod_chanmode_append(&chbuf, 'a', int_buff);
+ }
+ if (change->modes_set & MODE_ALTCHAN)
+ mod_chanmode_append(&chbuf, 'F', change->new_altchan);
+ if (change->modes_set & MODE_NOFLOOD)
+ mod_chanmode_append(&chbuf, 'f', change->new_noflood);
}
for (arg = 0; arg < change->argc; ++arg) {
if (change->args[arg].mode & MODE_REMOVE)
DO_MODE_CHAR(INVITEONLY, 'i');
DO_MODE_CHAR(NOPRIVMSGS, 'n');
DO_MODE_CHAR(LIMIT, 'l');
+ DO_MODE_CHAR(ACCESS, 'a');
+ DO_MODE_CHAR(ALTCHAN, 'F');
+ DO_MODE_CHAR(NOFLOOD, 'f');
DO_MODE_CHAR(KEY, 'k');
DO_MODE_CHAR(UPASS, 'U');
DO_MODE_CHAR(APASS, 'A');
DO_MODE_CHAR(REGONLY, 'r');
DO_MODE_CHAR(NOCOLORS, 'c');
DO_MODE_CHAR(NOCTCPS, 'C');
+ DO_MODE_CHAR(NONOTICES, 'N');
+ DO_MODE_CHAR(NOAMSGS, 'M');
+ DO_MODE_CHAR(AUDITORIUM, 'u');
+ DO_MODE_CHAR(SSLCHAN, 'S');
DO_MODE_CHAR(REGISTERED, 'z');
#undef DO_MODE_CHAR
}
DO_MODE_CHAR(REGONLY, 'r');
DO_MODE_CHAR(NOCOLORS, 'c');
DO_MODE_CHAR(NOCTCPS, 'C');
+ DO_MODE_CHAR(NONOTICES, 'N');
+ DO_MODE_CHAR(NOAMSGS, 'M');
+ DO_MODE_CHAR(AUDITORIUM, 'u');
+ DO_MODE_CHAR(SSLCHAN, 'S');
DO_MODE_CHAR(REGISTERED, 'z');
DO_MODE_CHAR(LIMIT, 'l'), args_used += sprintf(args + args_used, " %d", change->new_limit);
DO_MODE_CHAR(KEY, 'k'), args_used += sprintf(args + args_used, " %s", change->new_key);
+ DO_MODE_CHAR(ACCESS, 'a'), args_used += sprintf(args + args_used, " %d", change->new_access);
+ DO_MODE_CHAR(ALTCHAN, 'F'), args_used += sprintf(args + args_used, " %s", change->new_altchan);
+ DO_MODE_CHAR(NOFLOOD, 'f'), args_used += sprintf(args + args_used, " %s", change->new_noflood);
DO_MODE_CHAR(UPASS, 'U'), args_used += sprintf(args + args_used, " %s", change->new_upass);
DO_MODE_CHAR(APASS, 'A'), args_used += sprintf(args + args_used, " %s", change->new_apass);
#undef DO_MODE_CHAR
case 't': cleared |= MODE_TOPICLIMIT; break;
case 'i': cleared |= MODE_INVITEONLY; break;
case 'n': cleared |= MODE_NOPRIVMSGS; break;
+ case 'F':
+ cleared |= MODE_ALTCHAN;
+ channel->altchan[0] = '\0';
+ break;
+ case 'f':
+ cleared |= MODE_NOFLOOD;
+ channel->noflood[0] = '\0';
+ break;
case 'k':
cleared |= MODE_KEY;
channel->key[0] = '\0';
cleared |= MODE_LIMIT;
channel->limit = 0;
break;
+ case 'a':
+ cleared |= MODE_ACCESS;
+ channel->access = 0;
+ break;
case 'b': cleared |= MODE_BAN; break;
case 'D': cleared |= MODE_DELAYJOINS; break;
case 'r': cleared |= MODE_REGONLY; break;
case 'c': cleared |= MODE_NOCOLORS; break;
case 'C': cleared |= MODE_NOCTCPS; break;
+ case 'M': cleared |= MODE_NOAMSGS; break;
+ case 'u': cleared |= MODE_AUDITORIUM; break;
+ case 'S': cleared |= MODE_SSLCHAN; break;
+ case 'N': cleared |= MODE_NONOTICES; break;
case 'z': cleared |= MODE_REGISTERED; break;
}
}
void irc_regnick(struct userNode *user);
void irc_fakehost(struct userNode *user, const char *host, const char *ident, int force);
+/* svs maintenance */
+void irc_svsmode(struct userNode *from, struct userNode *user, const char *modes);
+void irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan);
+void irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan);
+void irc_relay(char *message);
+void irc_simul(struct userNode *target, char *command);
+
/* numeric messages */
void irc_numeric(struct userNode *user, unsigned int num, const char *format, ...);
/* RFC1459-compliant numeric responses */
/* Channel mode manipulation */
#define KEYLEN 23
+#define NOFLOODLEN 15
+#define CHANNELLEN 200
typedef unsigned long chan_mode_t;
/* Rules for struct mod_chanmode:
* For a membership mode change, args[n].mode can contain more than
*/
struct mod_chanmode {
chan_mode_t modes_set, modes_clear;
- unsigned int new_limit, argc;
+ unsigned int new_limit, new_access, argc;
#ifndef NDEBUG
unsigned int alloc_argc;
#endif
char new_key[KEYLEN + 1];
+ char new_altchan[CHANNELLEN + 1];
+ char new_noflood[NOFLOODLEN + 1];
char new_upass[KEYLEN + 1];
char new_apass[KEYLEN + 1];
struct {
} u;
} args[1];
};
-#define MCP_ALLOW_OVB 0x0001 /* allow op, voice, ban manipulation */
-#define MCP_FROM_SERVER 0x0002 /* parse as from a server */
-#define MCP_KEY_FREE 0x0004 /* -k without a key argument */
-#define MCP_REGISTERED 0x0008 /* chan is already registered; do not allow changes to MODE_REGISTERED */
-#define MCP_UPASS_FREE 0x0010 /* -U without a key argument */
-#define MCP_APASS_FREE 0x0020 /* -A without a key argument */
-#define MCP_NO_APASS 0x0040 /* Do not allow +/-A or +/-U */
-#define MC_ANNOUNCE 0x0100 /* send a mod_chanmode() change out */
-#define MC_NOTIFY 0x0200 /* make local callbacks to announce */
+#define MCP_ALLOW_OVB 0x0001 /* allow op, voice, ban manipulation */
+#define MCP_FROM_SERVER 0x0002 /* parse as from a server */
+#define MCP_KEY_FREE 0x0004 /* -k without a key argument */
+#define MCP_REGISTERED 0x0008 /* chan is already registered; do not allow changes to MODE_REGISTERED */
+#define MCP_UPASS_FREE 0x0010 /* -U without a key argument */
+#define MCP_APASS_FREE 0x0020 /* -A without a key argument */
+#define MCP_NO_APASS 0x0040 /* Do not allow +/-A or +/-U */
+#define MCP_IGN_REGISTERED 0x0080 /* chan is already registered; ignore changes to MODE_REGISTERED */
+#define MC_ANNOUNCE 0x0100 /* send a mod_chanmode() change out */
+#define MC_NOTIFY 0x0200 /* make local callbacks to announce */
+#define MCP_OPERMODE 0x0400
#ifdef NDEBUG
#define mod_chanmode_init(CHANMODE) do { memset((CHANMODE), 0, sizeof(*CHANMODE)); } while (0)
#else
struct mod_chanmode *mod_chanmode_alloc(unsigned int argc);
struct mod_chanmode *mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra);
-struct mod_chanmode *mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel);
+struct mod_chanmode *mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel);
void mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change);
void mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change);
char *mod_chanmode_format(struct mod_chanmode *desc, char *buffer);
--- /dev/null
+/* spamserv.c - anti spam service
+ * Copyright 2004 feigling
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version. Important limitations are
+ * listed in the COPYING file that accompanies this software.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, email srvx-maintainers@srvx.net.
+ *
+ * $Id: spamserv.c,v 1.08 2004/06/27 22:21:00 feigling Exp $
+ */
+
+#include "conf.h"
+#include "spamserv.h"
+#include "chanserv.h"
+#include "global.h"
+#include "modcmd.h"
+#include "saxdb.h"
+#include "timeq.h"
+#include "gline.h"
+
+#define SPAMSERV_CONF_NAME "services/spamserv"
+
+#define KEY_EXCEPTIONS "exceptions"
+#define KEY_FLAGS "flags"
+#define KEY_INFO "info"
+#define KEY_EXCEPTLEVEL "exceptlevel"
+#define KEY_EXPIRY "expiry"
+
+#define KEY_DEBUG_CHANNEL "debug_channel"
+#define KEY_GLOBAL_EXCEPTIONS "global_exceptions"
+#define KEY_NETWORK_RULES "network_rules"
+#define KEY_TRIGGER "trigger"
+#define KEY_SHORT_BAN_DURATION "short_ban_duration"
+#define KEY_LONG_BAN_DURATION "long_ban_duration"
+#define KEY_GLINE_DURATION "gline_duration"
+#define KEY_EXCEPTION_MAX "exception_max"
+#define KEY_EXCEPTION_MIN_LEN "exception_min_len"
+#define KEY_EXCEPTION_MAX_LEN "exception_max_len"
+#define KEY_ADV_CHAN_MUST_EXIST "adv_chan_must_exist"
+#define KEY_STRIP_MIRC_CODES "strip_mirc_codes"
+#define KEY_ALLOW_MOVE_MERGE "allow_move_merge"
+
+#define SPAMSERV_FUNC(NAME) MODCMD_FUNC(NAME)
+#define SPAMSERV_SYNTAX() svccmd_send_help(user, spamserv, cmd)
+#define SPAMSERV_MIN_PARMS(N) do { \
+(void)argv; \
+ if(argc < N) { \
+ ss_reply(MSG_MISSING_PARAMS, argv[0]); \
+ SPAMSERV_SYNTAX(); \
+ return 0; } } while(0)
+
+struct userNode *spamserv;
+static struct module *spamserv_module;
+static struct service *spamserv_service;
+static struct log_type *SS_LOG;
+static unsigned long crc_table[256];
+
+dict_t registered_channels_dict;
+dict_t connected_users_dict;
+dict_t killed_users_dict;
+
+#define spamserv_notice(target, format...) send_message(target , spamserv , ## format)
+#define spamserv_debug(format...) do { if(spamserv_conf.debug_channel) send_channel_notice(spamserv_conf.debug_channel , spamserv , ## format); } while(0)
+#define ss_reply(format...) send_message(user , spamserv , ## format)
+
+#define SET_SUBCMDS_SIZE 10
+
+const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "FLOODSCAN", "JOINFLOODSCAN", "EXCEPTLEVEL","SCANCHANOPS", "SCANVOICED"};
+
+static void spamserv_clear_spamNodes(struct chanNode *channel);
+static void spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban);
+static unsigned long crc32(const char *text);
+
+#define BINARY_OPTION(arguments...) return binary_option(arguments, user, channel, argc, argv);
+#define MULTIPLE_OPTION(arguments...) return multiple_option(arguments, values, ArrayLength(values), user, channel, argc, argv);
+
+static const struct message_entry msgtab[] = {
+ { "SSMSG_CHANNEL_OPTIONS", "Channel Options:" },
+ { "SSMSG_STRING_VALUE", "$b%s$b%s" },
+ { "SSMSG_NUMERIC_VALUE", "$b%s$b%d - %s" },
+ { "SSMSG_EASYNUMERIC_VALUE", "$b%s$b%d" },
+ { "SSMSG_INVALID_NUM_SET", "$b'%d'$b is an invalid %s setting." },
+ { "SSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." },
+ { "SSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
+
+ { "SSMSG_NOT_REGISTERED", "$b%s$b has not been registered with $b$X$b." },
+ { "SSMSG_NOT_REGISTERED_CS", "$b%s$b has not been registered with $b$C$b." },
+ { "SSMSG_ALREADY_REGISTERED", "$b%s$b is already registered." },
+ { "SSMSG_DEBUG_CHAN", "You may not register the debug channel." },
+ { "SSMSG_SUSPENDED_CS", "$b$C$b access to $b%s$b has been temporarily suspended, thus you can't %s it." },
+ { "SSMSG_SUSPENDED", "$b$X$b access to $b%s$b has been temporarily suspended." },
+ { "SSMSG_NO_REGISTER", "Due to an error it was not possible to register $b%s$b." },
+ { "SSMSG_REG_SUCCESS", "Channel $b%s$b registered." },
+ { "SSMSG_UNREG_SUCCESS", "$b%s$b has been unregistered." },
+ { "SSMSG_NO_ACCESS", "You lack sufficient access to use this command." },
+ { "SSMSG_MUST_BE_OPER", "You must be an irc operator to set this option." },
+ { "SSMSG_CONFIRM_UNREG", "To confirm this unregistration, you must append 'CONFIRM' to the end of your command. For example, 'unregister CONFIRM'." },
+
+ { "SSMSG_NO_EXCEPTIONS", "No words found in the exception list." },
+ { "SSMSG_NO_SUCH_EXCEPTION", "Word $b%s$b not found in the exception list." },
+ { "SSMSG_EXCEPTION_LIST", "The following words are in the exception list:" },
+ { "SSMSG_EXCEPTION_ADDED", "Word $b%s$b added to the exception list." },
+ { "SSMSG_EXCEPTION_DELETED", "Word $b%s$b deleted from the exception list." },
+ { "SSMSG_EXCEPTION_IN_LIST", "The word $b%s$b is already in the exception list." },
+ { "SSMSG_EXCEPTION_MAX", "The exception list has reached the maximum exceptions (max %lu). Delete a word to add another one." },
+ { "SSMSG_EXCEPTION_TOO_SHORT", "The word must be at least %lu characters long." },
+ { "SSMSG_EXCEPTION_TOO_LONG", "The word may not be longer than %lu characters." },
+
+ { "SSMSG_STATUS", "$bStatus:$b" },
+ { "SSMSG_STATUS_USERS", "Total Users Online: %u" },
+ { "SSMSG_STATUS_CHANNELS", "Registered Channels: %u" },
+ { "SSMSG_STATUS_MEMORY", "$bMemory Information:$b" },
+ { "SSMSG_STATUS_CHANNEL_LIST", "$bRegistered Channels:$b" },
+ { "SSMSG_STATUS_NO_CHANNEL", "No channels registered." },
+ { NULL, NULL }
+};
+
+#define SSMSG_DEBUG_KICK "Kicked user $b%s$b from $b%s$b, reason: %s"
+#define SSMSG_DEBUG_BAN "Banned user $b%s$b from $b%s$b, reason: %s"
+#define SSMSG_DEBUG_KILL "Killed user $b%s$b, last violation in $b%s$b"
+#define SSMSG_DEBUG_GLINE "Glined user $b%s$b, host $b%s$b, last violation in $b%s$b"
+#define SSMSG_DEBUG_RECONNECT "Killed user $b%s$b reconnected to the network"
+#define SSMSG_SPAM "Spamming"
+#define SSMSG_FLOOD "Flooding the channel/network"
+#define SSMSG_ADV "Advertising"
+#define SSMSG_JOINFLOOD "Join flooding the channel"
+#define SSMSG_WARNING "%s is against the network rules"
+#define SSMSG_WARNING_2 "You are violating the network rules"
+#define SSMSG_WARNING_RULES "%s is against the network rules. Read the network rules at %s"
+#define SSMSG_WARNING_RULES_2 "You are violating the network rules. Read the network rules at %s"
+
+static struct
+{
+ struct chanNode *debug_channel;
+ struct string_list *global_exceptions;
+ const char *network_rules;
+ unsigned char trigger;
+ unsigned long short_ban_duration;
+ unsigned long long_ban_duration;
+ unsigned long gline_duration;
+ unsigned long exception_max;
+ unsigned long exception_min_len;
+ unsigned long exception_max_len;
+ unsigned int adv_chan_must_exist : 1;
+ unsigned int strip_mirc_codes : 1;
+ unsigned int allow_move_merge : 1;
+} spamserv_conf;
+
+/***********************************************/
+/* Channel */
+/***********************************************/
+
+struct chanInfo*
+get_chanInfo(const char *channelname)
+{
+ return dict_find(registered_channels_dict, channelname, 0);
+}
+
+static void
+spamserv_join_channel(struct chanNode *channel)
+{
+ struct mod_chanmode change;
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].mode = MODE_CHANOP;
+ change.args[0].u.member = AddChannelUser(spamserv, channel);
+ mod_chanmode_announce(spamserv, channel, &change);
+}
+
+static void
+spamserv_part_channel(struct chanNode *channel, char *reason)
+{
+ /* we only have to clear the spamNodes because every other node expires on it's own */
+ spamserv_clear_spamNodes(channel);
+ DelChannelUser(spamserv, channel, reason, 0);
+}
+
+static struct chanInfo*
+spamserv_register_channel(struct chanNode *channel, struct string_list *exceptions, unsigned int flags, char *info)
+{
+ struct chanInfo *cInfo = malloc(sizeof(struct chanInfo));
+
+ if(!cInfo)
+ {
+ log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for cInfo; channel: %s", channel->name);
+ return NULL;
+ }
+
+ cInfo->channel = channel;
+ cInfo->exceptions = exceptions ? string_list_copy(exceptions) : alloc_string_list(1);
+ cInfo->flags = flags;
+ cInfo->exceptlevel = 400;
+ safestrncpy(cInfo->info, info, sizeof(cInfo->info));
+ cInfo->suspend_expiry = 0;
+ dict_insert(registered_channels_dict, cInfo->channel->name, cInfo);
+
+ return cInfo;
+}
+
+static void
+spamserv_unregister_channel(struct chanInfo *cInfo)
+{
+ if(!cInfo)
+ return;
+
+ dict_remove(registered_channels_dict, cInfo->channel->name);
+ free_string_list(cInfo->exceptions);
+ free(cInfo);
+}
+
+void
+spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+
+ if(cInfo)
+ {
+ if(suspend)
+ {
+ cInfo->flags |= CHAN_SUSPENDED;
+ cInfo->suspend_expiry = expiry;
+ spamserv_part_channel(channel, reason);
+ }
+ else
+ {
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ cInfo->flags &= ~CHAN_SUSPENDED;
+ cInfo->suspend_expiry = 0;
+ }
+ }
+ }
+}
+
+int
+spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+
+ if(cInfo)
+ {
+ char reason[MAXLEN];
+
+ if(!spamserv_conf.allow_move_merge || get_chanInfo(target->name))
+ {
+ if(move)
+ snprintf(reason, sizeof(reason), "unregistered due to a channel move to %s", target->name);
+ else
+ snprintf(reason, sizeof(reason), "unregistered due to a channel merge into %s", target->name);
+
+ spamserv_cs_unregister(user, channel, manually, reason);
+ return 0;
+ }
+
+ cInfo->channel = target;
+
+ dict_remove(registered_channels_dict, channel->name);
+ dict_insert(registered_channels_dict, target->name, cInfo);
+
+ if(move)
+ {
+ snprintf(reason, sizeof(reason), "Channel moved to %s by %s.", target->name, user->handle_info->handle);
+ }
+ else
+ {
+ spamserv_join_channel(target);
+ snprintf(reason, sizeof(reason), "%s merged into %s by %s.", channel->name, target->name, user->handle_info->handle);
+ }
+
+ if(!CHECK_SUSPENDED(cInfo))
+ spamserv_part_channel(channel, reason);
+
+ if(move)
+ snprintf(reason, sizeof(reason), "$X (channel %s) moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
+ else
+ snprintf(reason, sizeof(reason), "$X (channel %s) merged into %s by %s.", channel->name, target->name, user->handle_info->handle);
+
+ global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+ return 1;
+ }
+
+ return 0;
+}
+
+void
+spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+
+ if(cInfo)
+ {
+ char global[MAXLEN], partmsg[MAXLEN];
+
+ switch (type)
+ {
+ case manually:
+ snprintf(global, sizeof(global), "$X (channel %s) %s by %s.", channel->name, reason, user->handle_info->handle);
+ snprintf(partmsg, sizeof(partmsg), "%s %s by %s.", channel->name, reason, user->handle_info->handle);
+ break;
+ case expire:
+ snprintf(global, sizeof(global), "$X (channel %s) registration expired.", channel->name);
+ snprintf(partmsg, sizeof(partmsg), "%s registration expired.", channel->name);
+ break;
+ case lost_all_users:
+ snprintf(global, sizeof(global), "$X (channel %s) lost all users.", channel->name);
+ snprintf(partmsg, sizeof(partmsg), "%s lost all users.", channel->name);
+ break;
+ }
+
+ if(!CHECK_SUSPENDED(cInfo))
+ spamserv_part_channel(channel, partmsg);
+
+ spamserv_unregister_channel(cInfo);
+ global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, global);
+ }
+}
+
+/***********************************************/
+/* User */
+/***********************************************/
+
+static struct userInfo*
+get_userInfo(const char *nickname)
+{
+ return dict_find(connected_users_dict, nickname, 0);
+}
+
+static void
+spamserv_create_spamNode(struct chanNode *channel, struct userInfo *uInfo, char *text)
+{
+ struct spamNode *sNode = malloc(sizeof(struct spamNode));
+
+ if(!sNode)
+ {
+ log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for sNode; channel: %s; user: %s", channel->name, uInfo->user->nick);
+ return;
+ }
+
+ sNode->channel = channel;
+ sNode->crc32 = crc32(text);
+ sNode->count = 1;
+ sNode->next = NULL;
+
+ if(uInfo->spam)
+ {
+ struct spamNode *temp = uInfo->spam;
+
+ while(temp->next)
+ temp = temp->next;
+
+ sNode->prev = temp;
+ temp->next = sNode;
+ }
+ else
+ {
+ sNode->prev = NULL;
+ uInfo->spam = sNode;
+ }
+}
+
+static void
+spamserv_delete_spamNode(struct userInfo *uInfo, struct spamNode *sNode)
+{
+ if(!sNode)
+ return;
+
+ if(sNode == uInfo->spam)
+ uInfo->spam = sNode->next;
+
+ if(sNode->next)
+ sNode->next->prev = sNode->prev;
+ if(sNode->prev)
+ sNode->prev->next = sNode->next;
+
+ free(sNode);
+}
+
+static void
+spamserv_clear_spamNodes(struct chanNode *channel)
+{
+ struct userInfo *uInfo;
+ struct spamNode *sNode;
+ unsigned int i;
+
+ for(i = 0; i < channel->members.used; i++)
+ {
+ if((uInfo = get_userInfo(channel->members.list[i]->user->nick)))
+ {
+ if((sNode = uInfo->spam))
+ {
+ for(; sNode; sNode = sNode->next)
+ if(sNode->channel == channel)
+ break;
+
+ if(sNode)
+ spamserv_delete_spamNode(uInfo, sNode);
+ }
+ }
+ }
+}
+
+static void
+spamserv_create_floodNode(struct chanNode *channel, struct userNode *user, struct floodNode **uI_fNode)
+{
+ struct floodNode *fNode = malloc(sizeof(struct floodNode));
+
+ if(!fNode)
+ {
+ log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for fNode; channel: %s; user: %s", channel->name, user->nick);
+ return;
+ }
+
+ fNode->channel = channel;
+ fNode->owner = user;
+ fNode->count = 1;
+ fNode->time = now;
+ fNode->next = NULL;
+
+ if(*uI_fNode)
+ {
+ struct floodNode *temp = *uI_fNode;
+
+ while(temp->next)
+ temp = temp->next;
+
+ fNode->prev = temp;
+ temp->next = fNode;
+ }
+ else
+ {
+ fNode->prev = NULL;
+ *uI_fNode = fNode;
+ }
+}
+
+static void
+spamserv_delete_floodNode(struct floodNode **uI_fNode, struct floodNode *fNode)
+{
+ if(!fNode)
+ return;
+
+ if(fNode == *uI_fNode)
+ *uI_fNode = fNode->next;
+
+ if(fNode->next)
+ fNode->next->prev = fNode->prev;
+ if(fNode->prev)
+ fNode->prev->next = fNode->next;
+
+ free(fNode);
+}
+
+static void
+spamserv_create_user(struct userNode *user)
+{
+ struct userInfo *uInfo = malloc(sizeof(struct userInfo));
+ struct killNode *kNode = dict_find(killed_users_dict, irc_ntoa(&user->ip), 0);
+
+ if(!uInfo)
+ {
+ log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for uInfo; nick: %s", user->nick);
+ return;
+ }
+
+ if(kNode)
+ spamserv_debug(SSMSG_DEBUG_RECONNECT, user->nick);
+
+ uInfo->user = user;
+ uInfo->spam = NULL;
+ uInfo->flood = NULL;
+ uInfo->joinflood = NULL;
+ uInfo->flags = kNode ? USER_KILLED : 0;
+ uInfo->warnlevel = kNode ? kNode->warnlevel : 0;
+ uInfo->lastadv = 0;
+
+ dict_insert(connected_users_dict, user->nick, uInfo);
+
+ if(kNode)
+ {
+ dict_remove(killed_users_dict, irc_ntoa(&user->ip));
+ free(kNode);
+ }
+}
+
+static void
+spamserv_delete_user(struct userInfo *uInfo)
+{
+ if(!uInfo)
+ return;
+
+ if(uInfo->spam)
+ while(uInfo->spam)
+ spamserv_delete_spamNode(uInfo, uInfo->spam);
+
+ if(uInfo->flood)
+ while(uInfo->flood)
+ spamserv_delete_floodNode(&uInfo->flood, uInfo->flood);
+
+ if(uInfo->joinflood)
+ while(uInfo->joinflood)
+ spamserv_delete_floodNode(&uInfo->joinflood, uInfo->joinflood);
+
+ dict_remove(connected_users_dict, uInfo->user->nick);
+ free(uInfo);
+}
+
+static void
+spamserv_new_user_func(struct userNode *user)
+{
+ if(!IsLocal(user))
+ spamserv_create_user(user);
+}
+
+static void
+spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why))
+{
+ struct userInfo *uInfo = get_userInfo(user->nick);
+ struct killNode *kNode;
+
+ if(killer == spamserv)
+ {
+ kNode = malloc(sizeof(struct killNode));
+
+ if(!kNode)
+ {
+ log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for killNode - nickname %s", user->nick);
+ spamserv_delete_user(uInfo);
+ return;
+ }
+
+ if(uInfo->warnlevel > KILL_WARNLEVEL)
+ kNode->warnlevel = uInfo->warnlevel - KILL_WARNLEVEL;
+ else
+ kNode->warnlevel = 0;
+
+ kNode->time = now;
+
+ dict_insert(killed_users_dict, irc_ntoa(&user->ip), kNode);
+ }
+
+ spamserv_delete_user(uInfo);
+}
+
+static void
+spamserv_nick_change_func(struct userNode *user, const char *old_nick)
+{
+ struct userInfo *uInfo = get_userInfo(old_nick);
+
+ dict_remove(connected_users_dict, old_nick);
+ dict_insert(connected_users_dict, user->nick, uInfo);
+}
+
+static int
+spamserv_user_join(struct modeNode *mNode)
+{
+ struct chanNode *channel = mNode->channel;
+ struct userNode *user = mNode->user;
+ struct chanInfo *cInfo;
+ struct userInfo *uInfo;
+ struct floodNode *jfNode;
+
+ if(user->uplink->burst || !(cInfo = get_chanInfo(channel->name)) || !CHECK_JOINFLOOD(cInfo) || !(uInfo = get_userInfo(user->nick)))
+ return 0;
+
+
+ if(!CHECK_CHANOPS(cInfo))
+ {
+ //struct modeNode *mn = GetUserMode(channel, user);
+ //if(mn->modes & MODE_CHANOP)
+ // return;
+ if(check_user_level(channel, user, lvlGiveOps, 1, 0))
+ return 0;
+ }
+
+ if(!CHECK_VOICED(cInfo))
+ {
+ if(check_user_level(channel, user, lvlGiveVoice, 1, 0))
+ return 0;
+ }
+
+ if(cInfo->exceptlevel == 0)
+ return 0;
+ if(cInfo->exceptlevel < 501) {
+ struct userData *uData;
+ if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) {
+ return 0;
+ }
+ }
+
+ if(!(jfNode = uInfo->joinflood))
+ {
+ spamserv_create_floodNode(channel, user, &uInfo->joinflood);
+ }
+ else
+ {
+ for(; jfNode; jfNode = jfNode->next)
+ if(jfNode->channel == channel)
+ break;
+
+ if(!jfNode)
+ {
+ spamserv_create_floodNode(channel, user, &uInfo->joinflood);
+ }
+ else
+ {
+ jfNode->count++;
+ jfNode->time = now;
+
+ if(jfNode->count > JOINFLOOD_MAX)
+ {
+ char reason[MAXLEN];
+
+ spamserv_delete_floodNode(&uInfo->joinflood, jfNode);
+ snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_JOINFLOOD, spamserv_conf.network_rules);
+ spamserv_punish(channel, user, JOINFLOOD_B_DURATION, reason, 1);
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void
+spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
+{
+ struct userNode *user = mn->user;
+ struct chanNode *channel = mn->channel;
+ struct userInfo *uInfo;
+ struct spamNode *sNode;
+ struct floodNode *fNode;
+
+ if(user->dead || !get_chanInfo(channel->name) || !(uInfo = get_userInfo(user->nick)))
+ return;
+
+ if((sNode = uInfo->spam))
+ {
+ for(; sNode; sNode = sNode->next)
+ if(sNode->channel == channel)
+ break;
+
+ if(sNode)
+ spamserv_delete_spamNode(uInfo, sNode);
+ }
+
+ if((fNode = uInfo->flood))
+ {
+ for(; fNode; fNode = fNode->next)
+ if(fNode->channel == channel)
+ break;
+
+ if(fNode)
+ spamserv_delete_floodNode(&uInfo->flood, fNode);
+ }
+}
+
+/***********************************************/
+/* Other Stuff */
+/***********************************************/
+
+static void
+crc32_init(void)
+{
+ unsigned long crc;
+ int i, j;
+
+ for(i = 0; i < 256; i++)
+ {
+ crc = i;
+
+ for(j = 8; j > 0; j--)
+ {
+ if(crc & 1)
+ {
+ crc = (crc >> 1) ^ 0xEDB88320L;
+ }
+ else
+ {
+ crc >>= 1;
+ }
+ }
+
+ crc_table[i] = crc;
+ }
+}
+
+static unsigned long
+crc32(const char *text)
+{
+ register unsigned long crc = 0xFFFFFFFF;
+ unsigned int c, i = 0;
+
+ while((c = (unsigned int)text[i++]) != 0)
+ crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF];
+
+ return (crc^0xFFFFFFFF);
+}
+
+static void
+timeq_flood(UNUSED_ARG(void *data))
+{
+ dict_iterator_t it;
+ struct userInfo *uInfo;
+ struct floodNode *fNode;
+
+ for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+ {
+ uInfo = iter_data(it);
+
+ if(!(fNode = uInfo->flood))
+ continue;
+
+ for(; fNode; fNode = fNode->next)
+ {
+ if(now - fNode->time > FLOOD_EXPIRE)
+ {
+ if(!(--fNode->count))
+ spamserv_delete_floodNode(&uInfo->flood, fNode);
+ }
+ }
+ }
+
+ timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);
+}
+
+static void
+timeq_joinflood(UNUSED_ARG(void *data))
+{
+ dict_iterator_t it;
+ struct userInfo *uInfo;
+ struct floodNode *fNode;
+
+ for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+ {
+ uInfo = iter_data(it);
+
+ if(!(fNode = uInfo->joinflood))
+ continue;
+
+ for(; fNode; fNode = fNode->next)
+ {
+ if(now - fNode->time > JOINFLOOD_EXPIRE)
+ {
+ if(!(--fNode->count))
+ spamserv_delete_floodNode(&uInfo->joinflood, fNode);
+ }
+ }
+ }
+
+ timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);
+}
+
+static void
+timeq_adv(UNUSED_ARG(void *data))
+{
+ dict_iterator_t it;
+ struct userInfo *uInfo;
+
+ for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+ {
+ uInfo = iter_data(it);
+
+ if(uInfo->lastadv && uInfo->lastadv - now > ADV_EXPIRE)
+ {
+ uInfo->lastadv = 0;
+ uInfo->flags &= ~USER_ADV_WARNED;
+ }
+ }
+
+ timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);
+}
+
+static void
+timeq_warnlevel(UNUSED_ARG(void *data))
+{
+ dict_iterator_t it;
+ struct userInfo *uInfo;
+
+ for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+ {
+ uInfo = iter_data(it);
+
+ if(uInfo->warnlevel > 0)
+ uInfo->warnlevel--;
+ }
+
+ timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);
+}
+
+static void
+timeq_kill(UNUSED_ARG(void *data))
+{
+ dict_iterator_t it;
+ struct killNode *kNode;
+
+ for(it = dict_first(killed_users_dict); it; it = iter_next(it))
+ {
+ kNode = iter_data(it);
+
+ if(kNode->time - now > KILL_EXPIRE)
+ free(kNode);
+ }
+
+ timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);
+}
+
+static int
+binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[])
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ int value;
+
+ if(argc > 1)
+ {
+ if(enabled_string(argv[1]))
+ {
+ cInfo->flags |= mask;
+ value = 1;
+ }
+ else if(disabled_string(argv[1]))
+ {
+ cInfo->flags &= ~mask;
+ value = 0;
+ }
+ else
+ {
+ spamserv_notice(user, "SSMSG_INVALID_BINARY", argv[1]);
+ return 0;
+ }
+ }
+ else
+ {
+ value = (cInfo->flags & mask) ? 1 : 0;
+ }
+
+ spamserv_notice(user, "SSMSG_STRING_VALUE", name, value ? "Enabled." : "Disabled.");
+ return 1;
+}
+
+struct valueData
+{
+ char *description;
+ char value;
+ int oper_only : 1;
+};
+
+static int
+multiple_option(char *name, char *description, enum channelinfo info, struct valueData *values, int count, struct userNode *user, struct chanNode *channel, int argc, char *argv[])
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ int index;
+
+ if(argc > 1)
+ {
+ index = atoi(argv[1]);
+
+ if(index < 0 || index >= count)
+ {
+ spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, description);
+
+ for(index = 0; index < count; index++)
+ spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description);
+
+ return 0;
+ }
+
+ if(values[index].oper_only && !IsOper(user))
+ {
+ spamserv_notice(user, "SSMSG_MUST_BE_OPER");
+ return 0;
+ }
+
+ cInfo->info[info] = values[index].value;
+ }
+ else
+ {
+ for(index = 0; index < count && cInfo->info[info] != values[index].value; index++);
+ }
+
+ spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description);
+ return 1;
+}
+
+static int
+show_exceptions(struct userNode *user, struct chanInfo *cInfo)
+{
+ struct helpfile_table table;
+ unsigned int i;
+
+ if(!cInfo->exceptions->used)
+ {
+ spamserv_notice(user, "SSMSG_NO_EXCEPTIONS");
+ return 0;
+ }
+
+ spamserv_notice(user, "SSMSG_EXCEPTION_LIST");
+
+ table.length = 0;
+ table.width = 1;
+ table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;
+ table.contents = alloca(cInfo->exceptions->used * sizeof(*table.contents));
+
+ for(i = 0; i < cInfo->exceptions->used; i++)
+ {
+ table.contents[table.length] = alloca(table.width * sizeof(**table.contents));
+ table.contents[table.length][0] = cInfo->exceptions->list[i];
+ table.length++;
+ }
+
+ table_send(spamserv, user->nick, 0, NULL, table);
+
+ return 1;
+}
+
+static void
+show_memory_usage(struct userNode *user)
+{
+ dict_iterator_t it;
+ struct helpfile_table table;
+ struct chanInfo *cInfo;
+ struct userInfo *uInfo;
+ struct spamNode *sNode;
+ struct floodNode *fNode;
+ double channel_size = 0, user_size, size;
+ unsigned int spamcount = 0, floodcount = 0, i, j;
+ char buffer[64];
+
+ for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
+ {
+ cInfo = iter_data(it);
+
+ if(!cInfo->exceptions->used)
+ continue;
+
+ for(i = 0; i < cInfo->exceptions->used; i++)
+ channel_size += strlen(cInfo->exceptions->list[i]) * sizeof(char);
+ }
+
+ for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+ {
+ uInfo = iter_data(it);
+
+ for(sNode = uInfo->spam; sNode; sNode = sNode->next, spamcount++);
+ for(fNode = uInfo->flood; fNode; fNode = fNode->next, floodcount++);
+ for(fNode = uInfo->joinflood; fNode; fNode = fNode->next, floodcount++);
+ }
+
+ channel_size += dict_size(registered_channels_dict) * sizeof(struct chanInfo);
+
+ user_size = dict_size(connected_users_dict) * sizeof(struct userInfo) +
+ dict_size(killed_users_dict) * sizeof(struct killNode) +
+ spamcount * sizeof(struct spamNode) +
+ floodcount * sizeof(struct floodNode);
+
+ size = channel_size + user_size;
+
+ ss_reply("SSMSG_STATUS_MEMORY");
+
+ table.length = 3;
+ table.width = 4;
+ table.flags = TABLE_NO_FREE | TABLE_NO_HEADERS | TABLE_PAD_LEFT;
+ table.contents = calloc(table.length, sizeof(char**));
+
+ // chanInfo
+ table.contents[0] = calloc(table.width, sizeof(char*));
+ snprintf(buffer, sizeof(buffer), "Channel Memory Usage:");
+ table.contents[0][0] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), " %g Byte; ", channel_size);
+ table.contents[0][1] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), "%g KiloByte; ", channel_size / 1024);
+ table.contents[0][2] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), "%g MegaByte", channel_size / 1024 / 1024);
+ table.contents[0][3] = strdup(buffer);
+
+ // userInfo
+ table.contents[1] = calloc(table.width, sizeof(char*));
+ snprintf(buffer, sizeof(buffer), "User Memory Usage :");
+ table.contents[1][0] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), " %g Byte; ", user_size);
+ table.contents[1][1] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), "%g KiloByte; ", user_size / 1024);
+ table.contents[1][2] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), "%g MegaByte", user_size / 1024 / 1024);
+ table.contents[1][3] = strdup(buffer);
+
+ // total memory usage
+ table.contents[2] = calloc(table.width, sizeof(char*));
+ snprintf(buffer, sizeof(buffer), "Total Memory Usage :");
+ table.contents[2][0] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), " %g Byte; ", size);
+ table.contents[2][1] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), "%g KiloByte; ", size / 1024);
+ table.contents[2][2] = strdup(buffer);
+ snprintf(buffer, sizeof(buffer), "%g MegaByte", size / 1024 / 1024);
+ table.contents[2][3] = strdup(buffer);
+
+ table_send(spamserv, user->nick, 0, NULL, table);
+
+ for(i = 0; i < table.length; i++)
+ {
+ for(j = 0; j < table.width; j++)
+ free((char*)table.contents[i][j]);
+
+ free(table.contents[i]);
+ }
+
+ free(table.contents);
+}
+
+static void
+show_registered_channels(struct userNode *user)
+{
+ struct helpfile_table table;
+ dict_iterator_t it;
+
+ spamserv_notice(user, "SSMSG_STATUS_CHANNEL_LIST");
+
+ if(!dict_size(registered_channels_dict))
+ {
+ spamserv_notice(user, "SSMSG_STATUS_NO_CHANNEL");
+ return;
+ }
+
+ table.length = 0;
+ table.width = 1;
+ table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;
+ table.contents = alloca(dict_size(registered_channels_dict) * sizeof(*table.contents));
+
+ for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
+ {
+ struct chanInfo *cInfo = iter_data(it);
+
+ table.contents[table.length] = alloca(table.width * sizeof(**table.contents));
+ table.contents[table.length][0] = cInfo->channel->name;
+ table.length++;
+ }
+
+ table_send(spamserv, user->nick, 0, NULL, table);
+}
+
+/***********************************************/
+/* SpamServ_Func */
+/***********************************************/
+
+static
+SPAMSERV_FUNC(cmd_register)
+{
+ struct chanInfo *cInfo;
+ char reason[MAXLEN];
+
+ if(!channel || !channel->channel_info)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED_CS", channel->name);
+ return 0;
+ }
+
+ if(get_chanInfo(channel->name))
+ {
+ ss_reply("SSMSG_ALREADY_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(IsSuspended(channel->channel_info))
+ {
+ ss_reply("SSMSG_SUSPENDED_CS", channel->name, "register");
+ return 0;
+ }
+
+ if(channel == spamserv_conf.debug_channel)
+ {
+ ss_reply("SSMSG_DEBUG_CHAN");
+ return 0;
+ }
+
+ if(!(cInfo = spamserv_register_channel(channel, spamserv_conf.global_exceptions, CHAN_FLAGS_DEFAULT , CHAN_INFO_DEFAULT)))
+ {
+ ss_reply("SSMSG_NO_REGISTER", channel->name);
+ return 0;
+ }
+
+ spamserv_join_channel(cInfo->channel);
+
+ snprintf(reason, sizeof(reason), "%s (channel %s) registered by %s.", spamserv->nick, channel->name, user->handle_info->handle);
+ global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+ ss_reply("SSMSG_REG_SUCCESS", channel->name);
+
+ return 1;
+}
+
+static
+SPAMSERV_FUNC(cmd_unregister)
+{
+ struct chanInfo *cInfo;
+ struct chanData *cData;
+ struct userData *uData;
+ char reason[MAXLEN];
+
+ if(!channel || !(cData = channel->channel_info) || !(cInfo = get_chanInfo(channel->name)))
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(!(uData = GetChannelUser(cData, user->handle_info)) || (uData->access < UL_OWNER))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ if(!IsHelping(user))
+ {
+ if(IsSuspended(cData))
+ {
+ ss_reply("SSMSG_SUSPENDED_CS", channel->name, "unregister");
+ return 0;
+ }
+
+ if(argc < 2 || strcasecmp(argv[1], "CONFIRM"))
+ {
+ ss_reply("SSMSG_CONFIRM_UNREG");
+ return 0;
+ }
+ }
+
+ if(!CHECK_SUSPENDED(cInfo))
+ {
+ snprintf(reason, sizeof(reason), "%s unregistered by %s.", spamserv->nick, user->handle_info->handle);
+ spamserv_part_channel(channel, reason);
+ }
+
+ spamserv_unregister_channel(cInfo);
+
+ snprintf(reason, sizeof(reason), "%s (channel %s) unregistered by %s.", spamserv->nick, channel->name, user->handle_info->handle);
+ global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+ ss_reply("SSMSG_UNREG_SUCCESS", channel->name);
+
+ return 1;
+}
+
+static
+SPAMSERV_FUNC(cmd_status)
+{
+ ss_reply("SSMSG_STATUS");
+ ss_reply("SSMSG_STATUS_USERS", dict_size(connected_users_dict));
+ ss_reply("SSMSG_STATUS_CHANNELS", dict_size(registered_channels_dict));
+
+ if(IsOper(user) && argc > 1)
+ {
+ if(!irccasecmp(argv[1], "memory"))
+ show_memory_usage(user);
+ else if(!irccasecmp(argv[1], "channels"))
+ show_registered_channels(user);
+ }
+
+ return 1;
+}
+
+static
+SPAMSERV_FUNC(cmd_addexception)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ unsigned int i;
+
+ if(!cInfo || !channel->channel_info)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ if(argc < 2)
+ return show_exceptions(user, cInfo);
+
+ if(cInfo->exceptions->used == spamserv_conf.exception_max && !IsOper(user))
+ {
+ ss_reply("SSMSG_EXCEPTION_MAX", spamserv_conf.exception_max);
+ return 0;
+ }
+
+ if(strlen(argv[1]) < spamserv_conf.exception_min_len)
+ {
+ ss_reply("SSMSG_EXCEPTION_TOO_SHORT", spamserv_conf.exception_min_len);
+ return 0;
+ }
+ else if(strlen(argv[1]) > spamserv_conf.exception_max_len)
+ {
+ ss_reply("SSMSG_EXCEPTION_TOO_LONG", spamserv_conf.exception_max_len);
+ return 0;
+ }
+
+ for(i = 0; i < cInfo->exceptions->used; i++)
+ {
+ if(!irccasecmp(argv[1], cInfo->exceptions->list[i]))
+ {
+ ss_reply("SSMSG_EXCEPTION_IN_LIST", argv[1]);
+ return 0;
+ }
+ }
+
+ string_list_append(cInfo->exceptions, strdup(argv[1]));
+ ss_reply("SSMSG_EXCEPTION_ADDED", argv[1]);
+
+ return 1;
+}
+
+static
+SPAMSERV_FUNC(cmd_delexception)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ unsigned int i;
+ int found = -1;
+
+ if(!cInfo || !channel->channel_info)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ if(argc < 2)
+ return show_exceptions(user, cInfo);
+
+ for(i = 0; i < cInfo->exceptions->used; i++)
+ {
+ if(!irccasecmp(argv[1], cInfo->exceptions->list[i]))
+ {
+ found = i;
+ break;
+ }
+ }
+
+ if(found == -1)
+ {
+ ss_reply("SSMSG_NO_SUCH_EXCEPTION", argv[1]);
+ return 0;
+ }
+
+ string_list_delete(cInfo->exceptions, i);
+ ss_reply("SSMSG_EXCEPTION_DELETED", argv[1]);
+
+ return 1;
+}
+
+static
+SPAMSERV_FUNC(cmd_set)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct svccmd *subcmd;
+ char cmd_name[MAXLEN];
+ unsigned int i;
+
+ if(!cInfo)
+ {
+ ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+ return 0;
+ }
+
+ if(CHECK_SUSPENDED(cInfo))
+ {
+ ss_reply("SSMSG_SUSPENDED", channel->name);
+ return 0;
+ }
+
+ if(!check_user_level(channel,user,lvlSetters,1,0))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+
+ if(argc < 2)
+ {
+ ss_reply("SSMSG_CHANNEL_OPTIONS");
+
+ for(i = 0; i < SET_SUBCMDS_SIZE; i++)
+ {
+ sprintf(cmd_name, "%s %s", cmd->name, set_subcommands[i]);
+
+ if((subcmd = dict_find(cmd->parent->commands, cmd_name, NULL)))
+ subcmd->command->func(user, channel, 1, argv + 1, subcmd);
+ }
+
+ return 1;
+ }
+
+ sprintf(cmd_name, "%s %s", cmd->name, argv[1]);
+ subcmd = dict_find(cmd->parent->commands, cmd_name, NULL);
+
+ if(!subcmd)
+ {
+ reply("SSMSG_INVALID_OPTION", argv[1], argv[0]);
+ return 0;
+ }
+
+ return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
+}
+
+static
+SPAMSERV_FUNC(opt_spamlimit)
+{
+ struct valueData values[] =
+ {
+ {"Users may send the same message $b2$b times.", 'a', 0},
+ {"Users may send the same message $b3$b times.", 'b', 0},
+ {"Users may send the same message $b4$b times.", 'c', 0},
+ {"Users may send the same message $b5$b times.", 'd', 0},
+ {"Users may send the same message $b6$b times.", 'e', 0}
+ };
+
+ MULTIPLE_OPTION("SpamLimit ", "SpamLimit", ci_SpamLimit);
+}
+
+static
+SPAMSERV_FUNC(opt_advreaction)
+{
+ struct valueData values[] =
+ {
+ {"Kick on disallowed advertising.", 'k', 0},
+ {"Kickban on disallowed advertising.", 'b', 0},
+ {"Short timed ban on disallowed advertising.", 's', 0},
+ {"Long timed ban on disallowed advertising.", 'l', 0},
+ {"Kill on disallowed advertising.", 'd', 1}
+ };
+
+ MULTIPLE_OPTION("AdvReaction ", "AdvReaction", ci_AdvReaction);
+}
+
+static
+SPAMSERV_FUNC(opt_warnreaction)
+{
+ struct valueData values[] =
+ {
+ {"Kick after warning.", 'k', 0},
+ {"Kickban after warning.", 'b', 0},
+ {"Short timed ban after warning.", 's', 0},
+ {"Long timed ban after warning.", 'l', 0},
+ {"Kill after warning.", 'd', 1}
+ };
+
+ MULTIPLE_OPTION("WarnReaction ", "WarnReaction", ci_WarnReaction);
+}
+
+static
+SPAMSERV_FUNC(opt_advscan)
+{
+ BINARY_OPTION("AdvScan ", CHAN_ADV_SCAN);
+}
+
+static
+SPAMSERV_FUNC(opt_spamscan)
+{
+ BINARY_OPTION("SpamScan ", CHAN_SPAMSCAN);
+}
+
+static
+SPAMSERV_FUNC(opt_floodscan)
+{
+ BINARY_OPTION("FloodScan ", CHAN_FLOODSCAN);
+}
+
+static
+SPAMSERV_FUNC(opt_joinflood)
+{
+ BINARY_OPTION("JoinFloodScan ", CHAN_JOINFLOOD);
+}
+
+static
+SPAMSERV_FUNC(opt_scanops)
+{
+ BINARY_OPTION("ScanChanOps ", CHAN_SCAN_CHANOPS);
+}
+
+static
+SPAMSERV_FUNC(opt_scanvoiced)
+{
+ BINARY_OPTION("ScanVoiced ", CHAN_SCAN_VOICED);
+}
+
+static
+SPAMSERV_FUNC(opt_exceptlevel)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ int index;
+ if(argc > 1)
+ {
+ index = atoi(argv[1]);
+ if(index < 1 || index > 501)
+ {
+ spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, "ExceptLevel");
+ return 0;
+ }
+ if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < cInfo->exceptlevel || index > uData->access))
+ {
+ ss_reply("SSMSG_NO_ACCESS");
+ return 0;
+ }
+ cInfo->exceptlevel=index;
+ } else {
+ index=cInfo->exceptlevel;
+ }
+ spamserv_notice(user, "SSMSG_EASYNUMERIC_VALUE", "ExceptLevel ", index);
+ return 1;
+}
+
+static void
+to_lower(char *message)
+{
+ unsigned int i, diff = 'a' - 'A';
+
+ for(i = 0; i < strlen(message); i++)
+ {
+ if((message[i] >= 'A') && (message[i] <= 'Z'))
+ message[i] = message[i] + diff;
+ }
+}
+
+static char *
+strip_mirc_codes(char *text)
+{
+ // taken from xchat and modified
+ int nc = 0, i = 0, col = 0, len = strlen(text);
+ static char new_str[MAXLEN];
+
+ while(len > 0)
+ {
+ if((col && isdigit(*text) && nc < 2) ||
+ (col && *text == ',' && isdigit(*(text + 1)) && nc < 3))
+ {
+ nc++;
+
+ if(*text == ',')
+ nc = 0;
+ }
+ else
+ {
+ col = 0;
+
+ switch(*text)
+ {
+ case '\003':
+ col = 1;
+ nc = 0;
+ break;
+ case '\002':
+ case '\022':
+ case '\026':
+ case '\031':
+ case '\037':
+ break;
+ default:
+ new_str[i] = *text;
+ i++;
+ }
+ }
+
+ text++;
+ len--;
+ }
+
+ new_str[i] = '\0';
+
+ return new_str;
+}
+
+static int
+is_in_exception_list(struct chanInfo *cInfo, char *message)
+{
+ unsigned int i;
+
+ for(i = 0; i < cInfo->exceptions->used; i++)
+ if(strstr(message, cInfo->exceptions->list[i]))
+ return 1;
+
+ return 0;
+}
+
+static int
+check_advertising(struct chanInfo *cInfo, char *message)
+{
+ unsigned int i = 0;
+
+ if(spamserv_conf.strip_mirc_codes)
+ message = strip_mirc_codes(message);
+
+ if(is_in_exception_list(cInfo, message))
+ return 0;
+
+ while(message[i] != 0)
+ {
+ if(message[i] == '#')
+ {
+ char channelname[CHANNELLEN];
+ unsigned int j = 0;
+
+ if(!spamserv_conf.adv_chan_must_exist)
+ return 1;
+
+ /* only return 1, if the channel does exist */
+
+ while((message[i] != 0) && (message[i] != ' '))
+ {
+ channelname[j] = message[i];
+ i++;
+ j++;
+ }
+
+ channelname[j] = '\0';
+
+ if(GetChannel(channelname))
+ return 1;
+ }
+ else if((message[i] == 'w') && (message[i+1] == 'w') && (message[i+2] == 'w') && (message[i+3] == '.'))
+ return 1;
+ else if((message[i] == 'h') && (message[i+1] == 't') && (message[i+2] == 't') && (message[i+3] == 'p') && (message[i+4] == ':'))
+ return 1;
+ else if((message[i] == 'f') && (message[i+1] == 't') && (message[i+2] == 'p') && ((message[i+3] == '.') || (message[i+3] == ':')))
+ return 1;
+
+ i++;
+ }
+
+ return 0;
+}
+
+struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason);
+
+static void
+spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban)
+{
+ if(ban)
+ {
+ struct mod_chanmode change;
+ char *hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);
+
+ sanitize_ircmask(hostmask);
+
+ if(expires)
+ add_channel_ban(channel->channel_info, hostmask, spamserv->nick, now, now, now + expires, reason);
+
+ mod_chanmode_init(&change);
+ change.argc = 1;
+ change.args[0].mode = MODE_BAN;
+ change.args[0].u.hostmask = hostmask;
+ mod_chanmode_announce(spamserv, channel, &change);
+
+ free(hostmask);
+
+ spamserv_debug(SSMSG_DEBUG_BAN, user->nick, channel->name, reason);
+ }
+ else
+ spamserv_debug(SSMSG_DEBUG_KICK, user->nick, channel->name, reason);
+
+ KickChannelUser(user, channel, spamserv, reason);
+}
+
+void
+spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text)
+{
+ struct chanInfo *cInfo;
+ struct userInfo *uInfo;
+ struct spamNode *sNode;
+ struct floodNode *fNode;
+ unsigned int violation = 0;
+ char reason[MAXLEN];
+
+ /* make sure: spamserv is not disabled; srvx is running; spamserv is in the chan; chan is regged, user does exist */
+ if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick)))
+ return;
+
+ if(!CHECK_CHANOPS(cInfo))
+ {
+ struct modeNode *mn = GetUserMode(channel, user);
+ if(mn && mn->modes & MODE_CHANOP)
+ return;
+ //if(check_user_level(channel, user, lvlGiveOps, 1, 0))
+ // return;
+ }
+
+ if(!CHECK_VOICED(cInfo))
+ {
+ struct modeNode *mn = GetUserMode(channel, user);
+ if(mn && (mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP))
+ return;
+ }
+
+ if(cInfo->exceptlevel == 0)
+ return;
+ if(cInfo->exceptlevel < 501) {
+ struct userData *uData;
+ if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) {
+ return;
+ }
+ }
+
+ to_lower(text);
+
+ if(CHECK_SPAM(cInfo))
+ {
+ if(!(sNode = uInfo->spam))
+ {
+ spamserv_create_spamNode(channel, uInfo, text);
+ }
+ else
+ {
+ for(; sNode; sNode = sNode->next)
+ if(sNode->channel == channel)
+ break;
+
+ if(!sNode)
+ {
+ spamserv_create_spamNode(channel, uInfo, text);
+ }
+ else
+ {
+ unsigned long crc = crc32(text);
+
+ if(crc == sNode->crc32)
+ {
+ unsigned int spamlimit = 2;
+ sNode->count++;
+
+ switch(cInfo->info[ci_SpamLimit])
+ {
+ case 'a': spamlimit = 2; break;
+ case 'b': spamlimit = 3; break;
+ case 'c': spamlimit = 4; break;
+ case 'd': spamlimit = 5; break;
+ case 'e': spamlimit = 6; break;
+ }
+
+ if(sNode->count == spamlimit)
+ {
+ uInfo->warnlevel += SPAM_WARNLEVEL;
+
+ if(uInfo->warnlevel < MAX_WARNLEVEL)
+ spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules);
+ }
+ else if(sNode->count > spamlimit)
+ {
+ switch(cInfo->info[ci_WarnReaction])
+ {
+ case 'k': uInfo->flags |= USER_KICK; break;
+ case 'b': uInfo->flags |= USER_KICKBAN; break;
+ case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+ case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+ case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+ }
+
+ spamserv_delete_spamNode(uInfo, sNode);
+ uInfo->warnlevel += SPAM_WARNLEVEL;
+ violation = 1;
+ }
+ }
+ else
+ {
+ sNode->crc32 = crc;
+ sNode->count = 1;
+ }
+ }
+ }
+ }
+
+ if(CHECK_FLOOD(cInfo))
+ {
+ if(!(fNode = uInfo->flood))
+ {
+ spamserv_create_floodNode(channel, user, &uInfo->flood);
+ }
+ else
+ {
+ for(; fNode; fNode = fNode->next)
+ if(fNode->channel == channel)
+ break;
+
+ if(!fNode)
+ {
+ spamserv_create_floodNode(channel, user, &uInfo->flood);
+ }
+ else
+ {
+ if(((now - fNode->time) < FLOOD_EXPIRE))
+ {
+ fNode->count++;
+
+ if(fNode->count == FLOOD_MAX_LINES - 1)
+ {
+ uInfo->warnlevel += FLOOD_WARNLEVEL;
+
+ if(uInfo->warnlevel < MAX_WARNLEVEL)
+ spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules);
+ }
+ else if(fNode->count > FLOOD_MAX_LINES)
+ {
+ switch(cInfo->info[ci_WarnReaction])
+ {
+ case 'k': uInfo->flags |= USER_KICK; break;
+ case 'b': uInfo->flags |= USER_KICKBAN; break;
+ case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+ case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+ case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+ }
+
+ spamserv_delete_floodNode(&uInfo->flood, fNode);
+ uInfo->warnlevel += FLOOD_WARNLEVEL;
+ violation = 2;
+ }
+ }
+
+ fNode->time = now;
+ }
+ }
+ }
+
+ if(CHECK_ADV(cInfo) && check_advertising(cInfo, text))
+ {
+ if(CHECK_ADV_WARNED(uInfo))
+ {
+ switch(cInfo->info[ci_AdvReaction])
+ {
+ case 'k': uInfo->flags |= USER_KICK; break;
+ case 'b': uInfo->flags |= USER_KICKBAN; break;
+ case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+ case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+ case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+ }
+
+ uInfo->warnlevel += ADV_WARNLEVEL;
+ violation = 3;
+ }
+ else
+ {
+ uInfo->flags |= USER_ADV_WARNED;
+ uInfo->lastadv = now;
+ uInfo->warnlevel += ADV_WARNLEVEL;
+
+ if(uInfo->warnlevel < MAX_WARNLEVEL)
+ spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules);
+ }
+ }
+
+ if(!CHECK_WARNED(uInfo) && !CHECK_KILL(uInfo) && !CHECK_GLINE(uInfo) && uInfo->warnlevel == MAX_WARNLEVEL)
+ {
+ uInfo->flags |= USER_WARNED;
+ snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules);
+ irc_notice(spamserv, user->numeric, reason);
+ irc_privmsg(spamserv, user->numeric, reason);
+ }
+ else if(uInfo->warnlevel > MAX_WARNLEVEL)
+ {
+ if(CHECK_KILLED(uInfo))
+ uInfo->flags |= USER_GLINE;
+ else
+ uInfo->flags |= USER_KILL;
+
+ violation = 5;
+ }
+
+ if(!violation)
+ return;
+
+ switch(violation)
+ {
+ case 1: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); break;
+ case 2: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); break;
+ case 3: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); break;
+ default: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); break;
+ }
+
+ if(CHECK_GLINE(uInfo))
+ {
+ int size = strlen(user->hostname) + 3;
+ char *mask = alloca(size);
+ snprintf(mask, size, "*@%s", user->hostname);
+ gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 0, 1);
+ spamserv_debug(SSMSG_DEBUG_GLINE, user->nick, user->hostname, channel->name);
+ }
+ else if(CHECK_KILL(uInfo))
+ {
+ DelUser(user, spamserv, 1, reason);
+ spamserv_debug(SSMSG_DEBUG_KILL, user->nick, channel->name);
+ }
+ else if(CHECK_LONG_TBAN(uInfo))
+ {
+ spamserv_punish(channel, user, spamserv_conf.long_ban_duration, reason, 1);
+ }
+ else if(CHECK_SHORT_TBAN(uInfo))
+ {
+ spamserv_punish(channel, user, spamserv_conf.short_ban_duration, reason, 1);
+ }
+ else if(CHECK_KICKBAN(uInfo))
+ {
+ spamserv_punish(channel, user, 0, reason, 1);
+ }
+ else if(CHECK_KICK(uInfo))
+ {
+ spamserv_punish(channel, user, 0, reason, 0);
+ }
+}
+
+static int
+spamserv_saxdb_read(struct dict *database)
+{
+ dict_iterator_t it;
+ struct record_data *hir;
+ struct chanNode *channel;
+ struct chanInfo *cInfo;
+ struct string_list *strlist;
+ unsigned int flags,exceptlevel;
+ char *str, *info;
+ time_t expiry;
+
+ for(it = dict_first(database); it; it = iter_next(it))
+ {
+ hir = iter_data(it);
+
+ if(hir->type != RECDB_OBJECT)
+ {
+ log_module(SS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
+ continue;
+ }
+
+ channel = GetChannel(iter_key(it));
+ strlist = database_get_data(hir->d.object, KEY_EXCEPTIONS, RECDB_STRING_LIST);
+
+ str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
+ flags = str ? atoi(str) : 0;
+
+ info = database_get_data(hir->d.object, KEY_INFO, RECDB_QSTRING);
+ str = database_get_data(hir->d.object, KEY_EXCEPTLEVEL, RECDB_QSTRING);
+ exceptlevel = str ? atoi(str) : 400;
+
+ str = database_get_data(hir->d.object, KEY_EXPIRY, RECDB_QSTRING);
+ expiry = str ? strtoul(str, NULL, 0) : 0;
+
+ if(channel && info)
+ {
+ if((cInfo = spamserv_register_channel(channel, strlist, flags, info)))
+ {
+ /* if the channel is suspended and expiry = 0 it means: channel will
+ never expire ! it does NOT mean, the channel is not suspended */
+ if(CHECK_SUSPENDED(cInfo) && expiry && (expiry < now))
+ {
+ cInfo->flags &= ~CHAN_SUSPENDED;
+ spamserv_join_channel(cInfo->channel);
+ }
+ else if(!CHECK_SUSPENDED(cInfo))
+ spamserv_join_channel(cInfo->channel);
+ else
+ cInfo->suspend_expiry = expiry;
+ cInfo->exceptlevel=exceptlevel;
+ }
+ }
+ else
+ log_module(SS_LOG, LOG_ERROR, "Couldn't register channel %s. Channel or info invalid.", iter_key(it));
+ }
+
+ return 0;
+}
+
+static int
+spamserv_saxdb_write(struct saxdb_context *ctx)
+{
+ dict_iterator_t it;
+
+ for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
+ {
+ struct chanInfo *cInfo = iter_data(it);
+
+ saxdb_start_record(ctx, cInfo->channel->name, 1);
+
+ if(cInfo->exceptions->used)
+ saxdb_write_string_list(ctx, KEY_EXCEPTIONS, cInfo->exceptions);
+
+ if(cInfo->flags)
+ saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags);
+
+ saxdb_write_string(ctx, KEY_INFO, cInfo->info);
+ saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel);
+
+ if(cInfo->suspend_expiry)
+ saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry);
+
+ saxdb_end_record(ctx);
+ }
+ return 0;
+}
+
+static void
+spamserv_conf_read(void)
+{
+ dict_t conf_node;
+ const char *str;
+
+ if(!(conf_node = conf_get_data(SPAMSERV_CONF_NAME, RECDB_OBJECT)))
+ {
+ log_module(SS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", SPAMSERV_CONF_NAME);
+ return;
+ }
+
+ str = database_get_data(conf_node, KEY_DEBUG_CHANNEL, RECDB_QSTRING);
+
+ if(str)
+ {
+ spamserv_conf.debug_channel = AddChannel(str, now, "+tinms", NULL);
+
+ if(spamserv_conf.debug_channel)
+ spamserv_join_channel(spamserv_conf.debug_channel);
+ }
+ else
+ {
+ spamserv_conf.debug_channel = NULL;
+ }
+
+ spamserv_conf.global_exceptions = database_get_data(conf_node, KEY_GLOBAL_EXCEPTIONS, RECDB_STRING_LIST);
+
+ str = database_get_data(conf_node, KEY_NETWORK_RULES, RECDB_QSTRING);
+ spamserv_conf.network_rules = str ? str : NULL;
+
+ str = database_get_data(conf_node, KEY_TRIGGER, RECDB_QSTRING);
+ spamserv_conf.trigger = str ? str[0] : 0;
+
+ str = database_get_data(conf_node, KEY_SHORT_BAN_DURATION, RECDB_QSTRING);
+ spamserv_conf.short_ban_duration = str ? ParseInterval(str) : ParseInterval("15m");
+
+ str = database_get_data(conf_node, KEY_LONG_BAN_DURATION, RECDB_QSTRING);
+ spamserv_conf.long_ban_duration = str ? ParseInterval(str) : ParseInterval("1h");
+
+ str = database_get_data(conf_node, KEY_GLINE_DURATION, RECDB_QSTRING);
+ spamserv_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h");
+
+ str = database_get_data(conf_node, KEY_EXCEPTION_MAX, RECDB_QSTRING);
+ spamserv_conf.exception_max = str ? strtoul(str, NULL, 0) : 10;
+
+ str = database_get_data(conf_node, KEY_EXCEPTION_MIN_LEN, RECDB_QSTRING);
+ spamserv_conf.exception_min_len = str ? strtoul(str, NULL, 0) : 4;
+
+ str = database_get_data(conf_node, KEY_EXCEPTION_MAX_LEN, RECDB_QSTRING);
+ spamserv_conf.exception_max_len = str ? strtoul(str, NULL, 0) : 15;
+
+ str = database_get_data(conf_node, KEY_ADV_CHAN_MUST_EXIST, RECDB_QSTRING);
+ spamserv_conf.adv_chan_must_exist = str ? enabled_string(str) : 1;
+
+ str = database_get_data(conf_node, KEY_STRIP_MIRC_CODES, RECDB_QSTRING);
+ spamserv_conf.strip_mirc_codes = str ? enabled_string(str) : 0;
+
+ str = database_get_data(conf_node, KEY_ALLOW_MOVE_MERGE, RECDB_QSTRING);
+ spamserv_conf.allow_move_merge = str ? enabled_string(str) : 0;
+}
+
+static void
+spamserv_db_cleanup(void)
+{
+ dict_iterator_t it;
+
+ while((it = dict_first(registered_channels_dict)))
+ {
+ spamserv_unregister_channel(iter_data(it));
+ }
+
+ while((it = dict_first(killed_users_dict)))
+ {
+ free(iter_data(it));
+ }
+
+ dict_delete(registered_channels_dict);
+ dict_delete(connected_users_dict);
+ dict_delete(killed_users_dict);
+}
+
+void
+init_spamserv(const char *nick)
+{
+ if(!nick)
+ return;
+
+ const char *modes = conf_get_data("services/spamserv/modes", RECDB_QSTRING);
+ spamserv = AddLocalUser(nick, nick, NULL, "Anti Spam Services", modes);
+ spamserv_service = service_register(spamserv);
+ service_register(spamserv)->trigger = spamserv_conf.trigger;
+
+ conf_register_reload(spamserv_conf_read);
+
+ SS_LOG = log_register_type("SpamServ", "file:spamserv.log");
+
+ registered_channels_dict = dict_new();
+ connected_users_dict = dict_new();
+ killed_users_dict = dict_new();
+
+ reg_new_user_func(spamserv_new_user_func);
+ reg_del_user_func(spamserv_del_user_func);
+ reg_nick_change_func(spamserv_nick_change_func);
+ reg_join_func(spamserv_user_join);
+ reg_part_func(spamserv_user_part);
+
+ timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);
+ timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);
+ timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);
+ timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);
+ timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);
+
+ spamserv_module = module_register("SpamServ", SS_LOG, "spamserv.help", NULL);
+ modcmd_register(spamserv_module, "REGISTER", cmd_register, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+acceptchan,+helping", NULL);
+ modcmd_register(spamserv_module, "UNREGISTER", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+loghostmask", NULL);
+ modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "DELEXCEPTION", cmd_delexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL);
+ modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET WARNREACTION", opt_warnreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET ADVSCAN", opt_advscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET SPAMSCAN", opt_spamscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET FLOODSCAN", opt_floodscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET JOINFLOODSCAN", opt_joinflood, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET SCANCHANOPS", opt_scanops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET SCANVOICED", opt_scanvoiced, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+ modcmd_register(spamserv_module, "SET EXCEPTLEVEL", opt_exceptlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+
+ saxdb_register("SpamServ", spamserv_saxdb_read, spamserv_saxdb_write);
+ reg_exit_func(spamserv_db_cleanup);
+ message_register_table(msgtab);
+ crc32_init();
+}
--- /dev/null
+/* spamserv.h - anti spam service
+ * Copyright 2004 feigling
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version. Important limitations are
+ * listed in the COPYING file that accompanies this software.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, email srvx-maintainers@srvx.net.
+ *
+ * $Id: spamserv.h,v 1.3 2004/06/27 22:20:00 feigling Exp $
+ */
+
+#ifndef _spamserv_h
+#define _spamserv_h
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+/***********************************************/
+/* Channel */
+/***********************************************/
+
+enum channelinfo
+{
+ ci_SpamLimit = 0,
+ ci_AdvReaction,
+ ci_WarnReaction,
+ ci_Max
+};
+
+#define CHAN_INFO_SIZE (ci_Max + 1)
+#define CHAN_INFO_DEFAULT "bls"
+
+#define CHAN_SPAMSCAN 0x00000001
+#define CHAN_FLOODSCAN 0x00000002
+#define CHAN_JOINFLOOD 0x00000004
+#define CHAN_ADV_SCAN 0x00000008
+#define CHAN_SCAN_CHANOPS 0x00000010
+#define CHAN_SCAN_VOICED 0x00000020
+#define CHAN_SUSPENDED 0x00000040
+
+#define CHAN_FLAGS_DEFAULT (CHAN_SPAMSCAN | CHAN_FLOODSCAN | CHAN_JOINFLOOD)
+
+#define CHECK_SPAM(x) ((x)->flags & CHAN_SPAMSCAN)
+#define CHECK_FLOOD(x) ((x)->flags & CHAN_FLOODSCAN)
+#define CHECK_JOINFLOOD(x) ((x)->flags & CHAN_JOINFLOOD)
+#define CHECK_ADV(x) ((x)->flags & CHAN_ADV_SCAN)
+#define CHECK_CHANOPS(x) ((x)->flags & CHAN_SCAN_CHANOPS)
+#define CHECK_VOICED(x) ((x)->flags & CHAN_SCAN_VOICED)
+#define CHECK_SUSPENDED(x) ((x)->flags & CHAN_SUSPENDED)
+
+struct chanInfo
+{
+ struct chanNode *channel;
+ struct string_list *exceptions;
+ unsigned int flags : 30;
+ unsigned int exceptlevel;
+ char info[CHAN_INFO_SIZE];
+ time_t suspend_expiry;
+};
+
+/***********************************************/
+/* User */
+/***********************************************/
+
+#define USER_KICK 0x00000001
+#define USER_KICKBAN 0x00000002
+#define USER_SHORT_TBAN 0x00000004
+#define USER_LONG_TBAN 0x00000008
+#define USER_KILL 0x00000010
+#define USER_GLINE 0x00000020
+#define USER_WARNED 0x00000040
+#define USER_KILLED 0x00000080
+#define USER_ADV_WARNED 0x00000100
+
+#define CHECK_KICK(x) ((x)->flags & USER_KICK)
+#define CHECK_KICKBAN(x) ((x)->flags & USER_KICKBAN)
+#define CHECK_SHORT_TBAN(x) ((x)->flags & USER_SHORT_TBAN)
+#define CHECK_LONG_TBAN(x) ((x)->flags & USER_LONG_TBAN)
+#define CHECK_KILL(x) ((x)->flags & USER_KILL)
+#define CHECK_GLINE(x) ((x)->flags & USER_GLINE)
+#define CHECK_WARNED(x) ((x)->flags & USER_WARNED)
+#define CHECK_KILLED(x) ((x)->flags & USER_KILLED)
+#define CHECK_ADV_WARNED(x) ((x)->flags & USER_ADV_WARNED)
+
+#define SPAM_WARNLEVEL 1
+
+#define FLOOD_TIMEQ_FREQ 5
+#define FLOOD_EXPIRE 5
+#define FLOOD_WARNLEVEL 1
+#define FLOOD_MAX_LINES 8
+
+#define JOINFLOOD_TIMEQ_FREQ 225
+#define JOINFLOOD_EXPIRE 450
+#define JOINFLOOD_MAX 3
+#define JOINFLOOD_B_DURATION 900
+
+#define ADV_TIMEQ_FREQ 300
+#define ADV_EXPIRE 900
+#define ADV_WARNLEVEL 2
+
+#define WARNLEVEL_TIMEQ_FREQ 1800
+#define MAX_WARNLEVEL 6
+
+#define KILL_TIMEQ_FREQ 450
+#define KILL_EXPIRE 1800
+#define KILL_WARNLEVEL 3
+
+struct spamNode
+{
+ struct chanNode *channel;
+ unsigned long crc32;
+ unsigned int count;
+ struct spamNode *prev;
+ struct spamNode *next;
+};
+
+struct floodNode
+{
+ struct chanNode *channel;
+ struct userNode *owner;
+ unsigned int count;
+ time_t time;
+ struct floodNode *prev;
+ struct floodNode *next;
+};
+
+struct killNode
+{
+ unsigned int warnlevel;
+ time_t time;
+};
+
+struct userInfo
+{
+ struct userNode *user;
+ struct spamNode *spam;
+ struct floodNode *flood;
+ struct floodNode *joinflood;
+ unsigned int flags : 30;
+ unsigned int warnlevel;
+ time_t lastadv;
+};
+
+/***********************************************/
+/* Other Stuff */
+/***********************************************/
+
+enum cs_unreg
+{
+ manually,
+ expire,
+ lost_all_users
+};
+
+void init_spamserv(const char *nick);
+struct chanInfo *get_chanInfo(const char *channelname);
+void spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text);
+void spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason);
+int spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move);
+void spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason);
+
+#endif
\ No newline at end of file
--- /dev/null
+"<INDEX>" ("$b$X Help$b",
+ "The $b$X$b service checks the channel for spam, flood, joinflood and disallowed advertisements.",
+ "$bUser Commands:$b",
+ " ADDEXCEPTION Adds a word to the exception list.",
+ " DELEXCEPTION Deletes a word from the exception list.",
+ " SET Changes various channel settings.",
+ " STATUS Shows general information about $X.",
+ " VERSION Prints the srvx and $X version information.",
+ "$bStaff Commands:$b",
+ " REGISTER Registers a new channel.",
+ " UNREGISTER Removes $X from a registered channel.");
+"ADDEXCEPTION" ("/msg $X ADDEXCEPTION [word]",
+ "Without an argument, it will show all existing exceptions.",
+ "With an argument, it will add the given word to the exception list.",
+ "$X checks, if one of the words in the sentence of a user is in the exception list; if so, $X will not punish the user, doesn't matter, if it's a bad advertisement.",
+ "This means, you have to make sure, all exceptions are adequate.",
+ "$bFirst example$b: You added the word \"gamesurge.net\" to the exception list and someone posts \"www.gamesurge.net/aup\", he won't get punished.",
+ "$bSecond example$b: You added the word \"support\" to the list and someone tells another person to join #support, he won't get punished.",
+ "$bThird example$b: You added \"GameSurge\" to the list and someone posts \"JOIN #channelxyz on GameSurge\", he will NOT get punished, because the word \"GameSurge\" is in the sentence.",
+ "If he would say \"JOIN #channelxyz\", $X would punish him.",
+ "$uSee Also:$u delexception");
+"DELEXCEPTION" ("/msg $X DELEXCEPTION",
+ "Without an argument, it will show all existing exceptions.",
+ "With an argument, it will delete the given word from the exception list.",
+ "$uSee Also:$u addexception");
+"SET" ("/msg $X SET <#channel> [<parameter> [setting]]",
+ "This command will set various channel options. With no arguments, it will show the current values of all channel options.",
+ "Only channel owners and coowners may change settings.",
+ "SPAMLIMIT: Number of equal lines, a user may send.",
+ "ADVREACTION: What happens when someone advertises after warning.",
+ "WARNREACTION: What happens when someone continues spamming/flooding after warning.",
+ "ADVSCAN: Enables/Disables scanning for advertisements.",
+ "SPAMSCAN: Enables/Disables scanning for spam.",
+ "FLOODSCAN: Enables/Disables scanning for flood.",
+ "JOINFLOODSCAN: Enables/Disables scanning for joinflood.",
+ "SCANCHANOPS: Indicates whether $X has to scan messages from channel ops.",
+ "SCANVOICED: Indicates whether $X has to scan messages from voiced users.",
+ "$uSee Also:$u set spamlimit, set advreaction, set warnreaction, set advscan, set spamscan, set floodscan, set joinfloodscan, set scanchanops, set scanvoiced");
+"SET SPAMLIMIT" ("/msg $X SET <#channel> SPAMLIMIT <value>",
+ "You can specify the number of equal messages, a user may send. Valid settings are:",
+ "$b0$b Users may send the same message $b2$b times.",
+ "$b1$b Users may send the same message $b3$b times.",
+ "$b2$b Users may send the same message $b4$b times.",
+ "$b3$b Users may send the same message $b5$b times.",
+ "$b4$b Users may send the same message $b6$b times.",
+ "$uSee Also:$u set spamscan");
+"SET ADVREACTION" ("/msg $X SET <#channel> ADVREACTION <value>",
+ "This setting controls what happens to those who send disallowed advertisements to the channel after a warning:",
+ "$b0$b Kick on disallowed advertising.",
+ "$b1$b Kickban on disallowed advertising.",
+ "$b2$b Short timed ban (default: 15 minutes) on disallowed advertising.",
+ "$b3$b Long timed ban (default: 1 hour) on disallowed advertising.",
+ "$b4$b Kill on disallowed advertising. Only settable by irc operators.",
+ "$uSee Also:$u set advscan");
+"SET WARNREACTION" ("/msg $X SET <#channel> WARNREACTION <value>",
+ "This setting controls what happens to those who spam or flood the channel after a warning:",
+ "$b0$b Kick after warning.",
+ "$b1$b Kickban after warning.",
+ "$b2$b Short timed ban (default: 15 minutes) after warning.",
+ "$b3$b Long timed ban (default: 1 hour) after warning.",
+ "$b4$b Kill after warning. Only settable by irc operators.",
+ "$uSee Also:$u set spamscan, set floodscan");
+"SET ADVSCAN" ("/msg $X SET <#channel> ADVSCAN <1/0>",
+ "If this setting is enabled, $X checks all messages for advertisements.",
+ "Advertisements are: www.*, http:*, ftp.*, ftp:* and #*; e.g. #srvx, http://www.srvx.net etc ..");
+"SET SPAMSCAN" ("/msg $X SET <#channel> SPAMSCAN <1/0>",
+ "If this setting is enabled, $X checks all incoming channel messages for spam.",
+ "Posting the same message multiple times is considered as spam, which means, if someone posts the same message more than the number of times, which is allowed (/msg $X set SPAMLIMIT), $X will punish him.");
+"SET FLOODSCAN" ("/msg $X SET <#channel> FLOODSCAN <1/0>",
+ "If this setting is enabled, $X checks, if a person tries to flood the channel.",
+ "Posting messages in a small amount of time is considered as flood, so if someone tries to flood the channel, $X will punish him.");
+"SET JOINFLOODSCAN" ("/msg $X SET <#channel> JOINFLOODSCAN <1/0>",
+ "If this setting is enabled, $X checks, if a person joins the channel more than one time.",
+ "Normally users join a channel and stay in the channel or part and do not rejoin after a few seconds.",
+ "If they want to cause trouble, they join/part the channel very often. $X will punish every user, who does that.");
+"SET SCANCHANOPS" ("/msg $X SET <#channel> SCANCHANOPS <1/0>",
+ "If this setting is disabled, $X doesn't check messages from oped users for spam, flood and advertisements.");
+"SET SCANVOICED" ("/msg $X SET <#channel> SCANVOICED <1/0>",
+ "If this setting is disabled, $X doesn't check messages from voiced users for spam, flood and advertisements.");
+"REGISTER" ("/msg $X REGISTER <#channel>",
+ "Registers a channel with $X.",
+ "The Channel must be registered with $C and may not be suspended.",
+ "$uSee Also:$u unregister");
+"STATUS" ("/msg $X STATUS [MEMORY|CHANNELS]",
+ "$bSTATUS$b shows you general information about $X. An irc operator can get information about the memory usage and a list of all registered channels.");
+"UNREGISTER" ("/msg $X UNREGISTER <#channel> [CONFIRM]",
+ "Removes $X from the given channel.",
+ "If you are not network staff, you must add $bCONFIRM$b to the end of your line to confirm unregistration.",
+ "$bSee Also:$b register");
+"VERSION" ("/msg $X VERSION",
+ "$bVERSION$b causes $X to send you the srvx version and some additional version information about $X.");
\ No newline at end of file
"lc_h" "800"; // specifically lower case h
"uc_H" "800"; // .. and upper case H
"S" "999";
- "b" "1";
+ "b" "800";
+ "I" "800";
};
// and for who can change epithets for staff
"set_epithet_level" "800";
// how long must an account be inactive so it can be ounregistered without force?
"ounregister_inactive" "1M";
// which flags on an account require the ounregister to be used with force?
- "ounregister_flags" "ShgsfnHbu";
+ "ounregister_flags" "ShgsfnHbuI";
/* "require_qualified" has been removed. It is now
* integrated into the modcmd command authorization
* and dispatch mechanism. "/msg OpServ help modcmd"
// who to tell about staff auths?
"staff_auth_channel" "#opserv";
"staff_auth_channel_modes" "+tinms";
+ // Force Opers to be in staff_auth_channel
+ // 0 = don't force opers to be in the channel
+ // 1 = force opers to be in the channel
+ // 2 = force opers to be in the channel but kick them if they get mode -o set (deoper)
+ "staff_auth_force_opers" "2";
// how many clones to allow from an untrusted host?
"untrusted_max" "4";
// how long of a g-line should be issued if the max hosts is exceeded?
"size" "200";
"drain-rate" "3";
};
+
};
"chanserv" {
// How often to look for dnrs that have expired?
"dnr_expire_freq" "1h";
// what !set options should we show when user calls "!set" with no arguments?
- "set_shows" ("DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", "PubCmd", "InviteMe", "StrictOp", "AutoOp", "EnfModes", "EnfTopic", "TopicSnarf", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", "Setters", "CtcpUser", "CtcpReaction", "Protect", "Toys", "DynLimit", "NoDelete");
+ "set_shows" ("DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", "PubCmd", "InviteMe", "EnfModes", "EnfTopic", "TopicSnarf", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", "Setters", "CtcpUsers", "CtcpReaction", "Protect", "Toys", "DynLimit", "NoDelete");
// A list of !8ball responses
"8ball" ("Not a chance.",
"max_owned" "5";
// how long between automatic topic refreshes with TopicRefresh 0
"refresh_period" "3h";
+ // how long between two invites of an user
+ "invite_timeout" "10s";
// what should !access say for various staff?
"irc_operator_epithet" "a megalomaniacal power hungry tyrant";
"network_helper_epithet" "a wannabe tyrant";
"nodelete_level" "1";
// how long before a new channel owner can give ownership away?
"giveownership_timeout" "1w";
+ // message sent to new channels
+ "new_channel_unauthed_join" ""; //only sent if the user is unauthed
+ "new_channel_authed_join" ""; //only sent if the user is authed
+ "new_channel_message" ""; //always after the message above
+
};
"global" {
// should users get community announcements by default or not?
"announcements_default" "on";
};
+
+ "spamserv" {
+ // You may disable a service by removing its "nick" config item.
+ // That means: SERVICES WILL ONLY WORK IF YOU DEFINE THEIR NICK.
+ // (This is changed relative srvx-1.0.x, which would use default
+ // unless you specified ".disabled".)
+ "nick" "SpamServ";
+ "debug_channel" "#spamserv";
+ // url of the network rules. if you don't have network rules, remove this key.
+ "network_rules" "http://www.gamesurge.net/aup";
+ // trigger for spamserv; remove this key to disable the trigger
+ "trigger" "-";
+ // ban duration of a short timedban.
+ "short_ban_duration" "15m";
+ // ban duration of a long timedban.
+ "long_ban_duration" "1h";
+ // duration of a gline. SpamServ will issue it after several violations and a kill.
+ "gline_duration" "1h";
+ // users may add "exception_max" exceptions to the list. IRCOps can override "exception_max".
+ "exception_max" "10";
+ // minimum & maximum length of an exception.
+ "exception_min_len" "4";
+ "exception_max_len" "12";
+ // if someone advertises a channel, which doesn't exist (channel is empty, no users),
+ // SpamServ doesn't punish the user.
+ // enable this setting, if SpamServ has to ignore advertisements of channels, which do not exist.
+ // disable this setting, if SpamServ has to punish the users whenever they advertise.
+ "adv_chan_must_exist" "0";
+ // remove all mirc codes from messages before checking for advertisements.
+ // if this setting is disabled and someone spams a url which
+ // contains a bold char, SpamServ doesn't punish him.
+ "strip_mirc_codes" "1";
+ // enable this, if SpamServ has to "follow" ChanServ, when a channel moves or merges.
+ // disable it, if it shouldn't be possible to move or merge SpamServ with /msg chanserv move|merge.
+ "allow_move_merge" "1";
+ };
};
// The modules section gives configuration information to optional modules.
// "search" ("example.org", "example.net");
// "nameservers" ("127.0.0.1");
};
+ "watchdog" {
+ "nick" "Watchdog";
+ "modes" "+iok";
+ "ban_duration" "2h"; //only if the channel is registered with chanserv
+ "gline_duration" "1h";
+ "punishment_reason" "Your message contained a forbidden word.";
+ };
};
"policers" {
"NickServ" { "mondo_section" "NickServ"; };
"OpServ" { "mondo_section" "OpServ"; };
"sendmail" { "mondo_section" "sendmail"; };
+ "SpamServ" { "mondo_section" "SpamServ"; };
+ "Watchdog" { "mondo_section" "Watchdog"; };
// These are the options if you want a database to be in its own file.
"mondo" {