mirror of
https://github.com/NishiOwO/ircservices5.git
synced 2025-04-21 16:54:38 +00:00
932 lines
30 KiB
C
932 lines
30 KiB
C
/* Session limiting module.
|
|
* Based on code copyright (c) 1999-2000 Andrew Kempe (TheShadow)
|
|
* E-mail: <andrewk@isdial.net>
|
|
*
|
|
* IRC Services is copyright (c) 1996-2009 Andrew Church.
|
|
* E-mail: <achurch@achurch.org>
|
|
* Parts written by Andrew Kempe and others.
|
|
* This program is free but copyrighted software; see the file GPL.txt for
|
|
* details.
|
|
*/
|
|
|
|
#include "services.h"
|
|
#include "modules.h"
|
|
#include "conffile.h"
|
|
#include "commands.h"
|
|
#include "databases.h"
|
|
#include "language.h"
|
|
|
|
#include "operserv.h"
|
|
#include "maskdata.h"
|
|
#include "akill.h"
|
|
|
|
/*************************************************************************/
|
|
|
|
/* SESSION LIMITING
|
|
*
|
|
* The basic idea of session limiting is to prevent one host from having more
|
|
* than a specified number of sessions (client connections/clones) on the
|
|
* network at any one time. To do this we have a list of sessions and
|
|
* exceptions. Each session structure records information about a single host,
|
|
* including how many clients (sessions) that host has on the network. When a
|
|
* host reaches it's session limit, no more clients from that host will be
|
|
* allowed to connect.
|
|
*
|
|
* When a client connects to the network, we check to see if their host has
|
|
* reached the default session limit per host, and thus whether it is allowed
|
|
* any more. If it has reached the limit, we kill the connecting client; all
|
|
* the other clients are left alone. Otherwise we simply increment the counter
|
|
* within the session structure. When a client disconnects, we decrement the
|
|
* counter. When the counter reaches 0, we free the session.
|
|
*
|
|
* Exceptions allow one to specify custom session limits for a specific host
|
|
* or a range thereof. The first exception that the host matches is the one
|
|
* used.
|
|
*
|
|
* "Session Limiting" is likely to slow down services when there are frequent
|
|
* client connects and disconnects. The size of the exception list can also
|
|
* play a large role in this performance decrease. It is therefore recommened
|
|
* that you keep the number of exceptions to a minimum. A very simple hashing
|
|
* method is currently used to store the list of sessions. I'm sure there is
|
|
* room for improvement and optimisation of this, along with the storage of
|
|
* exceptions. Comments and suggestions are more than welcome!
|
|
*
|
|
* -TheShadow (02 April 1999)
|
|
*/
|
|
|
|
/*************************************************************************/
|
|
|
|
static Module *module_operserv;
|
|
static Module *module_akill;
|
|
|
|
/* create_kill() imported from operserv/akill */
|
|
static void (*p_create_akill)(char *mask, const char *reason, const char *who,
|
|
time_t expiry);
|
|
|
|
static int WallOSException;
|
|
static int WallExceptionExpire;
|
|
static int32 DefSessionLimit;
|
|
static time_t ExceptionExpiry;
|
|
static int32 MaxSessionLimit;
|
|
static char * SessionLimitExceeded;
|
|
static char * SessionLimitDetailsLoc;
|
|
static int SessionLimitAutokill;
|
|
static time_t SessionLimitMinKillTime;
|
|
static int32 SessionLimitMaxKillCount;
|
|
static time_t SessionLimitAutokillExpiry;
|
|
static char * SessionLimitAutokillReason;
|
|
|
|
/*************************************************************************/
|
|
|
|
typedef struct session_ Session;
|
|
struct session_ {
|
|
Session *prev, *next;
|
|
char *host;
|
|
int count; /* Number of clients with this host */
|
|
int killcount; /* Number of kills for this session */
|
|
time_t lastkill; /* Time of last kill */
|
|
};
|
|
|
|
#define HASH_STATIC static
|
|
#include "hash.h"
|
|
#define add_session _add_session
|
|
#define del_session _del_session
|
|
DEFINE_HASH(session, Session, host)
|
|
#undef del_session
|
|
#undef add_session
|
|
|
|
static void do_session(User *u);
|
|
static void do_exception(User *u);
|
|
|
|
static Command cmds[] = {
|
|
{"SESSION", do_session, is_services_oper, OPER_HELP_SESSION, -1,-1},
|
|
{"EXCEPTION", do_exception, is_services_oper, OPER_HELP_EXCEPTION, -1,-1},
|
|
{NULL}
|
|
};
|
|
|
|
/*************************************************************************/
|
|
/************************* Session list display **************************/
|
|
/*************************************************************************/
|
|
|
|
/* Syntax: SESSION LIST threshold
|
|
* Lists all sessions with atleast threshold clients.
|
|
* The threshold value must be greater than 1. This is to prevent
|
|
* accidental listing of the large number of single client sessions.
|
|
*
|
|
* Syntax: SESSION VIEW host
|
|
* Displays detailed session information about the supplied host.
|
|
*/
|
|
|
|
static void do_session(User *u)
|
|
{
|
|
Session *session;
|
|
MaskData *exception;
|
|
const char *cmd = strtok(NULL, " ");
|
|
const char *param1 = strtok(NULL, " ");
|
|
int mincount;
|
|
|
|
if (!cmd)
|
|
cmd = "";
|
|
|
|
if (stricmp(cmd, "LIST") == 0) {
|
|
if (!param1) {
|
|
syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_LIST_SYNTAX);
|
|
|
|
} else if ((mincount = atoi(param1)) <= 1) {
|
|
notice_lang(s_OperServ, u, OPER_SESSION_INVALID_THRESHOLD);
|
|
|
|
} else {
|
|
notice_lang(s_OperServ, u, OPER_SESSION_LIST_HEADER, mincount);
|
|
notice_lang(s_OperServ, u, OPER_SESSION_LIST_COLHEAD);
|
|
for (session = first_session(); session; session = next_session()){
|
|
if (session->count >= mincount)
|
|
notice_lang(s_OperServ, u, OPER_SESSION_LIST_FORMAT,
|
|
session->count, session->host);
|
|
}
|
|
}
|
|
} else if (stricmp(cmd, "VIEW") == 0) {
|
|
if (!param1) {
|
|
syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_VIEW_SYNTAX);
|
|
} else {
|
|
session = get_session(param1);
|
|
if (!session) {
|
|
notice_lang(s_OperServ, u, OPER_SESSION_NOT_FOUND, param1);
|
|
} else {
|
|
exception = get_matching_maskdata(MD_EXCEPTION, param1);
|
|
notice_lang(s_OperServ, u, OPER_SESSION_VIEW_FORMAT,
|
|
param1, session->count,
|
|
exception ? exception->limit : DefSessionLimit);
|
|
put_maskdata(exception);
|
|
}
|
|
}
|
|
} else {
|
|
syntax_error(s_OperServ, u, "SESSION", OPER_SESSION_SYNTAX);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/********************* Internal session functions ************************/
|
|
/*************************************************************************/
|
|
|
|
/* Free a session structure. Separate from del_session() because the
|
|
* module cleanup code also uses it.
|
|
*/
|
|
|
|
static inline void free_session(Session *session)
|
|
{
|
|
free(session->host);
|
|
free(session);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Attempt to add a host to the session list. If the addition of the new host
|
|
* causes the the session limit to be exceeded, kill the connecting user.
|
|
* Returns 1 if the host was added or 0 if the user was killed.
|
|
*/
|
|
|
|
static int add_session(const char *nick, const char *host)
|
|
{
|
|
Session *session;
|
|
MaskData *exception;
|
|
int sessionlimit = 0;
|
|
char buf[BUFSIZE];
|
|
time_t now = time(NULL);
|
|
|
|
session = get_session(host);
|
|
|
|
if (session) {
|
|
exception = get_matching_maskdata(MD_EXCEPTION, host);
|
|
sessionlimit = exception ? exception->limit : DefSessionLimit;
|
|
put_maskdata(exception);
|
|
|
|
if (sessionlimit != 0 && session->count >= sessionlimit) {
|
|
if (SessionLimitExceeded)
|
|
notice(s_OperServ, nick, SessionLimitExceeded, host);
|
|
if (SessionLimitDetailsLoc)
|
|
notice(s_OperServ, nick, SessionLimitDetailsLoc);
|
|
|
|
if (SessionLimitAutokill && module_akill) {
|
|
if (now <= session->lastkill + SessionLimitMinKillTime) {
|
|
session->killcount++;
|
|
if (session->killcount >= SessionLimitMaxKillCount) {
|
|
snprintf(buf, sizeof(buf), "*@%s", host);
|
|
p_create_akill(buf,SessionLimitAutokillReason,
|
|
s_OperServ,
|
|
now + SessionLimitAutokillExpiry);
|
|
session->killcount = 0;
|
|
}
|
|
} else {
|
|
session->killcount = 1;
|
|
}
|
|
session->lastkill = now;
|
|
}
|
|
|
|
/* We don't use kill_user() because a user stucture has not yet
|
|
* been created. Simply kill the user. -TheShadow */
|
|
send_cmd(s_OperServ, "KILL %s :%s (Session limit exceeded)",
|
|
nick, s_OperServ);
|
|
return 0;
|
|
} else {
|
|
session->count++;
|
|
return 1;
|
|
}
|
|
/* not reached */
|
|
}
|
|
|
|
/* Session does not exist, so create it */
|
|
session = scalloc(sizeof(Session), 1);
|
|
session->host = sstrdup(host);
|
|
session->count = 1;
|
|
session->killcount = 0;
|
|
session->lastkill = 0;
|
|
_add_session(session);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void del_session(const char *host)
|
|
{
|
|
Session *session;
|
|
|
|
module_log_debug(2, "del_session() called");
|
|
|
|
session = get_session(host);
|
|
if (!session) {
|
|
wallops(s_OperServ,
|
|
"WARNING: Tried to delete non-existent session: \2%s", host);
|
|
module_log("Tried to delete non-existent session: %s", host);
|
|
return;
|
|
}
|
|
|
|
if (session->count > 1) {
|
|
session->count--;
|
|
return;
|
|
}
|
|
_del_session(session);
|
|
module_log_debug(2, "del_session(): free session structure");
|
|
free_session(session);
|
|
|
|
module_log_debug(2, "del_session() done");
|
|
}
|
|
|
|
|
|
/*************************************************************************/
|
|
/************************ Exception manipulation *************************/
|
|
/*************************************************************************/
|
|
|
|
/* Syntax: EXCEPTION ADD [+expiry] mask limit reason
|
|
* Adds mask to the exception list with limit as the maximum session
|
|
* limit and +expiry as an optional expiry time.
|
|
*
|
|
* Syntax: EXCEPTION DEL mask
|
|
* Deletes the first exception that matches mask exactly.
|
|
*
|
|
* Syntax: EXCEPTION MOVE num newnum
|
|
* Moves the exception with number num to have number newnum.
|
|
*
|
|
* Syntax: EXCEPTION LIST [mask]
|
|
* Lists all exceptions or those matching mask.
|
|
*
|
|
* Syntax: EXCEPTION VIEW [mask]
|
|
* Displays detailed information about each exception or those matching
|
|
* mask.
|
|
*
|
|
* Syntax: EXCEPTION CHECK host
|
|
* Displays masks which the given host matches.
|
|
*
|
|
* Syntax: EXCEPTION COUNT
|
|
* Displays the number of entries in the exception list.
|
|
*/
|
|
|
|
static void do_exception_add(User *u);
|
|
static void do_exception_del(User *u);
|
|
static void do_exception_clear(User *u);
|
|
static void do_exception_move(User *u);
|
|
static void do_exception_list(User *u, int is_view);
|
|
static void do_exception_check(User *u);
|
|
static int exception_del_callback(int num, va_list args);
|
|
static int exception_list(User *u, MaskData *except, int *count, int skip,
|
|
int is_view);
|
|
static int exception_list_callback(int num, va_list args);
|
|
|
|
static void do_exception(User *u)
|
|
{
|
|
const char *cmd = strtok(NULL, " ");
|
|
|
|
if (!cmd)
|
|
cmd = "";
|
|
|
|
if (stricmp(cmd, "ADD") == 0) {
|
|
do_exception_add(u);
|
|
} else if (stricmp(cmd, "DEL") == 0) {
|
|
do_exception_del(u);
|
|
} else if (stricmp(cmd, "CLEAR") == 0) {
|
|
do_exception_clear(u);
|
|
} else if (stricmp(cmd, "MOVE") == 0) {
|
|
do_exception_move(u);
|
|
} else if (stricmp(cmd, "LIST") == 0 || stricmp(cmd, "VIEW") == 0) {
|
|
do_exception_list(u, stricmp(cmd,"VIEW")==0);
|
|
} else if (stricmp(cmd, "CHECK") == 0) {
|
|
do_exception_check(u);
|
|
} else if (stricmp(cmd, "COUNT") == 0) {
|
|
int count = maskdata_count(MD_EXCEPTION);
|
|
if (count)
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_COUNT, count);
|
|
else
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_EMPTY);
|
|
} else {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_SYNTAX);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_exception_add(User *u)
|
|
{
|
|
char *mask, *reason, *expiry, *limitstr;
|
|
time_t expires;
|
|
int limit, i;
|
|
MaskData *except;
|
|
time_t now = time(NULL);
|
|
|
|
if (maskdata_count(MD_EXCEPTION) >= MAX_MASKDATA) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_TOO_MANY);
|
|
return;
|
|
}
|
|
|
|
mask = strtok(NULL, " ");
|
|
if (mask && *mask == '+') {
|
|
expiry = mask+1;
|
|
mask = strtok(NULL, " ");
|
|
} else {
|
|
expiry = NULL;
|
|
}
|
|
limitstr = strtok(NULL, " ");
|
|
reason = strtok_remaining();
|
|
|
|
if (!mask || !limitstr || !reason) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_ADD_SYNTAX);
|
|
return;
|
|
}
|
|
|
|
expires = expiry ? dotime(expiry) : ExceptionExpiry;
|
|
if (expires < 0) {
|
|
notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
|
|
return;
|
|
} else if (expires > 0) {
|
|
expires += now;
|
|
}
|
|
|
|
limit = (limitstr && isdigit(*limitstr)) ? atoi(limitstr) : -1;
|
|
|
|
if (limit < 0 || limit > MaxSessionLimit) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_INVALID_LIMIT,
|
|
MaxSessionLimit);
|
|
return;
|
|
|
|
} else if (strchr(mask, '!') || strchr(mask, '@')) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_INVALID_HOSTMASK);
|
|
return;
|
|
} else if (put_maskdata(get_maskdata(MD_EXCEPTION, strlower(mask)))) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_ALREADY_PRESENT, mask);
|
|
} else {
|
|
/* Get highest maskdata number */
|
|
i = 0;
|
|
for (except = first_maskdata(MD_EXCEPTION); except;
|
|
except = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
if (except->num > i) /* should be in order, but let's be safe */
|
|
i = except->num;
|
|
}
|
|
/* Add exception with next number */
|
|
except = scalloc(1, sizeof(*except));
|
|
except->mask = sstrdup(mask);
|
|
except->limit = limit;
|
|
except->reason = sstrdup(reason);
|
|
except->time = now;
|
|
strbcpy(except->who, u->nick);
|
|
except->expires = expires;
|
|
except->num = i+1;
|
|
add_maskdata(MD_EXCEPTION, except);
|
|
if (WallOSException) {
|
|
char buf[BUFSIZE];
|
|
expires_in_lang(buf, sizeof(buf), NULL, expires);
|
|
wallops(s_OperServ, "%s added a session limit exception of"
|
|
" \2%d\2 for \2%s\2 (%s)", u->nick, limit, mask, buf);
|
|
}
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_ADDED, mask, limit);
|
|
if (readonly)
|
|
notice_lang(s_OperServ, u, READ_ONLY_MODE);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_exception_del(User *u)
|
|
{
|
|
char *mask;
|
|
MaskData *except;
|
|
int deleted = 0;
|
|
|
|
mask = strtok(NULL, " ");
|
|
if (!mask) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_DEL_SYNTAX);
|
|
return;
|
|
}
|
|
if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
|
|
int count, last = -1;
|
|
deleted = process_numlist(mask, &count, exception_del_callback, &last);
|
|
if (deleted == 0) {
|
|
if (count == 1) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_SUCH_ENTRY, last);
|
|
} else {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_MATCH);
|
|
}
|
|
} else if (deleted == 1) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED_ONE);
|
|
} else {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED_SEVERAL,
|
|
deleted);
|
|
}
|
|
} else {
|
|
for (except = first_maskdata(MD_EXCEPTION); except;
|
|
except = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
if (stricmp(mask, except->mask) == 0) {
|
|
del_maskdata(MD_EXCEPTION, except);
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_DELETED, mask);
|
|
deleted = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (deleted == 0)
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_NOT_FOUND, mask);
|
|
}
|
|
if (deleted && readonly)
|
|
notice_lang(s_OperServ, u, READ_ONLY_MODE);
|
|
|
|
/* Renumber the exception list. I don't believe in having holes in
|
|
* lists - it makes code more complex, harder to debug and we end up
|
|
* with huge index numbers. Imho, fixed numbering is only beneficial
|
|
* when one doesn't have range capable manipulation. -TheShadow */
|
|
|
|
/* That works fine up until two people do deletes at the same time
|
|
* and shoot themselves in the collective foot; and just because
|
|
* you have range handling doesn't mean someone won't do "DEL 5"
|
|
* followed by "DEL 7" and, again, shoot themselves in the foot.
|
|
* Besides, there's nothing wrong with complexity if it serves a
|
|
* purpose. Removed. --AC */
|
|
}
|
|
|
|
|
|
static int exception_del_callback(int num, va_list args)
|
|
{
|
|
MaskData *except;
|
|
int *last = va_arg(args, int *);
|
|
|
|
*last = num;
|
|
if ((except = get_exception_by_num(num)) != NULL) {
|
|
del_maskdata(MD_EXCEPTION, except);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_exception_clear(User *u)
|
|
{
|
|
char *mask;
|
|
MaskData *except;
|
|
|
|
mask = strtok(NULL, " ");
|
|
if (!mask || stricmp(mask,"ALL") != 0) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_CLEAR_SYNTAX);
|
|
return;
|
|
}
|
|
for (except = first_maskdata(MD_EXCEPTION); except;
|
|
except = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
del_maskdata(MD_EXCEPTION, except);
|
|
}
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_CLEARED);
|
|
if (readonly)
|
|
notice_lang(s_OperServ, u, READ_ONLY_MODE);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_exception_move(User *u)
|
|
{
|
|
MaskData *except;
|
|
char *n1str = strtok(NULL, " "); /* From index */
|
|
char *n2str = strtok(NULL, " "); /* To index */
|
|
int n1, n2;
|
|
|
|
if (!n1str || !n2str) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_MOVE_SYNTAX);
|
|
return;
|
|
}
|
|
n1 = atoi(n1str);
|
|
n2 = atoi(n2str);
|
|
if (n1 == n2 || n1 <= 0 || n2 <= 0) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_MOVE_SYNTAX);
|
|
return;
|
|
}
|
|
if (!(except = get_exception_by_num(n1))) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_NO_SUCH_ENTRY, n1);
|
|
return;
|
|
}
|
|
except = move_exception(except, n2);
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_MOVED,
|
|
except->mask, n1, n2);
|
|
put_maskdata(except);
|
|
if (readonly)
|
|
notice_lang(s_OperServ, u, READ_ONLY_MODE);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_exception_list(User *u, int is_view)
|
|
{
|
|
char *mask = strtok(NULL, " ");
|
|
char *s;
|
|
MaskData *except;
|
|
int count = 0, skip = 0;
|
|
int noexpire = 0;
|
|
|
|
|
|
if (mask && *mask == '+') {
|
|
skip = (int)atolsafe(mask+1, 0, INT_MAX);
|
|
if (skip < 0) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION",
|
|
OPER_EXCEPTION_LIST_SYNTAX);
|
|
return;
|
|
}
|
|
mask = strtok(NULL, " ");
|
|
}
|
|
|
|
if (mask)
|
|
strlower(mask);
|
|
|
|
if ((s = strtok(NULL," ")) != NULL && stricmp(s,"NOEXPIRE") == 0)
|
|
noexpire = 1;
|
|
|
|
if (mask && strspn(mask, "1234567890,-") == strlen(mask)) {
|
|
if (skip) {
|
|
syntax_error(s_OperServ, u, "EXCEPTION",
|
|
OPER_EXCEPTION_LIST_SYNTAX);
|
|
return;
|
|
}
|
|
process_numlist(mask, NULL, exception_list_callback, u,
|
|
&count, skip, noexpire, is_view);
|
|
} else {
|
|
for (except = first_maskdata(MD_EXCEPTION); except;
|
|
except = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
if ((!mask || match_wild(mask, except->mask))
|
|
&& (!noexpire || !except->expires))
|
|
exception_list(u, except, &count, skip, is_view);
|
|
}
|
|
}
|
|
if (!count)
|
|
notice_lang(s_OperServ, u,
|
|
mask ? OPER_EXCEPTION_NO_MATCH : OPER_EXCEPTION_EMPTY);
|
|
}
|
|
|
|
|
|
static int exception_list(User *u, MaskData *except, int *count, int skip,
|
|
int is_view)
|
|
{
|
|
if (!*count) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_HEADER);
|
|
if (!is_view)
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_COLHEAD);
|
|
}
|
|
(*count)++;
|
|
if (*count > skip && *count <= skip+ListMax) {
|
|
if (is_view) {
|
|
char timebuf[BUFSIZE], expirebuf[BUFSIZE];
|
|
strftime_lang(timebuf, sizeof(timebuf), u->ngi,
|
|
STRFTIME_SHORT_DATE_FORMAT, except->time);
|
|
expires_in_lang(expirebuf, sizeof(expirebuf), u->ngi,
|
|
except->expires);
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_VIEW_FORMAT,
|
|
except->num, except->mask,
|
|
*except->who ? except->who : "<unknown>",
|
|
timebuf, expirebuf, except->limit, except->reason);
|
|
} else { /* list */
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_LIST_FORMAT,
|
|
except->num, except->limit, except->mask);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
static int exception_list_callback(int num, va_list args)
|
|
{
|
|
User *u = va_arg(args, User *);
|
|
int *count = va_arg(args, int *);
|
|
int skip = va_arg(args, int);
|
|
int noexpire = va_arg(args, int);
|
|
int is_view = va_arg(args, int);
|
|
MaskData *except;
|
|
int retval = 0;
|
|
|
|
if ((except = get_exception_by_num(num)) != NULL) {
|
|
if (!noexpire || !except->expires)
|
|
retval = exception_list(u, except, count, skip, is_view);
|
|
put_maskdata(except);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static void do_exception_check(User *u)
|
|
{
|
|
char *host;
|
|
MaskData *except;
|
|
int sent_header = 0, count = 0;
|
|
|
|
host = strtok(NULL, " ");
|
|
if (host) {
|
|
strlower(host);
|
|
} else {
|
|
syntax_error(s_OperServ, u, "EXCEPTION", OPER_EXCEPTION_CHECK_SYNTAX);
|
|
return;
|
|
}
|
|
for (except = first_maskdata(MD_EXCEPTION); except;
|
|
except = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
if (match_wild(except->mask, host)) {
|
|
if (!sent_header) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_CHECK_HEADER, host);
|
|
sent_header = 1;
|
|
}
|
|
notice(s_OperServ, u->nick, " %s", except->mask);
|
|
count++;
|
|
}
|
|
}
|
|
if (sent_header) {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_CHECK_TRAILER, count);
|
|
} else {
|
|
notice_lang(s_OperServ, u, OPER_EXCEPTION_CHECK_NO_MATCH, host);
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************** Callback routines ***************************/
|
|
/*************************************************************************/
|
|
|
|
/* Callback to check session limiting for new users. */
|
|
|
|
static int check_sessions(int ac, char **av)
|
|
{
|
|
return !add_session(av[0], av[4]);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Callback to remove a quitting user's session. */
|
|
|
|
static int remove_session(User *u, char *reason_unused)
|
|
{
|
|
del_session(u->host);
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Callback for exception expiration. */
|
|
|
|
static int do_expire_maskdata(uint32 type, MaskData *md)
|
|
{
|
|
if (type == MD_EXCEPTION) {
|
|
if (WallExceptionExpire)
|
|
wallops(s_OperServ, "Session limit exception for %s has expired",
|
|
md->mask);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/* Callback for OperServ STATS ALL command. */
|
|
|
|
static int do_stats_all(User *u)
|
|
{
|
|
int32 count, mem;
|
|
Session *session;
|
|
MaskData *md;
|
|
|
|
count = mem = 0;
|
|
for (session = first_session(); session; session = next_session()) {
|
|
count++;
|
|
mem += sizeof(*session) + strlen(session->host)+1;
|
|
}
|
|
notice_lang(s_OperServ, u, OPER_STATS_ALL_SESSION_MEM,
|
|
count, (mem+512) / 1024);
|
|
|
|
for (md = first_maskdata(MD_EXCEPTION); md;
|
|
md = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
count++;
|
|
mem += sizeof(*md);
|
|
if (md->mask)
|
|
mem += strlen(md->mask)+1;
|
|
if (md->reason)
|
|
mem += strlen(md->reason)+1;
|
|
}
|
|
notice_lang(s_OperServ, u, OPER_STATS_ALL_EXCEPTION_MEM,
|
|
count, (mem+512) / 1024);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/**************************** Database stuff *****************************/
|
|
/*************************************************************************/
|
|
|
|
static void insert_exception(void *md) {
|
|
MaskData *except;
|
|
int num = 0;
|
|
for (except = first_maskdata(MD_EXCEPTION); except;
|
|
except = next_maskdata(MD_EXCEPTION)
|
|
) {
|
|
if (except->num > num) /* should be in order, but let's be safe */
|
|
num = except->num;
|
|
}
|
|
((MaskData *)md)->num = num+1;
|
|
add_maskdata(MD_EXCEPTION, md);
|
|
}
|
|
static void *first_exception(void)
|
|
{ return first_maskdata(MD_EXCEPTION); }
|
|
static void *next_exception(void)
|
|
{ return next_maskdata(MD_EXCEPTION); }
|
|
|
|
static DBField exception_dbfields[] = {
|
|
{ "mask", DBTYPE_STRING, offsetof(MaskData,mask) },
|
|
{ "limit", DBTYPE_INT16, offsetof(MaskData,limit) },
|
|
{ "reason", DBTYPE_STRING, offsetof(MaskData,reason) },
|
|
{ "who", DBTYPE_BUFFER, offsetof(MaskData,who), NICKMAX },
|
|
{ "time", DBTYPE_TIME, offsetof(MaskData,time) },
|
|
{ "expires", DBTYPE_TIME, offsetof(MaskData,expires) },
|
|
{ "lastused", DBTYPE_TIME, offsetof(MaskData,lastused) },
|
|
{ NULL }
|
|
};
|
|
static DBTable exception_dbtable = {
|
|
.name = "exception",
|
|
.newrec = new_maskdata,
|
|
.freerec = free_maskdata,
|
|
.insert = insert_exception,
|
|
.first = first_exception,
|
|
.next = next_exception,
|
|
.fields = exception_dbfields,
|
|
};
|
|
|
|
/*************************************************************************/
|
|
/***************************** Module stuff ******************************/
|
|
/*************************************************************************/
|
|
|
|
ConfigDirective module_config[] = {
|
|
{ "DefSessionLimit", { { CD_INT, 0, &DefSessionLimit } } },
|
|
{ "ExceptionExpiry", { { CD_TIME, 0, &ExceptionExpiry } } },
|
|
{ "MaxSessionLimit", { { CD_POSINT, 0, &MaxSessionLimit } } },
|
|
{ "SessionLimitAutokill",{{CD_SET, 0, &SessionLimitAutokill },
|
|
{ CD_TIME, 0, &SessionLimitMinKillTime },
|
|
{ CD_POSINT, 0, &SessionLimitMaxKillCount },
|
|
{ CD_TIME, 0, &SessionLimitAutokillExpiry },
|
|
{ CD_STRING, 0, &SessionLimitAutokillReason } } },
|
|
{ "SessionLimitDetailsLoc",{{CD_STRING, 0, &SessionLimitDetailsLoc } } },
|
|
{ "SessionLimitExceeded",{{CD_STRING, 0, &SessionLimitExceeded } } },
|
|
{ "WallExceptionExpire",{{CD_SET, 0, &WallExceptionExpire } } },
|
|
{ "WallOSException", { { CD_SET, 0, &WallOSException } } },
|
|
{ NULL }
|
|
};
|
|
|
|
/*************************************************************************/
|
|
|
|
static int do_load_module(Module *mod, const char *name)
|
|
{
|
|
if (strcmp(name, "operserv/akill") == 0) {
|
|
p_create_akill = get_module_symbol(mod, "create_akill");
|
|
if (p_create_akill)
|
|
module_akill = mod;
|
|
else
|
|
module_log("Symbol `create_akill' not found, automatic autokill"
|
|
" addition will not be available");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
static int do_unload_module(Module *mod)
|
|
{
|
|
if (mod == module_akill) {
|
|
p_create_akill = NULL;
|
|
module_akill = NULL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
int init_module()
|
|
{
|
|
if (!MaxSessionLimit)
|
|
MaxSessionLimit = MAX_MASKDATA_LIMIT;
|
|
|
|
module_operserv = find_module("operserv/main");
|
|
if (!module_operserv) {
|
|
module_log("Main OperServ module not loaded");
|
|
return 0;
|
|
}
|
|
use_module(module_operserv);
|
|
|
|
if (!register_commands(module_operserv, cmds)) {
|
|
module_log("Unable to register commands");
|
|
exit_module(0);
|
|
return 0;
|
|
}
|
|
|
|
/* Add user check callback at priority -10 so it runs after all the
|
|
* autokill/S-line/whatever checks (otherwise we get users added to
|
|
* sessions and then killed by S-lines, leaving the session count
|
|
* jacked up).
|
|
*/
|
|
if (!add_callback(NULL, "load module", do_load_module)
|
|
|| !add_callback(NULL, "unload module", do_unload_module)
|
|
|| !add_callback_pri(NULL, "user check", check_sessions, -10)
|
|
|| !add_callback(NULL, "user delete", remove_session)
|
|
|| !add_callback(module_operserv, "expire maskdata", do_expire_maskdata)
|
|
|| !add_callback(module_operserv, "STATS ALL", do_stats_all)
|
|
) {
|
|
module_log("Unable to add callbacks");
|
|
exit_module(0);
|
|
return 0;
|
|
}
|
|
|
|
module_akill = find_module("operserv/akill");
|
|
if (module_akill)
|
|
do_load_module(module_akill, "operserv/akill");
|
|
|
|
if (!register_dbtable(&exception_dbtable)) {
|
|
module_log("Unable to register database table");
|
|
exit_module(0);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
int exit_module(int shutdown_unused)
|
|
{
|
|
Session *session;
|
|
|
|
unregister_dbtable(&exception_dbtable);
|
|
|
|
if (module_akill)
|
|
do_unload_module(module_akill);
|
|
|
|
for (session = first_session(); session; session = next_session()) {
|
|
_del_session(session);
|
|
free_session(session);
|
|
}
|
|
|
|
remove_callback(NULL, "user delete", remove_session);
|
|
remove_callback(NULL, "user check", check_sessions);
|
|
remove_callback(NULL, "unload module", do_unload_module);
|
|
remove_callback(NULL, "load module", do_load_module);
|
|
|
|
if (module_operserv) {
|
|
remove_callback(module_operserv, "STATS ALL", do_stats_all);
|
|
remove_callback(module_operserv, "expire maskdata",do_expire_maskdata);
|
|
unregister_commands(module_operserv, cmds);
|
|
unuse_module(module_operserv);
|
|
module_operserv = NULL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|