/************************************************************************
* IRC - Internet Relay Chat, server/s_bsd.c
*
* Copyright (C) 2000-2003 TR-IRCD Development
*
* Copyright (C) 1990 Jarkko Oikarinen and
* University of Oulu, Co Center
*
* 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 "h.h"
#include "fd.h"
#include "s_bsd.h"
#include "event.h"
#include "listener.h"
#include "numeric.h"
#include "packet.h"
#include "resnew.h"
#include "s_auth.h"
#include "s_conf.h"
#include "hook.h"
#include "dh.h"
#ifndef IN_LOOPBACKNET
#define IN_LOOPBACKNET 0x7f
#endif
#ifndef INADDR_NONE
#define INADDR_NONE ((unsigned int) 0xffffffff)
#endif
time_t timeofday;
char *const NONB_ERROR_MSG = "set_non_blocking failed for %s:%s";
char *const OPT_ERROR_MSG = "disable_sock_options failed for %s:%s";
char *const SETBUF_ERROR_MSG = "set_sock_buffers failed for server %s:%s";
static const char *comm_err_str[] = {
"Comm OK",
"Error during bind()",
"Error during DNS lookup",
"connect timeout",
"Error during connect()",
"Comm Error"
};
static void comm_connect_callback(int fd, int status);
static PF comm_connect_timeout;
static void comm_connect_dns_callback(void *vptr, adns_answer * reply);
static void comm_tryall_callback(int fd, void *data);
static PF comm_connect_tryconnect;
static int hookid_read_packet = -1;
static int hookid_make_listener_client = -1;
void init_bsd(void) {
hookid_read_packet = hook_add_event("read packet");
hookid_make_listener_client = hook_add_event("make listener client");
}
/*
* get_sockerr - get the error value from the socket or the current errno
*
* Get the *real* error from the socket (well try to anyway..).
* This may only work when SO_DEBUG is enabled but its worth the
* gamble anyway.
*/
int get_sockerr(int fd)
{
int errtmp = errno;
#ifdef SO_ERROR
int err = 0;
socklen_t len = sizeof(err);
if (-1 < fd && !getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *) &err, (int *) &len)) {
if (err)
errtmp = err;
}
errno = errtmp;
#endif
return errtmp;
}
/*
* report_error - report an error from an errno.
* Record error to log and also send a copy to all *LOCAL* opers online.
*
* text is a *format* string for outputing error. It must
* contain only two '%s', the first will be replaced
* by the sockhost from the client_p, and the latter will
* be taken from sys_errlist[errno].
*
* client_p if not NULL, is the *LOCAL* client associated with
* the error.
*
* Cannot use perror() within daemon. stderr is closed in
* ircd and cannot be used. And, worse yet, it might have
* been reassigned to a normal connection...
*
* Actually stderr is still there IFF ircd was run with -s --Rodder
*/
void report_error(char *text, const char *who, int error)
{
char buf[1024];
who = (who) ? who : "";
ircsnprintf(buf, 1024, text, who, strerror(error));
sendto_lev(SNOTICE_LEV, buf);
logevent_call(LogSys.report_error, buf);
}
/*
* set_sock_buffers - set send and receive buffers for socket
*
* inputs - fd file descriptor
* - size to set
* output - returns true (1) if successful, false (0) otherwise
* side effects -
*/
int set_sock_buffers(int fd, int size)
{
if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (char *) &size, sizeof(size)) ||
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (char *) &size, sizeof(size)))
return 0;
return 1;
}
/*
* disable_sock_options
*
* inputs - fd
* output - returns true (1) if successful, false (0) otherwise
* side effects - disable_sock_options - if remote has any socket options set,
* disable them
*/
int disable_sock_options(int fd)
{
#if defined(IP_OPTIONS) && defined(IPPROTO_IP) && !defined(IPV6)
if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, NULL, 0))
return 0;
#endif
return 1;
}
/*
* set_non_blocking - Set the client connection into non-blocking mode.
*
* inputs - fd to set into non blocking mode
* output - 1 if successful 0 if not
* side effects - use POSIX compliant non blocking and
* be done with it.
*/
int set_non_blocking(int fd)
{
int nonb = 0;
int res;
nonb |= O_NONBLOCK;
#ifdef USE_SIGIO
setup_sigio_fd(fd);
#endif
res = fcntl(fd, F_GETFL, 0);
if (-1 == res || fcntl(fd, F_SETFL, res | nonb) == -1)
return 0;
fd_table[fd].flags.nonblocking = 1;
return 1;
}
/*
* 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.
*/
void add_connection(struct Listener *listener, int fd)
{
aClient *new_client;
#ifdef HAVE_ENCRYPTION_ON
int new_ssl = 0;
#endif
socklen_t len = sizeof(struct irc_sockaddr);
struct irc_sockaddr irn;
assert(0 != listener);
/*
* get the client socket name from the socket
* the client has already been checked out in accept_connection
*/
if (getpeername(fd, (struct sockaddr *) &SOCKADDR(irn), (int *) &len)) {
report_error("Failed in adding new connection %s :%s", get_listener_name(listener), errno);
ircstp->is_ref++;
fd_close(fd);
return;
}
new_client = make_client(NULL);
/*
* copy address to 'sockhost' as a string, copy it to host too
* so we have something valid to put into error messages...
*/
copy_s_addr(IN_ADDR(new_client->ip), S_ADDR(irn));
inetntop(DEF_FAM, &IN_ADDR(new_client->ip), new_client->hostip, HOSTIPLEN);
#ifdef IPV6
if((!IN6_IS_ADDR_V4MAPPED(&IN_ADDR2(new_client->ip))) &&
(!IN6_IS_ADDR_V4COMPAT(&IN_ADDR2(new_client->ip))))
new_client->aftype = AF_INET6;
else {
memmove(&new_client->ip.sins.sin.s_addr,&IN_ADDR(new_client->ip)[12], sizeof(struct in_addr));
new_client->aftype = AF_INET;
}
#else
new_client->aftype = AF_INET;
#endif
new_client->connectport = htons(S_PORT(irn));
strcpy(new_client->sockhost, new_client->hostip);
if (IsListenerHttp(listener)) {
struct hook_data thisdata;
thisdata.client_p = new_client;
if (hook_call_event(hookid_make_listener_client, &thisdata)) {
dlink_node * ptr;
ptr = dlinkFind(&unknown_list, new_client);
if (ptr)
dlinkDeleteNode(ptr, &unknown_list);
free_client(new_client);
new_client = thisdata.client_p;
}
}
new_client->fd = fd;
new_client->listener = listener;
++listener->ref_count;
#ifdef HAVE_ENCRYPTION_ON
if (IsListenerClientSSL(listener) && GeneralOpts.doing_ssl && !(new_client->ssl)) {
new_ssl = 1;
/*SSL client init */
if((new_client->ssl = SSL_new(ircdssl_ctx)) == NULL) {
dlink_node * ptr;
ptr = dlinkFind(&unknown_list, new_client);
if (ptr)
dlinkDeleteNode(ptr, &unknown_list);
sendto_lev(DEBUG_LEV, "Could not create new ssl connection %s", new_client->hostip);
ircstp->is_ref++;
free_client(new_client);
return;
}
SetSSL(new_client);
}
#endif
if (!set_non_blocking(new_client->fd))
report_error(NONB_ERROR_MSG, get_client_name(new_client, TRUE), errno);
if (!set_sock_buffers(new_client->fd, READBUF_SIZE))
report_error(SETBUF_ERROR_MSG, get_client_name(new_client, SHOW_IP), errno);
if (!disable_sock_options(new_client->fd))
report_error(OPT_ERROR_MSG, get_client_name(new_client, TRUE), errno);
#ifdef HAVE_ENCRYPTION_ON
if (IsSSL(new_client) && new_ssl) {
SSL_set_fd(new_client->ssl, new_client->fd);
if (!safe_SSL_accept(new_client, new_client->fd)) {
dlink_node * ptr;
ptr = dlinkFind(&unknown_list, new_client);
if (ptr)
dlinkDeleteNode(ptr, &unknown_list);
SSL_set_shutdown(new_client->ssl, SSL_RECEIVED_SHUTDOWN);
SSL_smart_shutdown(new_client->ssl);
SSL_free(new_client->ssl);
ircstp->is_ref++;
free_client(new_client);
return;
}
}
#endif
if (IsListenerHttp(new_client->listener))
comm_setselect(fd, FDLIST_HTTPD, COMM_SELECT_WRITE, comm_tryall_callback, new_client, 0);
else
comm_setselect(fd, FDLIST_IRCD, COMM_SELECT_WRITE, comm_tryall_callback, new_client, 0);
}
void dead_link_on_read(struct Client *client_p, int error)
{
/*
* ...hmm, with non-blocking sockets we might get
* here from quite valid reasons, although.. why
* would select report "data available" when there
* wasn't... so, this must be an error anyway... --msa
* actually, EOF occurs when read() returns 0 and
* in due course, select() returns that fd as ready
* for reading even though it ends up being an EOF. -avalon
*/
char errmsg[255];
int current_error = get_sockerr(client_p->fd);
logevent_call(LogSys.read_error, client_p, client_p->fd, current_error, error);
if (IsServer(client_p) || IsHandshake(client_p)) {
int connected = timeofday - client_p->firsttime;
if (error == 0) {
sendto_gnotice("Server %s closed the connection", get_client_name(client_p, TRUE));
} else {
report_error("Lost connection to %s: %d",
get_client_name(client_p, TRUE), current_error);
}
sendto_gnotice("%s had been connected for %d day%s, %2d:%02d:%02d",
client_p->name, connected / 86400,
(connected / 86400 == 1) ? "" : "s",
(connected % 86400) / 3600, (connected % 3600) / 60, connected % 60);
}
if (error == 0) {
strcpy(errmsg, "Remote host closed the connection");
} else {
ircsprintf(errmsg, "Read error: %d (%s)", current_error, strerror(current_error));
}
exit_client(client_p, &me, errmsg);
}
/*
* stolen from squid - its a neat (but overused! :) routine which we
* can use to see whether we can ignore this errno or not. It is
* generally useful for non-blocking network IO related errnos.
* -- adrian
*/
int ignoreErrno(int ierrno)
{
switch (ierrno) {
case EINPROGRESS:
case EWOULDBLOCK:
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
case EALREADY:
case EINTR:
#ifdef ERESTART
case ERESTART:
#endif
return 1;
default:
return 0;
}
/* NOTREACHED */
return 0;
}
/*
* comm_settimeout() - set the socket timeout
*
* Set the timeout for the fd
*/
void comm_settimeout(int fd, time_t timeout, PF * callback, void *cbdata)
{
assert(fd > -1);
assert(fd_table[fd].flags.open);
fd_table[fd].timeout = timeofday + (timeout / 1000);
fd_table[fd].timeout_handler = callback;
fd_table[fd].timeout_data = cbdata;
}
/*
* comm_setflush() - set a flush function
*
* A flush function is simply a function called if found during
* comm_timeouts(). Its basically a second timeout, except in this case
* I'm too lazy to implement multiple timeout functions! :-)
* its kinda nice to have it seperate, since this is designed for
* flush functions, and when comm_close() is implemented correctly
* with close functions, we _actually_ don't call comm_close() here ..
*/
void comm_setflush(int fd, time_t timeout, PF * callback, void *cbdata)
{
if (fd < 1)
return;
fd_table[fd].flush_timeout = timeofday + (timeout / 1000);
fd_table[fd].flush_handler = callback;
fd_table[fd].flush_data = cbdata;
}
/*
* comm_checktimeouts() - check the socket timeouts
*
* All this routine does is call the given callback/cbdata, without closing
* down the file descriptor. When close handlers have been implemented,
* this will happen.
*/
void comm_checktimeouts(void *notused)
{
int fd;
PF *hdl;
void *data;
for (fd = 0; fd <= highest_fd; fd++) {
if (!fd_table[fd].flags.open)
continue;
if (fd_table[fd].flags.closing)
continue;
/* check flush functions */
if (fd_table[fd].flush_handler &&
fd_table[fd].flush_timeout > 0 && fd_table[fd].flush_timeout < timeofday) {
hdl = fd_table[fd].flush_handler;
data = fd_table[fd].flush_data;
comm_setflush(fd, 0, NULL, NULL);
hdl(fd, data);
}
/* check timeouts */
if (fd_table[fd].timeout_handler &&
fd_table[fd].timeout > 0 && fd_table[fd].timeout < timeofday) {
/* Call timeout handler */
hdl = fd_table[fd].timeout_handler;
data = fd_table[fd].timeout_data;
comm_settimeout(fd, 0, NULL, NULL);
hdl(fd, fd_table[fd].timeout_data);
}
}
}
/*
* void comm_connect_tcp(int fd, const char *host, u_short port,
* struct sockaddr *clocal, int socklen,
* CNCB *callback, void *data, int aftype)
* Input: An fd to connect with, a host and port to connect to,
* a local sockaddr to connect from + length(or NULL to use the
* default), a callback, the data to pass into the callback, the
* address family.
* Output: None.
* Side-effects: A non-blocking connection to the host is started, and
* if necessary, set up for selection. The callback given
* may be called now, or it may be called later.
*/
void
comm_connect_tcp(int fd, const char *host, u_short port,
struct sockaddr *clocal, int socklen, CNCB * callback,
void *data, int aftype, int timeout)
{
fd_table[fd].flags.called_connect = 1;
assert(callback != NULL);
fd_table[fd].connect.callback = callback;
fd_table[fd].connect.data = data;
S_FAM(fd_table[fd].connect.hostaddr) = DEF_FAM;
S_PORT(fd_table[fd].connect.hostaddr) = htons(port);
/* Note that we're using a passed sockaddr here. This is because
* generally you'll be bind()ing to a sockaddr grabbed from
* getsockname(), so this makes things easier.
* XXX If NULL is passed as local, we should later on bind() to the
* virtual host IP, for completeness.
* -- adrian
*/
if ((clocal != NULL) && (bind(fd, clocal, socklen) < 0)) {
/* Failure, call the callback with COMM_ERR_BIND */
comm_connect_callback(fd, COMM_ERR_BIND);
/* ... and quit */
return;
}
/* Next, if we have been given an IP, get the addr and skip the
* DNS check (and head direct to comm_connect_tryconnect().
*/
if (inetpton(DEF_FAM, host, S_ADDR(&fd_table[fd].connect.hostaddr)) <= 0) {
/* Send the DNS request, for the next level */
fd_table[fd].dns_query = (struct DNSQuery *) MyMalloc(sizeof(struct DNSQuery));
fd_table[fd].dns_query->ptr = &fd_table[fd];
fd_table[fd].dns_query->callback = comm_connect_dns_callback;
adns_gethost(host, aftype, fd_table[fd].dns_query);
} else {
/* We have a valid IP, so we just call tryconnect */
/* Make sure we actually set the timeout here .. */
comm_settimeout(fd, timeout * 1000, comm_connect_timeout, NULL);
comm_connect_tryconnect(fd, NULL);
}
}
/*
* comm_connect_callback() - call the callback, and continue with life
*/
static void comm_connect_callback(int fd, int status)
{
CNCB *hdl;
/* This check is gross..but probably necessary */
if (fd_table[fd].connect.callback == NULL)
return;
/* Clear the connect flag + handler */
hdl = fd_table[fd].connect.callback;
fd_table[fd].connect.callback = NULL;
fd_table[fd].flags.called_connect = 0;
/* Clear the timeout handler */
comm_settimeout(fd, 0, NULL, NULL);
/* Call the handler */
hdl(fd, status, fd_table[fd].connect.data);
}
/*
* comm_tryall_callback - This does ensure the finishing of an ssl
* accept routine before we continue with identd lookups.
* -TimeMr14C
*/
static void comm_tryall_callback(int fd, void *data)
{
int doauth = -1;
aClient *cptr = data;
struct hook_data thisdata;
fdlist_t fdlist = FDLIST_NONE;
if (IsListenerHttp(cptr->listener)) {
fdlist = FDLIST_HTTPD;
doauth = 0;
} else if (IsListenerClient(cptr->listener)) {
fdlist = FDLIST_IRCD;
doauth = 2;
} else if (IsListenerServer(cptr->listener)) {
fdlist = FDLIST_IRCD;
doauth = 1;
} else if (IsListenerService(cptr->listener)) {
fdlist = FDLIST_IRCD;
doauth = 1;
}
#ifdef HAVE_ENCRYPTION_ON
if (IsSSL(cptr)) {
if (!SSL_is_init_finished(cptr->ssl)) {
if (!safe_SSL_accept(cptr, cptr->fd)) {
dead_link_on_read(cptr, errno);
return;
} else {
comm_setselect(fd, fdlist, COMM_SELECT_WRITE, comm_tryall_callback, cptr, 0);
return;
}
}
}
#endif
if (doauth == 2) {
start_auth(cptr);
} else if (doauth == 1) {
start_lookup(cptr);
} else if (doauth == 0) {
thisdata.check = fd;
thisdata.client_p = cptr;
hook_call_event(hookid_read_packet, &thisdata);
}
}
/*
* comm_connect_timeout() - this gets called when the socket connection
* times out. This *only* can be called once connect() is initially
* called ..
*/
static void comm_connect_timeout(int fd, void *notused)
{
/* error! */
comm_connect_callback(fd, COMM_ERR_TIMEOUT);
}
/*
* comm_connect_dns_callback() - called at the completion of the DNS request
*
* The DNS request has completed, so if we've got an error, return it,
* otherwise we initiate the connect()
*/
static void comm_connect_dns_callback(void *vptr, adns_answer * reply)
{
fde_t *F = vptr;
if (!reply) {
comm_connect_callback(F->fd, COMM_ERR_DNS);
return;
}
if (reply->status != adns_s_ok) {
/* Yes, callback + return */
comm_connect_callback(F->fd, COMM_ERR_DNS);
MyFree(reply);
MyFree(F->dns_query);
return;
}
/* No error, set a 10 second timeout */
comm_settimeout(F->fd, 30 * 1000, comm_connect_timeout, NULL);
/* Copy over the DNS reply info so we can use it in the connect() */
/*
* Note we don't fudge the refcount here, because we aren't keeping
* the DNS record around, and the DNS cache is gone anyway..
* -- adrian
*/
#ifdef IPV6
if(reply->rrs.addr->addr.sa.sa_family == AF_INET6) {
copy_s_addr(S_ADDR(F->connect.hostaddr), reply->rrs.addr->addr.inet6.sin6_addr.s6_addr);
} else {
/* IPv4 mapped address */
/* This is lazy... */
memset(&F->connect.hostaddr.sins.sin6.sin6_addr.s6_addr, 0x0000, 10);
memset(&F->connect.hostaddr.sins.sin6.sin6_addr.s6_addr[10], 0xffff, 2);
memcpy(&F->connect.hostaddr.sins.sin6.sin6_addr.s6_addr[12], &reply->rrs.addr->addr.inet.sin_addr.s_addr, 4);
}
#else
F->connect.hostaddr.sins.sin.sin_addr.s_addr = reply->rrs.addr->addr.inet.sin_addr.s_addr;
#endif
/* Now, call the tryconnect() routine to try a connect() */
MyFree(reply);
comm_connect_tryconnect(F->fd, NULL);
}
/* static void comm_connect_tryconnect(int fd, void *notused)
* Input: The fd, the handler data(unused).
* Output: None.
* Side-effects: Try and connect with pending connect data for the FD. If
* we succeed or get a fatal error, call the callback.
* Otherwise, it is still blocking or something, so register
* to select for a write event on this FD.
*/
static void comm_connect_tryconnect(int fd, void *notused)
{
int retval;
assert(fd_table[fd].connect.callback != NULL);
/* Try the connect() */
retval =
connect(fd,
(struct sockaddr *) &SOCKADDR(fd_table[fd].connect.hostaddr),
sizeof(struct irc_sockaddr));
/* Error? */
if (retval < 0) {
/*
* If we get EISCONN, then we've already connect()ed the socket,
* which is a good thing.
* -- adrian
*/
if (errno == EISCONN)
comm_connect_callback(fd, COMM_OK);
else if (ignoreErrno(errno))
/* Ignore error? Reschedule */
comm_setselect(fd, FDLIST_IRCD, COMM_SELECT_WRITE, comm_connect_tryconnect, NULL, 0);
else
/* Error? Fail with COMM_ERR_CONNECT */
comm_connect_callback(fd, COMM_ERR_CONNECT);
return;
}
/* If we get here, we've suceeded, so call with COMM_OK */
comm_connect_callback(fd, COMM_OK);
}
/*
* comm_error_str() - return an error string for the given error condition
*/
const char *comm_errstr(int error)
{
if (error < 0 || error >= COMM_ERR_MAX)
return "Invalid error number!";
return comm_err_str[error];
}
/*
* comm_open() - open a socket
*
* This is a highly highly cut down version of squid's comm_open() which
* for the most part emulates socket(), *EXCEPT* it fails if we're about
* to run out of file descriptors.
*/
int comm_open(int family, int sock_type, int proto, const char *note)
{
int fd;
/* First, make sure we aren't going to run out of file descriptors */
if (number_fd >= MAXCONNECTIONS) {
errno = ENFILE;
return -1;
}
/*
* Next, we try to open the socket. We *should* drop the reserved FD
* limit if/when we get an error, but we can deal with that later.
* XXX !!! -- adrian
*/
fd = socket(family, sock_type, proto);
if (fd < 0)
return -1; /* errno will be passed through, yay.. */
/* Set the socket non-blocking, and other wonderful bits */
if (!set_non_blocking(fd)) {
close(fd);
return -1;
}
/* Next, update things in our fd tracking */
fd_open(fd, FD_SOCKET, note);
return fd;
}
/*
* comm_accept() - accept an incoming connection
*
* This is a simple wrapper for accept() which enforces FD limits like
* comm_open() does.
*/
int comm_accept(int fd, struct irc_sockaddr *pn)
{
int newfd;
socklen_t addrlen = sizeof(struct irc_sockaddr);
if (number_fd >= MAXCONNECTIONS) {
errno = ENFILE;
return -1;
}
/*
* Next, do the accept(). if we get an error, we should drop the
* reserved fd limit, but we can deal with that when comm_open()
* also does it. XXX -- adrian
*/
newfd = accept(fd, (struct sockaddr *) &PSOCKADDR(pn), (int *) &addrlen);
if (newfd < 0)
return -1;
/* Set the socket non-blocking, and other wonderful bits */
if (!set_non_blocking(newfd)) {
close(newfd);
return -1;
}
/* Next, tag the FD as an incoming connection */
fd_open(newfd, FD_SOCKET, "Incoming connection");
/* .. and return */
return newfd;
}
syntax highlighted by Code2HTML, v. 0.9.1