/*
* NeoIRCd: NeoStats Group. Based on Hybird7
* client.c: Controls clients.
*
* Copyright (C) 2002 by the past and present ircd coders, and others.
*
* 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.
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* $Id: client.c,v 1.14 2003/03/13 08:49:24 fishwaldo Exp $
*/
#include "stdinc.h"
#include "config.h"
#include "tools.h"
#include "client.h"
#include "class.h"
#include "channel_mode.h"
#include "common.h"
#include "event.h"
#include "fdlist.h"
#include "hash.h"
#include "irc_string.h"
#include "ircd.h"
#include "list.h"
#include "s_gline.h"
#include "numeric.h"
#include "packet.h"
#include "s_auth.h"
#include "s_bsd.h"
#include "s_conf.h"
#include "s_log.h"
#include "s_misc.h"
#include "s_serv.h"
#include "send.h"
#include "whowas.h"
#include "s_debug.h"
#include "s_user.h"
#include "linebuf.h"
#include "hash.h"
#include "memory.h"
#include "hostmask.h"
#include "balloc.h"
#include "listener.h"
static void check_pings_list(dlink_list *list);
static void check_unknowns_list(dlink_list *list);
static EVH check_pings;
static int remote_client_count=0;
static int local_client_count=0;
static BlockHeap *client_heap = NULL;
static BlockHeap *lclient_heap = NULL;
dlink_list dead_list;
dlink_list abort_list;
/*
* client_heap_gc
*
* inputs - NONE
* output - NONE
* side effect - Does garbage collection of client heaps
*/
static void client_heap_gc(void *unused)
{
BlockHeapGarbageCollect(client_heap);
BlockHeapGarbageCollect(lclient_heap);
}
/*
* init_client
*
* inputs - NONE
* output - NONE
* side effects - initialize client free memory
*/
void
init_client(void)
{
remote_client_count = 0;
local_client_count = 0;
/*
* start off the check ping event .. -- adrian
* Every 30 seconds is plenty -- db
*/
client_heap = BlockHeapCreate(sizeof(struct Client), CLIENT_HEAP_SIZE);
lclient_heap = BlockHeapCreate(sizeof(struct LocalUser), LCLIENT_HEAP_SIZE);
eventAddIsh("check_pings", check_pings, NULL, 30);
eventAddIsh("client_heap_gc", client_heap_gc, NULL, 30);
}
/*
* make_client - create a new Client struct and set it to initial state.
*
* from == NULL, create local client (a client connected
* to a socket).
*
* from, create remote client (behind a socket
* associated with the client defined by
* 'from'). ('from' is a local client!!).
*/
struct Client*
make_client(struct Client* from)
{
struct Client* client_p = NULL;
struct LocalUser *localClient;
dlink_node *m;
client_p = BlockHeapAlloc(client_heap);
memset(client_p, 0, sizeof(struct Client));
if (from == NULL)
{
client_p->from = client_p; /* 'from' of local client is self! */
client_p->since = client_p->lasttime = client_p->firsttime = CurrentTime;
localClient = (struct LocalUser *)BlockHeapAlloc(lclient_heap);
memset(localClient, 0, sizeof(struct LocalUser));
client_p->localClient = localClient;
client_p->localClient->fd = -1;
client_p->localClient->ctrlfd = -1;
#ifndef HAVE_SOCKETPAIR
client_p->localClient->fd_r = -1;
client_p->localClient->ctrlfd_r = -1;
#endif
/* as good a place as any... */
m = make_dlink_node();
dlinkAdd(client_p, m, &unknown_list);
++local_client_count;
}
else
{ /* from is not NULL */
client_p->localClient = NULL;
client_p->from = from; /* 'from' of local client is self! */
++remote_client_count;
}
client_p->status = STAT_UNKNOWN;
strcpy(client_p->username, "unknown");
#if 0
client_p->name[0] = '\0';
client_p->flags = 0;
client_p->next = NULL;
client_p->prev = NULL;
client_p->hnext = NULL;
client_p->lnext = NULL;
client_p->lprev = NULL;
client_p->user = NULL;
client_p->serv = NULL;
client_p->servptr = NULL;
client_p->whowas = NULL;
client_p->allow_list.head = NULL;
client_p->allow_list.tail = NULL;
client_p->on_allow_list.head = NULL;
client_p->on_allow_list.tail = NULL;
#endif
return client_p;
}
void
free_client(struct Client* client_p)
{
assert(NULL != client_p);
assert(&me != client_p);
assert(NULL == client_p->prev);
assert(NULL == client_p->next);
if (MyConnect(client_p))
{
assert(IsClosing(client_p) && IsDead(client_p));
/*
* clean up extra sockets from P-lines which have been discarded.
*/
if (client_p->localClient->listener)
{
assert(0 < client_p->localClient->listener->ref_count);
if (0 == --client_p->localClient->listener->ref_count &&
!client_p->localClient->listener->active)
free_listener(client_p->localClient->listener);
client_p->localClient->listener = 0;
}
#ifdef USE_SSL
if (client_p->localClient->ssl)
{
SSL_smart_shutdown(client_p);
}
#endif
if (client_p->localClient->fd >= 0)
fd_close(client_p->localClient->fd);
linebuf_donebuf(&client_p->localClient->buf_recvq);
linebuf_donebuf(&client_p->localClient->buf_sendq);
BlockHeapFree(lclient_heap, client_p->localClient);
--local_client_count;
assert(local_client_count >= 0);
}
else
{
--remote_client_count;
}
BlockHeapFree(client_heap, client_p);
}
/*
* check_pings - go through the local client list and check activity
* kill off stuff that should die
*
* inputs - NOT USED (from event)
* output - next time_t when check_pings() should be called again
* side effects -
*
*
* A PING can be sent to clients as necessary.
*
* Client/Server ping outs are handled.
*/
/*
* Addon from adrian. We used to call this after nextping seconds,
* however I've changed it to run once a second. This is only for
* PING timeouts, not K/etc-line checks (thanks dianora!). Having it
* run once a second makes life a lot easier - when a new client connects
* and they need a ping in 4 seconds, if nextping was set to 20 seconds
* we end up waiting 20 seconds. This is stupid. :-)
* I will optimise (hah!) check_pings() once I've finished working on
* tidying up other network IO evilnesses.
* -- adrian
*/
static void
check_pings(void *notused)
{
check_pings_list(&lclient_list);
check_pings_list(&serv_list);
check_unknowns_list(&unknown_list);
}
/*
* Check_pings_list()
*
* inputs - pointer to list to check
* output - NONE
* side effects -
*/
static void
check_pings_list(dlink_list *list)
{
char scratch[32]; /* way too generous but... */
struct Client *client_p; /* current local client_p being examined */
int ping = 0; /* ping time value from client */
dlink_node *ptr, *next_ptr;
DLINK_FOREACH_SAFE(ptr, next_ptr, list->head)
{
client_p = ptr->data;
/*
** Note: No need to notify opers here. It's
** already done when "FLAGS_DEADSOCKET" is set.
*/
if (IsDead(client_p))
{
/* Ignore it, its been exited already */
continue;
}
if (IsPerson(client_p))
{
if(!IsExemptKline(client_p) &&
GlobalSetOptions.idletime &&
!IsOper(client_p) &&
!IsIdlelined(client_p) &&
((CurrentTime - client_p->user->last) > GlobalSetOptions.idletime))
{
struct ConfItem *aconf;
aconf = make_conf();
aconf->status = CONF_KILL;
DupString(aconf->host, client_p->host);
DupString(aconf->passwd, "idle exceeder");
DupString(aconf->user, client_p->username);
aconf->port = 0;
aconf->hold = CurrentTime + 60;
add_temp_kline(aconf);
sendto_realops_flags(FLAGS_ALL, L_ALL,
"Idle time limit exceeded for %s - temp k-lining",
get_client_name(client_p, HIDE_IP));
(void)exit_client(client_p, client_p, &me, aconf->passwd);
continue;
}
}
if (!IsRegistered(client_p))
ping = CONNECTTIMEOUT;
else
ping = get_client_ping(client_p);
if (ping < (CurrentTime - client_p->lasttime))
{
/*
* If the client/server hasnt talked to us in 2*ping seconds
* and it has a ping time, then close its connection.
*/
if (((CurrentTime - client_p->lasttime) >= (2 * ping) &&
IsPingSent(client_p)))
{
if (IsServer(client_p) || IsConnecting(client_p) ||
IsHandshake(client_p))
{
sendto_realops_flags(FLAGS_ALL|FLAGS_REMOTE, L_ADMIN,
"No response from %s, closing link",
get_client_name(client_p, HIDE_IP));
sendto_realops_flags(FLAGS_ALL|FLAGS_REMOTE, L_OPER,
"No response from %s, closing link",
get_client_name(client_p, MASK_IP));
ilog(L_NOTICE, "No response from %s, closing link",
get_client_name(client_p, HIDE_IP));
}
(void)ircsprintf(scratch,
"Ping timeout: %d seconds",
(int)(CurrentTime - client_p->lasttime));
(void)exit_client(client_p, client_p, &me, scratch);
continue;
}
else if (!IsPingSent(client_p))
{
/*
* if we havent PINGed the connection and we havent
* heard from it in a while, PING it to make sure
* it is still alive.
*/
SetPingSent(client_p);
client_p->lasttime = CurrentTime - ping;
sendto_one(client_p, "PING :%s", me.name);
}
}
/* ping_timeout: */
}
}
/*
* check_unknowns_list
*
* inputs - pointer to list of unknown clients
* output - NONE
* side effects - unknown clients get marked for termination after n seconds
*/
static void
check_unknowns_list(dlink_list *list)
{
dlink_node *ptr, *next_ptr;
struct Client *client_p;
DLINK_FOREACH_SAFE(ptr, next_ptr, list->head)
{
client_p = ptr->data;
/*
* Check UNKNOWN connections - if they have been in this state
* for > 30s, close them.
*/
if (client_p->firsttime ? ((CurrentTime - client_p->firsttime) > 30) : 0)
{
(void)exit_client(client_p, client_p, &me, "Connection timed out");
}
}
}
/*
* check_klines
* inputs - NONE
* output - NONE
* side effects - Check all connections for a pending kline against the
* client, exit the client if a kline matches.
*/
void
check_klines(void)
{
struct Client *client_p; /* current local client_p being examined */
struct ConfItem *aconf = NULL;
char *reason; /* pointer to reason string */
dlink_node *ptr, *next_ptr;
DLINK_FOREACH_SAFE(ptr, next_ptr, lclient_list.head)
{
client_p = ptr->data;
if (IsMe(client_p))
continue;
/* If a client is already being exited
*/
if (IsDead(client_p))
continue;
/* if there is a returned struct ConfItem then kill it */
if ((aconf = find_dline_conf(&client_p->localClient->ip,
client_p->localClient->aftype)) != NULL)
{
if (aconf->status & CONF_EXEMPTDLINE)
continue;
sendto_realops_flags(FLAGS_ALL, L_ALL,"DLINE active for %s",
get_client_name(client_p, HIDE_IP));
if (ConfigFileEntry.kline_with_connection_closed &&
ConfigFileEntry.kline_with_reason)
{
reason = "Connection closed";
if(IsPerson(client_p))
sendto_one(client_p, form_str(ERR_YOUREBANNEDCREEP),
me.name, client_p->name,
aconf->passwd ? aconf->passwd : "D-lined");
else
sendto_one(client_p, "NOTICE DLINE :*** You have been D-lined");
}
else
{
if(ConfigFileEntry.kline_with_connection_closed)
reason = "Connection closed";
else if(ConfigFileEntry.kline_with_reason && aconf->passwd)
reason = aconf->passwd;
else
reason = "D-lined";
if(IsPerson(client_p))
sendto_one(client_p, form_str(ERR_YOUREBANNEDCREEP),
me.name, client_p->name, reason);
else
sendto_one(client_p, "NOTICE DLINE :*** You have been D-lined");
}
(void)exit_client(client_p, client_p, &me, reason);
continue; /* and go examine next fd/client_p */
}
if (IsPerson(client_p))
{
if (ConfigFileEntry.glines &&
(aconf = find_gkill(client_p, client_p->username)))
{
if (IsExemptKline(client_p))
{
sendto_realops_flags(FLAGS_ALL, L_ALL,
"GLINE over-ruled for %s, client is kline_exempt",
get_client_name(client_p, HIDE_IP));
continue;
}
if (IsExemptGline(client_p))
{
sendto_realops_flags(FLAGS_ALL, L_ALL,
"GLINE over-ruled for %s, client is gline_exempt",
get_client_name(client_p, HIDE_IP));
continue;
}
sendto_realops_flags(FLAGS_ALL, L_ALL, "GLINE active for %s",
get_client_name(client_p, HIDE_IP));
if(ConfigFileEntry.kline_with_connection_closed &&
ConfigFileEntry.kline_with_reason)
{
reason = "Connection closed";
sendto_one(client_p, form_str(ERR_YOUREBANNEDCREEP),
me.name, client_p->name,
aconf->passwd ? aconf->passwd : "G-lined");
}
else
{
if(ConfigFileEntry.kline_with_connection_closed)
reason = "Connection closed";
else if(ConfigFileEntry.kline_with_reason && aconf->passwd)
reason = aconf->passwd;
else
reason = "G-lined";
sendto_one(client_p, form_str(ERR_YOUREBANNEDCREEP),
me.name, client_p->name, reason);
}
(void)exit_client(client_p, client_p, &me, reason);
/* and go examine next fd/client_p */
continue;
}
else if((aconf = find_kill(client_p)) != NULL)
{
/* if there is a returned struct ConfItem.. then kill it */
if (IsExemptKline(client_p))
{
sendto_realops_flags(FLAGS_ALL, L_ALL,
"KLINE over-ruled for %s, client is kline_exempt",
get_client_name(client_p, HIDE_IP));
continue;
}
sendto_realops_flags(FLAGS_ALL, L_ALL, "KLINE active for %s",
get_client_name(client_p, HIDE_IP));
if(ConfigFileEntry.kline_with_connection_closed &&
ConfigFileEntry.kline_with_reason)
{
reason = "Connection closed";
sendto_one(client_p, form_str(ERR_YOUREBANNEDCREEP),
me.name, client_p->name,
aconf->passwd ? aconf->passwd : "K-lined");
}
else
{
if(ConfigFileEntry.kline_with_connection_closed)
reason = "Connection closed";
else if(ConfigFileEntry.kline_with_reason && aconf->passwd)
reason = aconf->passwd;
else
reason = "K-lined";
sendto_one(client_p, form_str(ERR_YOUREBANNEDCREEP),
me.name, client_p->name, reason