/***********************************************************************
* IRC - Internet Relay Chat, server/s_serv.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.
*/
#include "struct.h"
#include "common.h"
#include "sys.h"
#include "numeric.h"
#include "msg.h"
#include "channel.h"
#include "h.h"
#include "s_bsd.h"
#include "s_conf.h"
#include "confitem.h"
#include "listener.h"
#include "ircsprintf.h"
#include "packet.h"
#include "throttle.h"
#include "zlink.h"
#include "dh.h"
#include "hook.h"
static int do_server_estab(aClient *);
static void serv_connect_callback(int fd, int status, void *data);
static int hookid_sendtnick_ts = 0;
static int hookid_inform_other_servers = 0;
static int hookid_start_server_estab = 0;
static int hookid_continue_server_estab = 0;
static int hookid_connect_to_server = 0;
void init_server(void)
{
hookid_sendtnick_ts = hook_add_event("sendnick TS");
hookid_inform_other_servers = hook_add_event("inform other servers");
hookid_start_server_estab = hook_add_event("start server estab");
hookid_continue_server_estab = hook_add_event("continue server estab");
hookid_connect_to_server = hook_add_event("connect to server");
return;
}
/*
* add_server_to_list()
* input - pointer to client
* output - none
* side effects - server is added to global_serv_list
*/
void add_server_to_list(struct Client *client_p)
{
dlink_node *ptr;
ptr = make_dlink_node();
dlinkAdd(client_p, ptr, &global_serv_list);
return;
}
/*
* remove_server_from_list()
*
* input - pointer to client
* output - none
* side effects - server is removed from GlocalServerList
*/
void remove_server_from_list(struct Client *client_p)
{
dlink_node *ptr;
struct Client *target_p;
for (ptr = global_serv_list.head; ptr; ptr = ptr->next) {
target_p = ptr->data;
if (client_p == target_p) {
dlinkDelete(ptr, &global_serv_list);
free_dlink_node(ptr);
break;
}
}
return;
}
/*
* try_connections - scan through configuration and try new connections.
* Returns the calendar time when the next call to this
* function should be made latest. (No harm done if this
* is called earlier or later...)
*/
void try_connections(void *unused)
{
struct ConfItem *aconf;
struct Client *client_p;
int connecting = FALSE;
int confrq;
time_t next = 0;
struct ConnClass *cltmp;
struct ConfItem *con_conf = NULL;
logevent_call(LogSys.conncheck, smalldate(timeofday));
for (aconf = GlobalConfItemList; aconf; aconf = aconf->next) {
/*
* Also when already connecting! (update holdtimes) --SRB
*/
if (!(aconf->status & CONF_SERVER) || aconf->port <= 0)
continue;
cltmp = aconf->class;
/*
* Skip this entry if the use of it is still on hold until
* future. Otherwise handle this entry (and set it on hold
* until next time). Will reset only hold times, if already
* made one successfull connection... [this algorithm is
* a bit fuzzy... -- msa >;) ]
*/
if (aconf->hold > timeofday) {
if (next > aconf->hold || next == 0)
next = aconf->hold;
continue;
}
confrq = get_con_freq(cltmp);
aconf->hold = timeofday + confrq;
/*
* Found a CONNECT config with port specified, scan clients
* and see if this server is already connected?
*/
client_p = find_server(aconf->name);
if (!client_p && (cltmp->links < cltmp->max_connections) && !connecting) {
con_conf = aconf;
/* We connect only one at time... */
connecting = TRUE;
}
if ((next > aconf->hold) || (next == 0))
next = aconf->hold;
}
if (connecting) {
if (con_conf->next) { /* are we already last? */
struct ConfItem **pconf;
for (pconf = &GlobalConfItemList; (aconf = *pconf); pconf = &(aconf->next))
/*
* put the current one at the end and
* make sure we try all connections
*/
if (aconf == con_conf)
*pconf = aconf->next;
(*pconf = con_conf)->next = 0;
}
if (con_conf->flags & CONF_FLAGS_ALLOW_AUTO_CONN) {
/*
* We used to only print this if serv_connect() actually
* suceeded, but since comm_tcp_connect() can call the callback
* immediately if there is an error, we were getting error messages
* in the wrong order. SO, we just print out the activated line,
* and let serv_connect() / serv_connect_callback() print an
* error afterwards if it fails.
* -- adrian
*/
sendto_lev(SNOTICE_LEV,
"Connection to %s[%s] activated.", con_conf->name, con_conf->host);
serv_connect(con_conf, 0);
}
logevent_call(LogSys.conncheck, smalldate(next));
}
}
int check_server(char *name, struct Client *client_p)
{
struct ConfItem *aconf = NULL;
struct ConfItem *server_aconf = NULL;
int error = -2;
char *q;
char passarr[PASSWDLEN];
assert(0 != client_p);
if (!(client_p->passwd))
return NOT_AUTHORIZED;
/* loop through looking for all possible connect items that might work */
for (aconf = GlobalConfItemList; aconf; aconf = aconf->next) {
if ((aconf->status & CONF_SERVER) == 0)
continue;
if (match(aconf->name, name))
continue;
error = -3;
/* XXX: Fix me for IPv6 */
/* XXX sockhost is the IPv4 ip as a string */
if (match(aconf->host, client_p->sockhost) && match(aconf->host, client_p->hostip)) {
error = NOT_AUTHORIZED;
} else {
q = client_p->passwd;
if (q && strcmp(aconf->passwd, calcpass(q, passarr)) == 0)
server_aconf = aconf;
}
}
if (server_aconf == NULL)
return error;
if (client_p->listener && !IsListenerServer(client_p->listener))
return INVALID_CONNECTION;
attach_conf(client_p, server_aconf);
if (IsConfCompressed(server_aconf))
client_p->protoflags |= PFLAGS_DOZIP;
if (IsConfEncrypted(server_aconf))
client_p->protoflags |= PFLAGS_DODKEY;
/* Now find all leaf or hub config items for this server */
for (aconf = GlobalConfItemList; aconf; aconf = aconf->next) {
if (match(aconf->name, name))
continue;
attach_conf(client_p, aconf);
}
if (aconf != NULL) {
#ifdef IPV6
if (IN6_IS_ADDR_UNSPECIFIED((struct in6_addr *)
&IN_ADDR(aconf->ipnum)))
#else
if (IN_ADDR(aconf->ipnum) == INADDR_NONE)
#endif
copy_s_addr(IN_ADDR(aconf->ipnum), IN_ADDR(client_p->ip));
}
return 0;
}
static void sendnick_TS(aClient *cptr, aClient *acptr)
{
struct hook_data thisdata;
thisdata.client_p = cptr;
thisdata.source_p = acptr;
hook_call_event(hookid_sendtnick_ts, &thisdata);
}
/*
* server_estab
*
* inputs - pointer to a struct Client
* output -
* side effects -
*/
int server_estab(struct Client *client_p)
{
struct ConfItem *aconf;
struct hook_data thisdata;
const char *inpath;
static char inpath_ip[HOSTLEN * 2 + USERLEN + 5];
char *host;
dlink_node *m;
assert(0 != client_p);
ClearAccess(client_p);
strcpy(inpath_ip, get_client_name(client_p, SHOW_IP));
inpath = get_client_name(client_p, MASK_IP); /* "refresh" inpath with host */
host = client_p->name;
if (!(aconf = find_conf_name(&client_p->confs, host, CONF_SERVER))) {
/* This shouldn't happen, better tell the ops... -A1kmm */
sendto_gnotice("Warning: Lost connect{} block "
"for server %s(this shouldn't happen)!", host);
return exit_client(client_p, client_p, "Lost connect{} block!");
}
memset((void *) client_p->passwd, 0, sizeof(client_p->passwd));
/* Its got identd , since its a server */
SetGotId(client_p);
if (IsUnknown(client_p)) {
thisdata.client_p = client_p;
thisdata.confitem = aconf;
/*
* jdc -- 1. Use EmptyString(), not [0] index reference.
* 2. Check aconf->spasswd, not aconf->passwd.
*/
thisdata.check = EmptyString(aconf->spasswd);
hook_call_event(hookid_start_server_estab, &thisdata);
}
det_confs_butmask(client_p, CONF_SERVER);
/*
* WARNING
* In the following code in place of plain server's
* name we send what is returned by get_client_name
* which may add the "sockhost" after the name. It's
* *very* *important* that there is a SPACE between
* the name and sockhost (if present). The receiving
* server will start the information field from this
* first blank and thus puts the sockhost into info.
* ...a bit tricky, but you have been warned, besides
* code is more neat this way... --msa
*/
SetServer(client_p);
client_p->servptr = &me;
/* Some day, all these lists will be consolidated *sigh* */
add_client_to_llist(&(me.serv->servers), client_p);
me.serv->servercnt++;
if (IsConfUltimate(aconf)) {
client_p->protoflags |= PFLAGS_ULINE;
Count.myulined++;
}
if (IsConfHub(aconf))
client_p->protoflags |= PFLAGS_ISHUB;
m = dlinkFind(&unknown_list, client_p);
if (m != NULL) {
Count.unknown--;
dlinkDelete(m, &unknown_list);
dlinkAdd(client_p, m, &serv_list);
}
Count.server++;
Count.myserver++;
GeneralOpts.split = 0;
add_server_to_list(client_p);
add_to_client_hash_table(client_p->name, client_p);
/* doesnt duplicate client_p->serv if allocated this struct already */
make_server(client_p);
client_p->firsttime = timeofday;
throttle_remove(client_p->hostip);
client_p->serv->nline = aconf;
fd_note(client_p->fd, "Server: %s", client_p->name);
#ifdef HAVE_ENCRYPTION_ON
if (!CanDoDKEY(client_p) || !WantDKEY(client_p))
return continue_server_estab(client_p);
else {
SetNegoServer(client_p); /* VERY IMPORTANT THAT THIS IS HERE */
sendto_one(client_p, "%s START", MSG_DKEY);
}
return 0;
#else
return continue_server_estab(client_p);
#endif
}
int continue_server_estab(struct Client *client_p)
{
struct hook_data thisdata;
thisdata.client_p = client_p;
/*
* Pass on my client information to the new server
*
* First, pass only servers (idea is that if the link gets
* cancelled beacause the server was already there,
* there are no NICK's to be cancelled...). Of course,
* if cancellation occurs, all this info is sent anyway,
* and I guess the link dies when a read is attempted...? --msa
*
* Note: Link cancellation to occur at this point means
* that at least two servers from my fragment are building
* up connection this other fragment at the same time, it's
* a race condition, not the normal way of operation...
*
* ALSO NOTE: using the get_client_name for server names--
* see previous *WARNING*!!! (Also, original inpath
* is destroyed...)
*/
hook_call_event(hookid_continue_server_estab, &thisdata);
return do_server_estab(client_p);
}
static int do_server_estab(aClient *cptr)
{
aClient *acptr;
aChannel *chptr;
dlink_node *ptr;
struct hook_data thisdata;
struct ChanMember *cm;
char *inpath = get_client_name(cptr, HIDEME);
if (IsZipCapable(cptr) && DoZipThis(cptr)) {
sendto_one_server(cptr, NULL, TOK1_SVINFO, "ZIP");
SetZipOut(cptr);
cptr->serv->zout = output_zipstream();
}
cptr->pingval = get_client_ping(cptr);
cptr->sendqlen = get_sendq(cptr);
if (cptr->id.string[0]) {
char b64id[8];
/*
* we need to do this or we strcpy on top of ourselves.
* somewhat kludgy, but this is the best way to do things without
* writing another function to handle "local" servers - lucas
*/
strcpy(b64id, cptr->id.string);
if (add_base64_server(cptr, b64id)) {
char *sname;
acptr = find_server_by_base64_id(b64id, NULL);
sname = acptr ? acptr->name : "*UNKNOWN*";
sendto_gnotice
("Link %s overridden, identity %s already held by %s",
get_client_name(cptr, HIDEME), b64id, sname);
return exit_client(cptr, &me, "Duplicate identity overridden");
}
}
thisdata.client_p = cptr;
hook_call_event(hookid_inform_other_servers, &thisdata);
sendto_gnotice("Link with %s established, states:%s%s%s%s%s",
inpath,
ZipOut(cptr) ? " Output-compressed" : "",
RC4EncLink(cptr) ? " encrypted" : "",
IsULine(cptr) ? " ULined" : "",
HasID(cptr) ? " Identity-capable" : "", IsTS7(cptr) ? " TS7" : " Non-TS7");
/*
* Notify everyone of the fact that this has just linked: the entire network should get two
* of these, one explaining the link between me->serv and the other between serv->me
*/
sendto_serv_butone(NULL, &me, TOK1_GNOTICE,
":Link with %s established: %s",
inpath, IsTS7(cptr) ? "TS7 link" : "Non-TS7 link!");
sendto_service(SERVICE_SEE_SERVERS, 0, &me, NULL, TOK1_SERVER, "%s 2 :%s", cptr->name,
cptr->info);
push_all_maskitems(cptr, MASKITEM_QUARANTINE);
push_all_maskitems(cptr, MASKITEM_GECOS);
push_all_maskitems(cptr, MASKITEM_JUPITER);
push_all_maskitems(cptr, MASKITEM_ZAPLINE);
send_services(cptr);
/*
* Bursts are about to start.. send a BURST
*/
if (!IsULine(cptr))
sendto_one_server(cptr, NULL, TOK1_BURST, "");
/*
* * Send it in the shortened format with the TS, if it's a TS
* server; walk the list of channels, sending all the nicks that
* haven't been sent yet for each channel, then send the channel
* itself -- it's less obvious than sending all nicks first, but
* on the receiving side memory will be allocated more nicely
* saving a few seconds in the handling of a split -orabidoo
*/
for (chptr = channel; chptr; chptr = chptr->nextch) {
for (ptr = chptr->members.head; ptr; ptr = ptr->next) {
cm = ptr->data;
acptr = cm->client_p;
if (!NickIsSent(acptr)) {
if (acptr->from != cptr) {
sendnick_TS(cptr, acptr);
acptr->protoflags |= PFLAGS_NICKISSENT;
}
}
}
send_channel_modes(cptr, chptr);
}
/*
* also send out those that are not on any channel
*/
for (acptr = &me; acptr; acptr = acptr->prev) {
if (!NickIsSent(acptr)) {
if (acptr->from != cptr) {
sendnick_TS(cptr, acptr);
acptr->protoflags |= PFLAGS_NICKISSENT;
}
}
}
for (acptr = &me; acptr; acptr = acptr->prev)
acptr->protoflags &= ~PFLAGS_NICKISSENT;
if (ZipOut(cptr)) {
unsigned long inb, outb;
double rat;
zip_get_stats(cptr->serv->zout, &inb, &outb, &rat);
if (inb) {
sendto_gnotice
("Connect burst to %s: %lu bytes normal, %lu compressed (%3.2f%%)",
get_client_name(cptr, HIDEME), inb, outb, rat);
sendto_serv_butone(cptr, &me, TOK1_GNOTICE,
":Connect burst to %s: %lu bytes normal, %lu compressed (%3.2f%%)",
get_client_name(cptr, HIDEME), inb, outb, rat);
}
}
/*
* stuff a PING at the end of this burst so we can figure out when
* the other side has finished processing it.
*/
cptr->flags |= FLAGS_BURST | FLAGS_PINGSENT;
if (!IsULine(cptr))
cptr->flags |= FLAGS_SOBSENT;
sendto_one_server(cptr, NULL, TOK1_PING, ":%C", &me);
return 0;
}
/*
* serv_connect() - initiate a server connection
*
* inputs - pointer to conf
* - pointer to client doing the connet
* output -
* side effects -
*
* This code initiates a connection to a server. It first checks to make
* sure the given server exists. If this is the case, it creates a socket,
* creates a client, saves the socket information in the client, and
* initiates a connection to the server through comm_connect_tcp(). The
* completion of this goes through serv_completed_connection().
*
* We return 1 if the connection is attempted, since we don't know whether
* it suceeded or not, and 0 if it fails in here somewhere.
*/
int serv_connect(aConfItem *aconf, struct Client *by)
{
aClient *client_p;
int fd;
char buf[HOSTIPLEN];
/* Make sure aconf is useful */
assert(aconf != NULL);
/* log */
inetntop(DEF_FAM, &IN_ADDR(aconf->ipnum), buf, HOSTIPLEN);
/*
* Make sure this server isn't already connected
* Note: aconf should ALWAYS be a valid C: line
*/
if ((client_p = find_server(aconf->name))) {
sendto_gnotice("Server %s already present from %s",
aconf->name, get_client_name(client_p, SHOW_IP));
if (by && IsPerson(by) && !MyClient(by))
send_me_notice(by,
":Server %s already present from %s",
aconf->name, get_client_name(client_p, HIDEME));
return 0;
}
/* create a socket for the server connection */
if ((fd = comm_open(DEF_FAM, SOCK_STREAM, 0, NULL)) < 0) {
/* Eek, failure to create the socket */
report_error("opening stream socket to %s: %s", aconf->name, errno);
return 0;
}
/* servernames are always guaranteed under HOSTLEN chars */
fd_note(fd, "Server: %s", aconf->name);
/* Create a local client */
client_p = make_client(NULL);
/* Copy in the server, hostname, fd */
strlcpy_irc(client_p->name, aconf->name, HOSTLEN);
strlcpy_irc(client_p->hostip, aconf->host, HOSTLEN);
inetntop(DEF_FAM, &IN_ADDR(aconf->ipnum), client_p->hostip, HOSTIPLEN);
client_p->fd = fd;
/*
* Set up the initial server evilness, ripped straight from
* connect_server(), so don't blame me for it being evil.
* -- adrian
*/
if (!set_non_blocking(client_p->fd))
report_error(NONB_ERROR_MSG, get_client_name(client_p, TRUE), errno);
if (!set_sock_buffers(client_p->fd, READBUF_SIZE))
report_error(SETBUF_ERROR_MSG, get_client_name(client_p, TRUE), errno);
/*
* NOTE: if we're here we have a valid C:Line and the client should
* have started the connection and stored the remote address/port and
* ip address name in itself
*
* Attach config entries to client here rather than in
* serv_connect_callback(). This to avoid null pointer references.
*/
if (!attach_cn_lines(client_p, aconf->name, aconf->host)) {
dlink_node * ptr;
ptr = dlinkFind(&unknown_list, client_p);
if (ptr)
dlinkDeleteNode(ptr, &unknown_list);
sendto_gnotice("Host %s is not enabled for connecting:no C/N-line", aconf->name);
if (by && IsPerson(by) && !MyClient(by))
send_me_notice(by, ":Connect to host %s failed.", client_p->name);
det_confs_butmask(client_p, 0);
free_client(client_p);
return 0;
}
/*
* at this point we have a connection in progress and C/N lines
* attached to the client, the socket info should be saved in the
* client and it should either be resolved or have a valid address.
*
* The socket has been connected or connect is in progress.
*/
make_server(client_p);
if (by && IsPerson(by)) {
strcpy(client_p->serv->bynick, by->name);
if (client_p->serv->user)
free_user(client_p->serv->user);
client_p->serv->user = by->user;
} else {
strcpy(client_p->serv->bynick, "AutoConn.");
if (client_p->serv->user)
free_user(client_p->serv->user);
client_p->serv->user = NULL;
}
SetConnecting(client_p);
add_client_to_list(client_p);
/* from def_fam */
client_p->aftype = aconf->aftype;
/* Now, initiate the connection */
if ((aconf->aftype == AF_INET) && ServerInfo.specific_ipv4_vhost) {
struct irc_sockaddr ipn;
memset(&ipn, 0, sizeof(struct irc_sockaddr));
S_FAM(ipn) = DEF_FAM;
S_PORT(ipn) = 0;
copy_s_addr(S_ADDR(ipn), IN_ADDR(ServerInfo.address));
comm_connect_tcp(client_p->fd, aconf->host,
aconf->port,
(struct sockaddr *) &SOCKADDR(ipn),
sizeof(struct irc_sockaddr),
serv_connect_callback, client_p, aconf->aftype, 30);
#ifdef IPV6
} else if ((aconf->aftype == AF_INET6) && ServerInfo.specific_ipv6_vhost) {
struct irc_sockaddr ipn;
memset(&ipn, 0, sizeof(struct irc_sockaddr));
S_FAM(ipn) = AF_INET6;
S_PORT(ipn) = 0;
copy_s_addr(S_ADDR(ipn), IN_ADDR(ServerInfo.address6));
comm_connect_tcp(client_p->fd, aconf->host, aconf->port,
(struct sockaddr *) &SOCKADDR(ipn), sizeof(struct irc_sockaddr),
serv_connect_callback, client_p, aconf->aftype, 30);
#endif
} else {
comm_connect_tcp(client_p->fd, aconf->host,
aconf->port, NULL, 0, serv_connect_callback, client_p, aconf->aftype, 30);
}
return 1;
}
/*
* serv_connect_callback() - complete a server connection.
*
* This routine is called after the server connection attempt has
* completed. If unsucessful, an error is sent to ops and the client
* is closed. If sucessful, it goes through the initialisation/check
* procedures, the capabilities are sent, and the socket is then
* marked for reading.
*/
static void serv_connect_callback(int fd, int status, void *data)
{
struct hook_data thisdata;
aClient *client_p = data;
aConfItem *aconf;
/* First, make sure its a real client! */
assert(client_p != NULL);
assert(client_p->fd == fd);
/* Next, for backward purposes, record the ip of the server */
copy_s_addr(IN_ADDR(client_p->ip), S_ADDR(fd_table[fd].connect.hostaddr));
/* Check the status */
if (status != COMM_OK) {
/* We have an error, so report it and quit */
sendto_gnotice("Error connecting to %s: %s", client_p->name, comm_errstr(status));
client_p->flags |= FLAGS_DEADSOCKET;
exit_client(client_p, &me, "Connection error");
return;
}
/* COMM_OK, so continue the connection procedure */
aconf = find_conf_name(&client_p->confs, client_p->name, CONF_SERVER);
if (!aconf) {
sendto_gnotice("Lost C-Line for %s", get_client_name(client_p, HIDEME));
exit_client(client_p, &me, "Lost C-line");
return;
}
/* Next, send the initial handshake */
SetHandshake(client_p);
if (IsConfUltimate(aconf))
client_p->protoflags |= PFLAGS_ULINE;
if (IsConfHub(aconf))
client_p->protoflags |= PFLAGS_ISHUB;
if (IsConfCompressed(aconf))
client_p->protoflags |= PFLAGS_DOZIP;
if (IsConfEncrypted(aconf))
client_p->protoflags |= PFLAGS_DODKEY;
/*
* jdc -- Check and send spasswd, not passwd.
*/
thisdata.check = EmptyString(aconf->passwd);
thisdata.client_p = client_p;
thisdata.confitem = aconf;
hook_call_event(hookid_connect_to_server, &thisdata);
/*
* If we've been marked dead because a send failed, just exit
* here now and save everyone the trouble of us ever existing.
*/
if (IsDead(client_p)) {
sendto_gnotice("%s went dead during handshake", client_p->name);
exit_client(client_p, &me, "Went dead during handshake");
return;
}
/* If we get here, we're ok, so lets start reading some data */
comm_setselect(fd, FDLIST_IRCD, COMM_SELECT_READ, read_server_packet, client_p, 0);
}
syntax highlighted by Code2HTML, v. 0.9.1