mirror of
https://github.com/matrix-construct/construct
synced 2024-11-03 12:28:52 +01:00
61b517ca3c
* To benefit from the precompiled-header (PCH) it MUST provide "the first C token." Advantages: Never worry about the include stack again. Remember, this means one less thing for random module developers, community people learning C++, and new developers to deal with. It should reduce the learning curve and barrier for participation. Disadvantages: Makes overall compilation a bit slower, especially without any additional work to improve it again. There are several opportunities, places where the PCH is probably being ignored, etc that can be addressed.
604 lines
15 KiB
C++
604 lines
15 KiB
C++
/*
|
|
* ircd-ratbox: A slightly useful ircd.
|
|
* listener.c: Listens on a port.
|
|
*
|
|
* Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
|
|
* Copyright (C) 1996-2002 Hybrid Development Team
|
|
* Copyright (C) 2002-2005 ircd-ratbox development team
|
|
*
|
|
* 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
|
|
*/
|
|
|
|
|
|
namespace ircd {
|
|
|
|
#if defined(NO_IN6ADDR_ANY) && defined(RB_IPV6)
|
|
static const struct in6_addr in6addr_any =
|
|
{ { { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 } } };
|
|
#endif
|
|
|
|
static struct Listener *ListenerPollList = NULL;
|
|
static int accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, void *data);
|
|
static void accept_callback(rb_fde_t *F, int status, struct sockaddr *addr, rb_socklen_t addrlen, void *data);
|
|
static SSL_OPEN_CB accept_sslcallback;
|
|
|
|
static struct Listener *
|
|
make_listener(struct rb_sockaddr_storage *addr)
|
|
{
|
|
struct Listener *listener = (struct Listener *) rb_malloc(sizeof(struct Listener));
|
|
s_assert(0 != listener);
|
|
listener->name = me.name;
|
|
listener->F = NULL;
|
|
|
|
memcpy(&listener->addr, addr, sizeof(struct rb_sockaddr_storage));
|
|
listener->next = NULL;
|
|
return listener;
|
|
}
|
|
|
|
void
|
|
free_listener(struct Listener *listener)
|
|
{
|
|
s_assert(NULL != listener);
|
|
if(listener == NULL)
|
|
return;
|
|
/*
|
|
* remove from listener list
|
|
*/
|
|
if(listener == ListenerPollList)
|
|
ListenerPollList = listener->next;
|
|
else
|
|
{
|
|
struct Listener *prev = ListenerPollList;
|
|
for (; prev; prev = prev->next)
|
|
{
|
|
if(listener == prev->next)
|
|
{
|
|
prev->next = listener->next;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* free */
|
|
rb_free(listener);
|
|
}
|
|
|
|
#define PORTNAMELEN 6 /* ":31337" */
|
|
|
|
/*
|
|
* get_listener_port - return displayable listener port
|
|
*/
|
|
static uint16_t
|
|
get_listener_port(const struct Listener *listener)
|
|
{
|
|
return ntohs(GET_SS_PORT(&listener->addr));
|
|
}
|
|
|
|
/*
|
|
* get_listener_name - return displayable listener name and port
|
|
* returns "host.foo.org:6667" for a given listener
|
|
*/
|
|
const char *
|
|
get_listener_name(const struct Listener *listener)
|
|
{
|
|
static char buf[HOSTLEN + HOSTLEN + PORTNAMELEN + 4];
|
|
|
|
s_assert(NULL != listener);
|
|
if(listener == NULL)
|
|
return NULL;
|
|
|
|
snprintf(buf, sizeof(buf), "%s[%s/%u]",
|
|
me.name, listener->name, get_listener_port(listener));
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* show_ports - send port listing to a client
|
|
* inputs - pointer to client to show ports to
|
|
* output - none
|
|
* side effects - show ports
|
|
*/
|
|
void
|
|
show_ports(struct Client *source_p)
|
|
{
|
|
struct Listener *listener = 0;
|
|
|
|
for (listener = ListenerPollList; listener; listener = listener->next)
|
|
{
|
|
sendto_one_numeric(source_p, RPL_STATSPLINE,
|
|
form_str(RPL_STATSPLINE), 'P',
|
|
get_listener_port(listener),
|
|
IsOperAdmin(source_p) ? listener->name : me.name,
|
|
listener->ref_count, (listener->active) ? "active" : "disabled",
|
|
listener->ssl ? " ssl" : "");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* inetport - create a listener socket in the AF_INET or AF_INET6 domain,
|
|
* bind it to the port given in 'port' and listen to it
|
|
* returns true (1) if successful false (0) on error.
|
|
*/
|
|
|
|
static int
|
|
inetport(struct Listener *listener)
|
|
{
|
|
rb_fde_t *F;
|
|
int opt = 1;
|
|
const char *errstr;
|
|
|
|
/*
|
|
* At first, open a new socket
|
|
*/
|
|
|
|
F = rb_socket(GET_SS_FAMILY(&listener->addr), SOCK_STREAM, 0, "Listener socket");
|
|
|
|
#ifdef RB_IPV6
|
|
if(GET_SS_FAMILY(&listener->addr) == AF_INET6)
|
|
{
|
|
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&listener->addr;
|
|
if(!IN6_ARE_ADDR_EQUAL(&in6->sin6_addr, &in6addr_any))
|
|
{
|
|
rb_inet_ntop(AF_INET6, &in6->sin6_addr, listener->vhost, sizeof(listener->vhost));
|
|
listener->name = listener->vhost;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
struct sockaddr_in *in = (struct sockaddr_in *)&listener->addr;
|
|
if(in->sin_addr.s_addr != INADDR_ANY)
|
|
{
|
|
rb_inet_ntop(AF_INET, &in->sin_addr, listener->vhost, sizeof(listener->vhost));
|
|
listener->name = listener->vhost;
|
|
}
|
|
}
|
|
|
|
if(F == NULL)
|
|
{
|
|
sendto_realops_snomask(SNO_GENERAL, L_ALL,
|
|
"Cannot open socket for listener on port %d",
|
|
get_listener_port(listener));
|
|
ilog(L_MAIN, "Cannot open socket for listener %s",
|
|
get_listener_name(listener));
|
|
return 0;
|
|
}
|
|
else if((maxconnections - 10) < rb_get_fd(F)) /* XXX this is kinda bogus*/
|
|
{
|
|
ilog_error("no more connections left for listener");
|
|
sendto_realops_snomask(SNO_GENERAL, L_ALL,
|
|
"No more connections left for listener on port %d",
|
|
get_listener_port(listener));
|
|
ilog(L_MAIN, "No more connections left for listener %s",
|
|
get_listener_name(listener));
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* XXX - we don't want to do all this crap for a listener
|
|
* set_sock_opts(listener);
|
|
*
|
|
* FIXME - doesn't this belong in librb? --Elizafox
|
|
*/
|
|
if(setsockopt(rb_get_fd(F), SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(opt)))
|
|
{
|
|
errstr = strerror(rb_get_sockerr(F));
|
|
sendto_realops_snomask(SNO_GENERAL, L_ALL,
|
|
"Cannot set SO_REUSEADDR for listener on port %d: %s",
|
|
get_listener_port(listener), errstr);
|
|
ilog(L_MAIN, "Cannot set SO_REUSEADDR for listener %s: %s",
|
|
get_listener_name(listener), errstr);
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
/* FIXME - doesn't this belong in librb? --Elizafox */
|
|
if(bind(rb_get_fd(F), (struct sockaddr *) &listener->addr, GET_SS_LEN(&listener->addr)))
|
|
{
|
|
errstr = strerror(rb_get_sockerr(F));
|
|
sendto_realops_snomask(SNO_GENERAL, L_ALL,
|
|
"Cannot bind for listener on port %d: %s",
|
|
get_listener_port(listener), errstr);
|
|
ilog(L_MAIN, "Cannot bind for listener %s: %s",
|
|
get_listener_name(listener), errstr);
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
if(rb_listen(F, SOMAXCONN, listener->defer_accept))
|
|
{
|
|
errstr = strerror(rb_get_sockerr(F));
|
|
sendto_realops_snomask(SNO_GENERAL, L_ALL,
|
|
"Cannot listen() for listener on port %d: %s",
|
|
get_listener_port(listener), errstr);
|
|
ilog(L_MAIN, "Cannot listen() for listener %s: %s",
|
|
get_listener_name(listener), errstr);
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
listener->F = F;
|
|
|
|
rb_accept_tcp(listener->F, accept_precallback, accept_callback, listener);
|
|
return 1;
|
|
}
|
|
|
|
static struct Listener *
|
|
find_listener(struct rb_sockaddr_storage *addr)
|
|
{
|
|
struct Listener *listener = NULL;
|
|
struct Listener *last_closed = NULL;
|
|
|
|
for (listener = ListenerPollList; listener; listener = listener->next)
|
|
{
|
|
if(GET_SS_FAMILY(addr) != GET_SS_FAMILY(&listener->addr))
|
|
continue;
|
|
|
|
switch(GET_SS_FAMILY(addr))
|
|
{
|
|
case AF_INET:
|
|
{
|
|
struct sockaddr_in *in4 = (struct sockaddr_in *)addr;
|
|
struct sockaddr_in *lin4 = (struct sockaddr_in *)&listener->addr;
|
|
if(in4->sin_addr.s_addr == lin4->sin_addr.s_addr &&
|
|
in4->sin_port == lin4->sin_port )
|
|
{
|
|
if(listener->F == NULL)
|
|
last_closed = listener;
|
|
else
|
|
return(listener);
|
|
}
|
|
break;
|
|
}
|
|
#ifdef RB_IPV6
|
|
case AF_INET6:
|
|
{
|
|
struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)addr;
|
|
struct sockaddr_in6 *lin6 =(struct sockaddr_in6 *)&listener->addr;
|
|
if(IN6_ARE_ADDR_EQUAL(&in6->sin6_addr, &lin6->sin6_addr) &&
|
|
in6->sin6_port == lin6->sin6_port)
|
|
{
|
|
if(listener->F == NULL)
|
|
last_closed = listener;
|
|
else
|
|
return(listener);
|
|
}
|
|
break;
|
|
|
|
}
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return last_closed;
|
|
}
|
|
|
|
|
|
/*
|
|
* add_listener- create a new listener
|
|
* port - the port number to listen on
|
|
* vhost_ip - if non-null must contain a valid IP address string in
|
|
* the format "255.255.255.255"
|
|
*/
|
|
void
|
|
add_listener(int port, const char *vhost_ip, int family, int ssl, int defer_accept, int wsock)
|
|
{
|
|
struct Listener *listener;
|
|
struct rb_sockaddr_storage vaddr;
|
|
|
|
/*
|
|
* if no port in conf line, don't bother
|
|
*/
|
|
if(port == 0)
|
|
return;
|
|
memset(&vaddr, 0, sizeof(vaddr));
|
|
SET_SS_FAMILY(&vaddr, family);
|
|
|
|
if(vhost_ip != NULL)
|
|
{
|
|
if(family == AF_INET)
|
|
{
|
|
if(rb_inet_pton(family, vhost_ip, &((struct sockaddr_in *)&vaddr)->sin_addr) <= 0)
|
|
return;
|
|
}
|
|
#ifdef RB_IPV6
|
|
else
|
|
{
|
|
if(rb_inet_pton(family, vhost_ip, &((struct sockaddr_in6 *)&vaddr)->sin6_addr) <= 0)
|
|
return;
|
|
|
|
}
|
|
#endif
|
|
} else
|
|
{
|
|
switch(family)
|
|
{
|
|
case AF_INET:
|
|
((struct sockaddr_in *)&vaddr)->sin_addr.s_addr = INADDR_ANY;
|
|
break;
|
|
#ifdef RB_IPV6
|
|
case AF_INET6:
|
|
memcpy(&((struct sockaddr_in6 *)&vaddr)->sin6_addr, &in6addr_any, sizeof(struct in6_addr));
|
|
break;
|
|
default:
|
|
return;
|
|
#endif
|
|
}
|
|
}
|
|
switch(family)
|
|
{
|
|
case AF_INET:
|
|
SET_SS_LEN(&vaddr, sizeof(struct sockaddr_in));
|
|
SET_SS_FAMILY(&vaddr, AF_INET);
|
|
SET_SS_PORT(&vaddr, htons(port));
|
|
break;
|
|
#ifdef RB_IPV6
|
|
case AF_INET6:
|
|
SET_SS_LEN(&vaddr, sizeof(struct sockaddr_in6));
|
|
SET_SS_FAMILY(&vaddr, AF_INET6);
|
|
SET_SS_PORT(&vaddr, htons(port));
|
|
break;
|
|
#endif
|
|
default:
|
|
break;
|
|
}
|
|
if((listener = find_listener(&vaddr)))
|
|
{
|
|
if(listener->F != NULL)
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
listener = make_listener(&vaddr);
|
|
listener->next = ListenerPollList;
|
|
ListenerPollList = listener;
|
|
}
|
|
|
|
listener->F = NULL;
|
|
listener->ssl = ssl;
|
|
listener->defer_accept = defer_accept;
|
|
listener->wsock = wsock;
|
|
|
|
if(inetport(listener))
|
|
listener->active = 1;
|
|
else
|
|
close_listener(listener);
|
|
}
|
|
|
|
/*
|
|
* close_listener - close a single listener
|
|
*/
|
|
void
|
|
close_listener(struct Listener *listener)
|
|
{
|
|
s_assert(listener != NULL);
|
|
if(listener == NULL)
|
|
return;
|
|
if(listener->F != NULL)
|
|
{
|
|
rb_close(listener->F);
|
|
listener->F = NULL;
|
|
}
|
|
|
|
listener->active = 0;
|
|
|
|
if(listener->ref_count)
|
|
return;
|
|
|
|
free_listener(listener);
|
|
}
|
|
|
|
/*
|
|
* close_listeners - close and free all listeners that are not being used
|
|
*/
|
|
void
|
|
close_listeners()
|
|
{
|
|
struct Listener *listener;
|
|
struct Listener *listener_next = 0;
|
|
/*
|
|
* close all 'extra' listening ports we have
|
|
*/
|
|
for (listener = ListenerPollList; listener; listener = listener_next)
|
|
{
|
|
listener_next = listener->next;
|
|
close_listener(listener);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* add_connection - creates a client which has just connected to us on
|
|
* the given fd. The sockhost field is initialized with the ip# of the host.
|
|
* The client is sent to the auth module for verification, and not put in
|
|
* any client list yet.
|
|
*/
|
|
static void
|
|
add_connection(struct Listener *listener, rb_fde_t *F, struct sockaddr *sai, struct sockaddr *lai)
|
|
{
|
|
struct Client *new_client;
|
|
bool defer = false;
|
|
s_assert(NULL != listener);
|
|
|
|
/*
|
|
* get the client socket name from the socket
|
|
* the client has already been checked out in accept_connection
|
|
*/
|
|
new_client = make_client(NULL);
|
|
|
|
if (listener->ssl)
|
|
{
|
|
rb_fde_t *xF[2];
|
|
if(rb_socketpair(AF_UNIX, SOCK_STREAM, 0, &xF[0], &xF[1], "Incoming ssld Connection") == -1)
|
|
{
|
|
free_client(new_client);
|
|
return;
|
|
}
|
|
new_client->localClient->ssl_callback = accept_sslcallback;
|
|
defer = true;
|
|
new_client->localClient->ssl_ctl = start_ssld_accept(F, xF[1], connid_get(new_client)); /* this will close F for us */
|
|
if(new_client->localClient->ssl_ctl == NULL)
|
|
{
|
|
free_client(new_client);
|
|
return;
|
|
}
|
|
F = xF[0];
|
|
SetSSL(new_client);
|
|
}
|
|
|
|
if (listener->wsock)
|
|
{
|
|
rb_fde_t *xF[2];
|
|
if(rb_socketpair(AF_UNIX, SOCK_STREAM, 0, &xF[0], &xF[1], "Incoming wsockd Connection") == -1)
|
|
{
|
|
free_client(new_client);
|
|
return;
|
|
}
|
|
new_client->localClient->ws_ctl = start_wsockd_accept(F, xF[1], connid_get(new_client)); /* this will close F for us */
|
|
if(new_client->localClient->ws_ctl == NULL)
|
|
{
|
|
free_client(new_client);
|
|
return;
|
|
}
|
|
F = xF[0];
|
|
}
|
|
|
|
memcpy(&new_client->localClient->ip, sai, sizeof(struct rb_sockaddr_storage));
|
|
memcpy(&new_client->preClient->lip, lai, sizeof(struct rb_sockaddr_storage));
|
|
|
|
/*
|
|
* copy address to 'sockhost' as a string, copy it to host too
|
|
* so we have something valid to put into error messages...
|
|
*/
|
|
rb_inet_ntop_sock((struct sockaddr *)&new_client->localClient->ip, new_client->sockhost,
|
|
sizeof(new_client->sockhost));
|
|
|
|
|
|
rb_strlcpy(new_client->host, new_client->sockhost, sizeof(new_client->host));
|
|
|
|
new_client->localClient->F = F;
|
|
new_client->localClient->listener = listener;
|
|
|
|
++listener->ref_count;
|
|
|
|
authd_initiate_client(new_client, defer);
|
|
}
|
|
|
|
static int
|
|
accept_sslcallback(struct Client *client_p, int status)
|
|
{
|
|
authd_deferred_client(client_p);
|
|
return 0; /* use default handler if status != RB_OK */
|
|
}
|
|
|
|
static const char *toofast = "ERROR :Reconnecting too fast, throttled.\r\n";
|
|
|
|
static int
|
|
accept_precallback(rb_fde_t *F, struct sockaddr *addr, rb_socklen_t addrlen, void *data)
|
|
{
|
|
struct Listener *listener = (struct Listener *)data;
|
|
char buf[BUFSIZE];
|
|
struct ConfItem *aconf;
|
|
static time_t last_oper_notice = 0;
|
|
int len;
|
|
|
|
if(listener->ssl && (!ircd_ssl_ok || !get_ssld_count()))
|
|
{
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
if((maxconnections - 10) < rb_get_fd(F)) /* XXX this is kinda bogus */
|
|
{
|
|
++ServerStats.is_ref;
|
|
/*
|
|
* slow down the whining to opers bit
|
|
*/
|
|
if((last_oper_notice + 20) <= rb_current_time())
|
|
{
|
|
sendto_realops_snomask(SNO_GENERAL, L_ALL,
|
|
"All connections in use. (%s)",
|
|
get_listener_name(listener));
|
|
last_oper_notice = rb_current_time();
|
|
}
|
|
|
|
rb_write(F, "ERROR :All connections in use\r\n", 31);
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
aconf = find_dline(addr, addr->sa_family);
|
|
if(aconf != NULL && (aconf->status & CONF_EXEMPTDLINE))
|
|
return 1;
|
|
|
|
/* Do an initial check we aren't connecting too fast or with too many
|
|
* from this IP... */
|
|
if(aconf != NULL)
|
|
{
|
|
ServerStats.is_ref++;
|
|
|
|
if(ConfigFileEntry.dline_with_reason)
|
|
{
|
|
len = snprintf(buf, sizeof(buf), "ERROR :*** Banned: %s\r\n", get_user_ban_reason(aconf));
|
|
if (len >= (int)(sizeof(buf)-1))
|
|
{
|
|
buf[sizeof(buf) - 3] = '\r';
|
|
buf[sizeof(buf) - 2] = '\n';
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
}
|
|
}
|
|
else
|
|
strcpy(buf, "ERROR :You have been D-lined.\r\n");
|
|
|
|
rb_write(F, buf, strlen(buf));
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
if(check_reject(F, addr))
|
|
return 0;
|
|
|
|
if(throttle_add(addr))
|
|
{
|
|
rb_write(F, toofast, strlen(toofast));
|
|
rb_close(F);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
accept_callback(rb_fde_t *F, int status, struct sockaddr *addr, rb_socklen_t addrlen, void *data)
|
|
{
|
|
struct Listener *listener(reinterpret_cast<struct Listener *>(data));
|
|
struct rb_sockaddr_storage lip;
|
|
rb_socklen_t locallen = sizeof(struct rb_sockaddr_storage);
|
|
|
|
ServerStats.is_ac++;
|
|
|
|
if(getsockname(rb_get_fd(F), (struct sockaddr *) &lip, &locallen) < 0)
|
|
{
|
|
/* this can fail if the connection disappeared in the meantime */
|
|
rb_close(F);
|
|
return;
|
|
}
|
|
|
|
add_connection(listener, F, addr, (struct sockaddr *)&lip);
|
|
}
|
|
|
|
}
|