/************************************************************************
* IRC - Internet Relay Chat, server/s_exit.c
*
* Copyright (C) 2000-2003 TR-IRCD Development
*
* Copyright (C) 1990 Jarkko Oikarinen and
* University of Oulu, Co Center
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* $Id: s_exit.c,v 1.11 2004/02/24 19:03:33 tr-ircd Exp $
*/
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "zlink.h"
#include "dh.h"
#include "h.h"
#include "msg.h"
#include "listener.h"
#include "resnew.h"
#include "s_auth.h"
#include "s_conf.h"
#include "event.h"
static void exit_one_client(aClient *, aClient *, char *, int);
/*
* NOQUIT
* a method of reducing the stress on the network during server splits
* by sending only a simple "SQUIT" message for the server that is dropping,
* instead of thousands upon thousands of QUIT messages for each user,
* plus an SQUIT for each server behind the dead link.
*
* Original idea by Cabal95, implementation by lucas
*/
/*
* exit_recursion
*
* recursive function!
* therefore, we pass dead and reason to ourselves.
* in the beginning, dead == cptr, so it will be the one
* out of the loop last. therefore, dead should remain a good pointer.
* dead: the actual server that split (if this belongs to us, we
* absolutely CANNOT send to it)
* source_p: the client that caused this split
* spinfo: split reason, as generated in exit_server
* comment: comment provided
*/
static inline void exit_recursion(aClient *dead, aClient *source_p, char *spinfo)
{
aClient *acptr;
if (IsMe(dead))
return;
if (dead->serv == NULL)
return;
/*
* okay, this is annoying.
* first off, we need two loops.
* one: to remove all the clients.
* two: to remove all the servers.
* HOWEVER! removing a server may cause removal of more servers and more clients.
* and this may make our pointer to next bad. therefore, we have to restart
* the server loop each time we find a server.
* We _NEED_ two different loops: all clients must be removed before the server is
* removed. Otherwise, bad things (tm) can happen.
*/
logevent_call(LogSys.server_noquit, "BEGIN NOQUIT RECURSION", dead);
while ((acptr = dead->serv->users)) {
exit_one_client(acptr, dead, spinfo, 1);
SetExited(acptr);
remove_client_from_list(acptr);
}
while ((acptr = dead->serv->servers)) {
exit_recursion(acptr, source_p, spinfo);
exit_one_client(acptr, source_p, spinfo, 1);
SetExited(acptr);
remove_client_from_list(acptr);
}
logevent_call(LogSys.server_noquit, "END NOQUIT RECURSION", dead);
}
/*
* * exit_client *
*
* This function exits a client of *any* type (user, server, etc)
* from this server. Also, this generates all necessary prototol
* messages that this exit may cause.
*
* 1) If the client is a local client, then this implicitly exits
* all other clients depending on this connection (e.g. remote
* clients having 'from'-field that points to this.
*
* 2) If the client is a remote client, then only this is exited.
*
* For convenience, this function returns a suitable value for
* m_function return value:
*
*/
int exit_client(aClient *exit_ptr, /* Client exiting */
aClient *source_p, /* Client generating this Exit, !NULL */
char *comment /* Reason for the exit */
)
{
dlink_node *m;
aConfItem *aconf;
// logevent_call(LogSys.exit_client, exit_ptr, source_p, comment);
if (IsMe(exit_ptr))
return 0;
if (IsExited(exit_ptr) || IsClosing(exit_ptr) || IsHttpClient(exit_ptr))
return CLIENT_EXITED;
SetClosing(exit_ptr);
if (exit_ptr->fd >= 0) {
if (!IsDead(exit_ptr))
send_queued(exit_ptr->fd, exit_ptr);
sendto_one_server(exit_ptr, NULL, TOK1_ERROR,
":Closing Link: %s (%s)",
IsPerson(exit_ptr) ? exit_ptr->sockhost : "0.0.0.0", comment);
}
if (IsLocalClient(exit_ptr)) {
if (IsListenerHttp(exit_ptr->listener))
return CLIENT_EXITED;
delete_adns_queries(exit_ptr->dns_query);
delete_identd_queries(exit_ptr);
m = dlinkFind(&unknown_list, exit_ptr);
if (m != NULL) {
Count.unknown--;
dlinkDelete(m, &unknown_list);
free_dlink_node(m);
}
if (IsClient(exit_ptr)) {
Count.local--;
if (IsAnOper(exit_ptr)) {
m = dlinkFind(&locoper_list, exit_ptr);
if (m != NULL) {
dlinkDelete(m, &locoper_list);
free_dlink_node(m);
}
}
if (IsPerson(exit_ptr)) { /* a little extra paranoia */
m = dlinkFind(&lclient_list, exit_ptr);
if (m != NULL) {
dlinkDelete(m, &lclient_list);
free_dlink_node(m);
}
}
ircstp->is_cl++;
ircstp->is_cbs += exit_ptr->sendB;
ircstp->is_cbr += exit_ptr->receiveB;
ircstp->is_cks += exit_ptr->sendK;
ircstp->is_ckr += exit_ptr->receiveK;
ircstp->is_cti += timeofday - exit_ptr->firsttime;
if (ircstp->is_cbs > 2047) {
ircstp->is_cks += (ircstp->is_cbs >> 10);
ircstp->is_cbs &= 0x3ff;
}
if (ircstp->is_cbr > 2047) {
ircstp->is_ckr += (ircstp->is_cbr >> 10);
ircstp->is_cbr &= 0x3ff;
}
}
if (IsNegoServer(exit_ptr))
sendto_lev(SNOTICE_LEV, "Lost server %s during negotiation: %s",
exit_ptr->name, comment);
if (IsServer(exit_ptr)) {
Count.myserver--;
if (IsULine(exit_ptr))
Count.myulined--;
m = dlinkFind(&serv_list, exit_ptr);
if (m != NULL) {
dlinkDelete(m, &serv_list);
free_dlink_node(m);
}
sendto_gnotice("%C was connected for %lu seconds. %lu/%lu sendK/recvK.",
exit_ptr, timeofday - exit_ptr->firsttime, exit_ptr->sendK,
exit_ptr->receiveK);
ircstp->is_sv++;
ircstp->is_sbs += exit_ptr->sendB;
ircstp->is_sbr += exit_ptr->receiveB;
ircstp->is_sks += exit_ptr->sendK;
ircstp->is_skr += exit_ptr->receiveK;
ircstp->is_sti += timeofday - exit_ptr->firsttime;
if (ircstp->is_sbs > 2047) {
ircstp->is_sks += (ircstp->is_sbs >> 10);
ircstp->is_sbs &= 0x3ff;
}
if (ircstp->is_sbr > 2047) {
ircstp->is_skr += (ircstp->is_sbr >> 10);
ircstp->is_sbr &= 0x3ff;
}
/*
* If the connection has been up for a long amount of time, schedule
* a 'quick' reconnect, else reset the next-connect cycle.
*/
if ((aconf = find_conf_exact(exit_ptr->name, exit_ptr->username,
exit_ptr->sockhost, CONF_SERVER))) {
/*
* Reschedule a faster reconnect, if this was a automatically
* connected configuration entry. (Note that if we have had
* a rehash in between, the status has been changed to
* CONF_ILLEGAL). But only do this if it was a "good" link.
*/
aconf->hold = time(NULL);
aconf->hold += (aconf->hold - exit_ptr->since > HANGONGOODLINK) ?
HANGONRETRYDELAY : aconf->class->connect_frequency;
}
if (GeneralOpts.split == 0)
eventAddIsh("check_splitmode", check_splitmode, NULL, 1800);
GeneralOpts.split = 1;
}
if (!IsClient(exit_ptr) && !IsServer(exit_ptr))
ircstp->is_ni++;
if (IsPerson(exit_ptr)) {
hash_del_watch_list(exit_ptr);
sendto_lev(CCONN_LEV,
"Client exiting: %^C [%s] [%s]",
exit_ptr,
(exit_ptr->flags & FLAGS_NORMALEX) ? "Client Quit" : comment,
exit_ptr->hostip);
}
if (IsService(exit_ptr)) {
sendto_lev(SERVICE_LEV, "Exiting Service %s (%s@%s)",
exit_ptr->name, exit_ptr->username, exit_ptr->sockhost);
Count.myservice--;
}
det_confs_butmask(exit_ptr, 0);
exit_one_client(exit_ptr, source_p, comment, 0);
if (exit_ptr->fd >= 0) {
#ifdef HAVE_ENCRYPTION_ON
/* Close the ssl connection also */
if (IsSSL(exit_ptr)) {
SSL_set_shutdown(exit_ptr->ssl, SSL_RECEIVED_SHUTDOWN);
SSL_smart_shutdown(exit_ptr->ssl);
SSL_free(exit_ptr->ssl);
exit_ptr->ssl = NULL;
}
#endif
fd_close(exit_ptr->fd);
exit_ptr->fd = -1;
exit_ptr->flags |= FLAGS_DEADSOCKET;
}
linebuf_donebuf(&exit_ptr->sendQ);
linebuf_donebuf(&exit_ptr->recvQ);
memset(exit_ptr->passwd, 0, sizeof(exit_ptr->passwd));
/* clean up extra sockets from P-lines which have been discarded. */
if (exit_ptr->listener) {
assert(0 < exit_ptr->listener->ref_count);
if (0 == --exit_ptr->listener->ref_count && !exit_ptr->listener->active)
free_listener(exit_ptr->listener);
exit_ptr->listener = 0;
}
m = make_dlink_node();
dlinkAdd(exit_ptr, m, &exit_list);
} else if (!IsLocalClient(exit_ptr)) {
// exit_ptr->from = NULL; /* ...this should catch them! >:) --msa */
exit_one_client(exit_ptr, source_p, comment, 0);
remove_client_from_list(exit_ptr);
}
SetExited(exit_ptr);
return CLIENT_EXITED;
}
/*
* * Exit one client, local or remote. Assuming all dependants have *
* been already removed, and socket closed for local client.
*/
static void exit_one_client(aClient *exit_ptr, aClient *source_p, char *comment, int split)
{
dlink_node *lp, *ptr;
dlink_node *next_lp;
aClient *acptr;
if (IsMe(exit_ptr))
return;
if (IsClient(exit_ptr))
Count.total--;
if (IsAnOper(exit_ptr))
Count.oper--;
if (IsInvisible(exit_ptr))
Count.invisi--;
if (IsServer(exit_ptr)) {
char splitname[HOSTLEN + HOSTLEN + 2];
if (ServerHide.enable) {
if (ServerHide.flatten_links)
ircsprintf(splitname, "%C %*C", &me, exit_ptr);
else
ircsprintf(splitname, "a.server.on.%s %C", ServerInfo.networkname, &me);
} else {
ircsprintf(splitname, "%*C %*C", exit_ptr->servptr, exit_ptr);
}
logevent_call(LogSys.exit_server, exit_ptr, source_p, comment);
exit_recursion(exit_ptr, source_p, splitname);
for (ptr = serv_list.head; ptr; ptr = next_lp) {
next_lp = ptr->next;
acptr = ptr->data;
if (!acptr || IsMe(acptr) || acptr == exit_ptr || acptr == source_p)
continue;
if (exit_ptr->from == acptr) /* "upstream" squit */
sendto_one_server(acptr, source_p, TOK1_SQUIT, "%~C :%s", exit_ptr, comment);
else
sendto_one_server(acptr, NULL, TOK1_SQUIT, "%~C :%s", exit_ptr, comment);
}
if (HasID(exit_ptr))
rem_base64_server(exit_ptr);
remove_server_from_list(exit_ptr);
if (exit_ptr->servptr && exit_ptr->servptr->serv) {
del_client_from_llist(&(exit_ptr->servptr->serv->servers), exit_ptr);
exit_ptr->servptr->serv->servercnt--;
}
Count.server--;
if (exit_ptr->serv) {
#ifdef HAVE_ENCRYPTION_ON
if (exit_ptr->serv->sin)
dh_end_session(exit_ptr->serv->sin);
if (exit_ptr->serv->sout)
dh_end_session(exit_ptr->serv->sout);
if (exit_ptr->serv->rc4_in)
rc4_destroystate(exit_ptr->serv->rc4_in);
if (exit_ptr->serv->rc4_out)
rc4_destroystate(exit_ptr->serv->rc4_out);
#endif
if (exit_ptr->serv->zin)
destroy_unzipstream(exit_ptr->serv->zin);
if (exit_ptr->serv->zout)
destroy_zipstream(exit_ptr->serv->zout);
exit_ptr->serv->nline = NULL;
MyFree((char *) exit_ptr->serv);
}
}
if (IsPerson(exit_ptr)) {
/*
* * If a person is on a channel, send a QUIT notice * to every
* client (person) on the same channel (so * that the client can
* show the "**signoff" message). * (Note: The notice is to the
* local clients *only*)
*/
/*
* If this exit is generated from "m_kill", then there is no
* sense in sending the QUIT--KILL's have been sent instead.
*/
if (!split) {
if ((exit_ptr->flags & FLAGS_KILLED) == 0) {
sendto_serv_butone(exit_ptr->from, exit_ptr, TOK1_QUIT, ":%s", comment);
}
}
send_quit_to_common_channels(exit_ptr, comment);
if (MyClient(exit_ptr))
free_localuser_identity(exit_ptr);
sendto_service(SERVICE_SEE_QUITS, 0, exit_ptr, NULL, TOK1_QUIT, ":%s", comment);
for (lp = exit_ptr->user->channel.head; lp; lp = next_lp) {
next_lp = lp->next;
remove_user_from_channel(exit_ptr, lp->data);
}
for (lp = exit_ptr->user->invited.head; lp; lp = next_lp) {
next_lp = lp->next;
del_invite(exit_ptr, lp->data);
}
for (lp = exit_ptr->user->silence.head; lp; lp = next_lp) {
next_lp = lp->next;
del_silence(exit_ptr, lp->data);
}
remove_dcc_references(exit_ptr);
remove_accept_references(exit_ptr);
if (HasID(exit_ptr))
rem_userid_from_server(exit_ptr->servptr, exit_ptr);
if (IsRegisteredUser(exit_ptr))
hash_check_watch(exit_ptr, RPL_LOGOFF);
if (exit_ptr->servptr && exit_ptr->servptr->serv) {
del_client_from_llist(&(exit_ptr->servptr->serv->users), exit_ptr);
exit_ptr->servptr->serv->usercnt--;
}
add_history(exit_ptr, 0);
off_history(exit_ptr);
free_user(exit_ptr->user);
exit_ptr->user = NULL;
}
if (IsService(exit_ptr)) {
if (exit_ptr->service) {
sendto_lev(SERVICE_LEV,
"Received QUIT from service %s (%s@%s)",
exit_ptr->name, exit_ptr->username, exit_ptr->sockhost);
if (exit_ptr->from == exit_ptr)
sendto_lev(SERVICE_LEV, "Service %s disconnected", exit_ptr->name);
}
Count.service--;
if (exit_ptr->service)
free_service(exit_ptr);
}
del_from_client_hash_table(exit_ptr->name, exit_ptr);
return;
}
void remove_exited_clients(void *notused)
{
dlink_node *ptr, *prev_ptr;
aClient *cptr;
for (ptr = exit_list.tail; ptr; ptr = prev_ptr) {
prev_ptr = ptr->prev;
cptr = ptr->data;
if (!cptr)
continue;
if (IsExited(cptr)) {
remove_client_from_list(cptr);
dlinkDeleteNode(ptr, &exit_list);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1