mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-30 21:26:37 +02:00
39688517b0
"./unrealircd reloadtls" and there is now also a "./unrealircd status" The output is colorized if the terminal supports it (just like on the boot screen) and also the exit status is 0 for success and non-0 for failure. The purpose of all this is that you can easily detect rehash errors on the command line. These three commands communicate to UnrealIRCd via the new control UNIX socket, which is in ~/data/unrealircd.ctl. This also does a lot of other stuff because we now have an internal tool called bin/unrealircdctl which is called by ./unrealircd for some of the commands to communicate to the unrealircd.ctl socket. Later on more of the existing functionality may be moved to that tool and we may also provide it on Windows in CLI mode so people have more of the same functionality as on *NIX.
1368 lines
37 KiB
C
1368 lines
37 KiB
C
/*
|
|
* Unreal Internet Relay Chat Daemon, src/socket.c
|
|
* Copyright (C) 1990 Jarkko Oikarinen and
|
|
* University of Oulu, Computing 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 1, 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.
|
|
*/
|
|
|
|
/** @file
|
|
* @brief Socket functions such as reading, writing, connecting.
|
|
*
|
|
* The actual data parsing functions (for incoming data) are in
|
|
* src/parse.c.
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
#include "dns.h"
|
|
|
|
int OpenFiles = 0; /* GLOBAL - number of files currently open */
|
|
int readcalls = 0;
|
|
|
|
void completed_connection(int, int, void *);
|
|
void set_sock_opts(int, Client *, SocketType);
|
|
void set_ipv6_opts(int);
|
|
void close_listener(ConfigItem_listen *listener);
|
|
static char readbuf[BUFSIZE];
|
|
char zlinebuf[BUFSIZE];
|
|
extern char *version;
|
|
MODVAR time_t last_allinuse = 0;
|
|
|
|
void start_of_normal_client_handshake(Client *client);
|
|
extern void start_of_control_client_handshake(Client *client);
|
|
void proceed_normal_client_handshake(Client *client, struct hostent *he);
|
|
|
|
/** Close all connections - only used when we terminate the server (eg: /DIE or SIGTERM) */
|
|
void close_connections(void)
|
|
{
|
|
Client *client;
|
|
|
|
list_for_each_entry(client, &lclient_list, lclient_node)
|
|
{
|
|
if (client->local->fd >= 0)
|
|
{
|
|
fd_close(client->local->fd);
|
|
client->local->fd = -2;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry(client, &unknown_list, lclient_node)
|
|
{
|
|
if (client->local->fd >= 0)
|
|
{
|
|
fd_close(client->local->fd);
|
|
client->local->fd = -2;
|
|
}
|
|
|
|
if (client->local->authfd >= 0)
|
|
{
|
|
fd_close(client->local->authfd);
|
|
client->local->fd = -1;
|
|
}
|
|
}
|
|
|
|
list_for_each_entry(client, &control_list, lclient_node)
|
|
{
|
|
if (client->local->fd >= 0)
|
|
{
|
|
fd_close(client->local->fd);
|
|
client->local->fd = -2;
|
|
}
|
|
}
|
|
|
|
close_unbound_listeners();
|
|
|
|
OpenFiles = 0;
|
|
|
|
#ifdef _WIN32
|
|
WSACleanup();
|
|
#endif
|
|
}
|
|
|
|
/** Accept an incoming connection.
|
|
* @param listener_fd The file descriptor of a listen() socket.
|
|
* @param data The listen { } block configuration data.
|
|
*/
|
|
static void listener_accept(int listener_fd, int revents, void *data)
|
|
{
|
|
ConfigItem_listen *listener = data;
|
|
int cli_fd;
|
|
|
|
if ((cli_fd = fd_accept(listener->fd)) < 0)
|
|
{
|
|
if ((ERRNO != P_EWOULDBLOCK) && (ERRNO != P_ECONNABORTED))
|
|
{
|
|
/* Trouble! accept() returns a strange error.
|
|
* Previously in such a case we would just log/broadcast the error and return,
|
|
* causing this message to be triggered at a rate of XYZ per second (100% CPU).
|
|
* Now we close & re-start the listener.
|
|
* Of course the underlying cause of this issue should be investigated, as this
|
|
* is very much a workaround.
|
|
*/
|
|
if (listener->file)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on file $file: $socket_error",
|
|
log_data_socket_error(listener->fd),
|
|
log_data_string("file", listener->file));
|
|
} else {
|
|
unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: $socket_error",
|
|
log_data_socket_error(listener->fd),
|
|
log_data_string("listen_ip", listener->ip),
|
|
log_data_integer("listen_port", listener->port));
|
|
}
|
|
close_listener(listener);
|
|
start_listeners();
|
|
}
|
|
return;
|
|
}
|
|
|
|
ircstats.is_ac++;
|
|
|
|
set_sock_opts(cli_fd, NULL, listener->socket_type);
|
|
|
|
/* Allow connections to the control socket, even if maxclients is reached */
|
|
if (listener->options & LISTENER_CONTROL)
|
|
{
|
|
/* ... but not unlimited ;) */
|
|
if ((++OpenFiles >= maxclients+(CLIENTS_RESERVE/2)) || (cli_fd >= maxclients+(CLIENTS_RESERVE/2)))
|
|
{
|
|
ircstats.is_ref++;
|
|
if (last_allinuse < TStime() - 15)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
|
|
log_data_string("file", listener->file));
|
|
last_allinuse = TStime();
|
|
}
|
|
fd_close(cli_fd);
|
|
--OpenFiles;
|
|
return;
|
|
}
|
|
} else
|
|
{
|
|
if ((++OpenFiles >= maxclients) || (cli_fd >= maxclients))
|
|
{
|
|
ircstats.is_ref++;
|
|
if (last_allinuse < TStime() - 15)
|
|
{
|
|
if (listener->file)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on file $file: All connections in use",
|
|
log_data_string("file", listener->file));
|
|
} else {
|
|
unreal_log(ULOG_FATAL, "listen", "ACCEPT_ERROR_MAXCLIENTS", NULL, "Cannot accept incoming connection on IP \"$listen_ip\" port $listen_port: All connections in use",
|
|
log_data_string("listen_ip", listener->ip),
|
|
log_data_integer("listen_port", listener->port));
|
|
}
|
|
last_allinuse = TStime();
|
|
}
|
|
|
|
(void)send(cli_fd, "ERROR :All connections in use\r\n", 31, 0);
|
|
|
|
fd_close(cli_fd);
|
|
--OpenFiles;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* add_connection() may fail. we just don't care. */
|
|
add_connection(listener, cli_fd);
|
|
}
|
|
|
|
int unreal_listen_inet(ConfigItem_listen *listener)
|
|
{
|
|
const char *ip = listener->ip;
|
|
int port = listener->port;
|
|
|
|
if (BadPtr(ip))
|
|
ip = "*";
|
|
|
|
if (*ip == '*')
|
|
{
|
|
if (listener->socket_type == SOCKET_TYPE_IPV6)
|
|
ip = "::";
|
|
else
|
|
ip = "0.0.0.0";
|
|
}
|
|
|
|
/* At first, open a new socket */
|
|
if (listener->fd >= 0)
|
|
abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
|
|
|
|
if (port == 0)
|
|
abort(); /* Impossible as well, right? */
|
|
|
|
listener->fd = fd_socket(listener->socket_type == SOCKET_TYPE_IPV6 ? AF_INET6 : AF_INET, SOCK_STREAM, 0, "Listener socket");
|
|
if (listener->fd < 0)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
|
|
"Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
|
|
log_data_socket_error(-1),
|
|
log_data_string("listen_ip", ip),
|
|
log_data_integer("listen_port", port));
|
|
return -1;
|
|
}
|
|
|
|
if (++OpenFiles >= maxclients)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_ERROR_MAXCLIENTS", NULL,
|
|
"Could not listen on IP \"$listen_ip\" on port $listen_port: all connections in use",
|
|
log_data_string("listen_ip", ip),
|
|
log_data_integer("listen_port", port));
|
|
fd_close(listener->fd);
|
|
listener->fd = -1;
|
|
--OpenFiles;
|
|
return -1;
|
|
}
|
|
|
|
set_sock_opts(listener->fd, NULL, listener->socket_type);
|
|
|
|
if (!unreal_bind(listener->fd, ip, port, listener->socket_type))
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
|
|
"Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
|
|
log_data_socket_error(listener->fd),
|
|
log_data_string("listen_ip", ip),
|
|
log_data_integer("listen_port", port));
|
|
fd_close(listener->fd);
|
|
listener->fd = -1;
|
|
--OpenFiles;
|
|
return -1;
|
|
}
|
|
|
|
if (listen(listener->fd, LISTEN_SIZE) < 0)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_LISTEN_ERROR", NULL,
|
|
"Could not listen on IP \"$listen_ip\" on port $listen_port: $socket_error",
|
|
log_data_socket_error(listener->fd),
|
|
log_data_string("listen_ip", ip),
|
|
log_data_integer("listen_port", port));
|
|
fd_close(listener->fd);
|
|
listener->fd = -1;
|
|
--OpenFiles;
|
|
return -1;
|
|
}
|
|
|
|
#ifdef TCP_DEFER_ACCEPT
|
|
if (listener->options & LISTENER_DEFER_ACCEPT)
|
|
{
|
|
int yes = 1;
|
|
|
|
(void)setsockopt(listener->fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &yes, sizeof(int));
|
|
}
|
|
#endif
|
|
|
|
#ifdef SO_ACCEPTFILTER
|
|
if (listener->options & LISTENER_DEFER_ACCEPT)
|
|
{
|
|
struct accept_filter_arg afa;
|
|
|
|
memset(&afa, '\0', sizeof afa);
|
|
strlcpy(afa.af_name, "dataready", sizeof afa.af_name);
|
|
(void)setsockopt(listener->fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof afa);
|
|
}
|
|
#endif
|
|
|
|
fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int unreal_listen_unix(ConfigItem_listen *listener)
|
|
{
|
|
if (listener->socket_type != SOCKET_TYPE_UNIX)
|
|
abort(); /* "impossible" */
|
|
|
|
/* At first, open a new socket */
|
|
if (listener->fd >= 0)
|
|
abort(); /* Socket already exists but we are asked to create and listen on one. Bad! */
|
|
|
|
listener->fd = fd_socket(AF_UNIX, SOCK_STREAM, 0, "Listener socket (UNIX)");
|
|
if (listener->fd < 0)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_SOCKET_ERROR", NULL,
|
|
"Could not create UNIX domain socket for $file: $socket_error",
|
|
log_data_socket_error(-1),
|
|
log_data_string("file", listener->file));
|
|
return -1;
|
|
}
|
|
|
|
if (++OpenFiles >= maxclients)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_ERROR_MAXCLIENTS", NULL,
|
|
"Could not create UNIX domain socket for $file: all connections in use",
|
|
log_data_string("file", listener->file));
|
|
fd_close(listener->fd);
|
|
listener->fd = -1;
|
|
--OpenFiles;
|
|
return -1;
|
|
}
|
|
|
|
set_sock_opts(listener->fd, NULL, listener->socket_type);
|
|
|
|
if (!unreal_bind(listener->fd, listener->file, 0, SOCKET_TYPE_UNIX))
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_BIND_ERROR", NULL,
|
|
"Could not listen on UNIX domain socket $file: $socket_error",
|
|
log_data_socket_error(listener->fd),
|
|
log_data_string("file", listener->file));
|
|
fd_close(listener->fd);
|
|
listener->fd = -1;
|
|
--OpenFiles;
|
|
return -1;
|
|
}
|
|
|
|
if (listen(listener->fd, LISTEN_SIZE) < 0)
|
|
{
|
|
unreal_log(ULOG_FATAL, "listen", "LISTEN_LISTEN_ERROR", NULL,
|
|
"Could not listen on UNIX domain socket $file: $socket_error",
|
|
log_data_socket_error(listener->fd),
|
|
log_data_string("file", listener->file));
|
|
fd_close(listener->fd);
|
|
listener->fd = -1;
|
|
--OpenFiles;
|
|
return -1;
|
|
}
|
|
|
|
fd_setselect(listener->fd, FD_SELECT_READ, listener_accept, listener);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Create a listener port.
|
|
* @param listener The listen { } block configuration
|
|
* @returns 0 on success and <0 on error. Yeah, confusing.
|
|
*/
|
|
int unreal_listen(ConfigItem_listen *listener)
|
|
{
|
|
if ((listener->socket_type == SOCKET_TYPE_IPV4) || (listener->socket_type == SOCKET_TYPE_IPV6))
|
|
return unreal_listen_inet(listener);
|
|
return unreal_listen_unix(listener);
|
|
}
|
|
|
|
/** Activate a listen { } block */
|
|
int add_listener(ConfigItem_listen *listener)
|
|
{
|
|
if (unreal_listen(listener))
|
|
{
|
|
/* Error is already handled upstream */
|
|
listener->fd = -2;
|
|
}
|
|
|
|
if (listener->fd >= 0)
|
|
{
|
|
listener->options |= LISTENER_BOUND;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
listener->fd = -1;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/** Close the listener socket, but do not free it (yet).
|
|
* This will only close the socket so no new clients are accepted.
|
|
* It also marks the listener as no longer "bound".
|
|
* Once the last client exits the listener will actually be freed.
|
|
* @param listener The listen { } block.
|
|
*/
|
|
void close_listener(ConfigItem_listen *listener)
|
|
{
|
|
if (listener->fd >= 0)
|
|
{
|
|
unreal_log(ULOG_INFO, "listen", "LISTEN_REMOVED", NULL,
|
|
"UnrealIRCd is now no longer listening on $listen_ip:$listen_port",
|
|
log_data_string("listen_ip", listener->ip),
|
|
log_data_integer("listen_port", listener->port));
|
|
fd_close(listener->fd);
|
|
--OpenFiles;
|
|
}
|
|
|
|
listener->options &= ~LISTENER_BOUND;
|
|
listener->fd = -1;
|
|
/* We can already free the TLS context, since it is only
|
|
* used for new connections, which we no longer accept.
|
|
*/
|
|
if (listener->ssl_ctx)
|
|
{
|
|
SSL_CTX_free(listener->ssl_ctx);
|
|
listener->ssl_ctx = NULL;
|
|
}
|
|
}
|
|
|
|
/** Close all listeners that were pending to be closed. */
|
|
void close_unbound_listeners(void)
|
|
{
|
|
ConfigItem_listen *aconf, *aconf_next;
|
|
|
|
/* close all 'extra' listening ports we have */
|
|
for (aconf = conf_listen; aconf != NULL; aconf = aconf_next)
|
|
{
|
|
aconf_next = aconf->next;
|
|
if (aconf->flag.temporary)
|
|
close_listener(aconf);
|
|
}
|
|
}
|
|
|
|
int maxclients = 1024 - CLIENTS_RESERVE;
|
|
|
|
/** Check the maximum number of sockets (users) that we can handle - called on startup.
|
|
*/
|
|
void check_user_limit(void)
|
|
{
|
|
#ifdef RLIMIT_FD_MAX
|
|
struct rlimit limit;
|
|
long m;
|
|
|
|
if (!getrlimit(RLIMIT_FD_MAX, &limit))
|
|
{
|
|
if (limit.rlim_max < MAXCONNECTIONS)
|
|
m = limit.rlim_max;
|
|
else
|
|
m = MAXCONNECTIONS;
|
|
|
|
/* Adjust soft limit (if necessary, which is often the case) */
|
|
if (m != limit.rlim_cur)
|
|
{
|
|
limit.rlim_cur = limit.rlim_max = m;
|
|
if (setrlimit(RLIMIT_FD_MAX, &limit) == -1)
|
|
{
|
|
/* HACK: if it's mac os X then don't error... */
|
|
#ifndef OSXTIGER
|
|
fprintf(stderr, "error setting maximum number of open files to %ld\n",
|
|
(long)limit.rlim_cur);
|
|
exit(-1);
|
|
#endif // OSXTIGER
|
|
}
|
|
}
|
|
/* This can only happen if it is due to resource limits (./Config already rejects <100) */
|
|
if (m < 100)
|
|
{
|
|
fprintf(stderr, "\nERROR: Your OS has a limit placed on this account.\n"
|
|
"This machine only allows UnrealIRCd to handle a maximum of %ld open connections/files, which is VERY LOW.\n"
|
|
"Please check with your system administrator to bump this limit.\n"
|
|
"The recommended ulimit -n setting is at least 1024 and "
|
|
"preferably 4096.\n"
|
|
"Note that this error is often seen on small web shells that are not meant for running IRC servers.\n",
|
|
m);
|
|
exit(-1);
|
|
}
|
|
maxclients = m - CLIENTS_RESERVE;
|
|
}
|
|
#endif // RLIMIT_FD_MAX
|
|
|
|
#ifndef _WIN32
|
|
#ifdef BACKEND_SELECT
|
|
if (MAXCONNECTIONS > FD_SETSIZE)
|
|
{
|
|
fprintf(stderr, "MAXCONNECTIONS (%d) is higher than FD_SETSIZE (%d)\n", MAXCONNECTIONS, FD_SETSIZE);
|
|
fprintf(stderr, "You should not see this error on Linux or FreeBSD\n");
|
|
fprintf(stderr, "You might need to recompile the IRCd and answer a lower value to the MAXCONNECTIONS question in ./Config\n");
|
|
exit(-1);
|
|
}
|
|
#endif
|
|
#endif
|
|
#ifdef _WIN32
|
|
maxclients = MAXCONNECTIONS - CLIENTS_RESERVE;
|
|
#endif
|
|
}
|
|
|
|
/** Initialize some systems - called on startup */
|
|
void init_sys(void)
|
|
{
|
|
#ifndef _WIN32
|
|
/* Create new session / set process group */
|
|
(void)setsid();
|
|
#endif
|
|
|
|
init_resolver(1);
|
|
return;
|
|
}
|
|
|
|
/** Replace a file descriptor (*NIX only).
|
|
* See close_std_descriptors() as for why.
|
|
* @param oldfd: the old FD to close and re-use
|
|
* @param name: descriptive string of the old fd, eg: "stdin".
|
|
* @param mode: an open() mode, such as O_WRONLY.
|
|
*/
|
|
void replacefd(int oldfd, char *name, int mode)
|
|
{
|
|
#ifndef _WIN32
|
|
int newfd = open("/dev/null", mode);
|
|
if (newfd < 0)
|
|
{
|
|
fprintf(stderr, "Warning: could not open /dev/null\n");
|
|
return;
|
|
}
|
|
if (oldfd < 0)
|
|
{
|
|
fprintf(stderr, "Warning: could not replace %s (invalid fd)\n", name);
|
|
return;
|
|
}
|
|
if (dup2(newfd, oldfd) < 0)
|
|
{
|
|
fprintf(stderr, "Warning: could not replace %s (dup2 error)\n", name);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Mass close standard file descriptors (stdin, stdout, stderr).
|
|
* We used to really just close them here (or in init_sys() actually),
|
|
* making the fd's available for other purposes such as internet sockets.
|
|
* For safety we now dup2() them to /dev/null. This in case someone
|
|
* accidentally does a fprintf(stderr,..) somewhere in the code or some
|
|
* library outputs error messages to stderr (such as libc with heap
|
|
* errors). We don't want any IRC client to receive such a thing!
|
|
*/
|
|
void close_std_descriptors(void)
|
|
{
|
|
#if !defined(_WIN32) && !defined(NOCLOSEFD)
|
|
replacefd(fileno(stdin), "stdin", O_RDONLY);
|
|
replacefd(fileno(stdout), "stdout", O_WRONLY);
|
|
replacefd(fileno(stderr), "stderr", O_WRONLY);
|
|
#endif
|
|
}
|
|
|
|
/** Do an ident lookup if necessary.
|
|
* @param client The incoming client
|
|
*/
|
|
void consider_ident_lookup(Client *client)
|
|
{
|
|
char buf[BUFSIZE];
|
|
|
|
/* If ident checking is disabled or it's an outgoing connect, then no ident check */
|
|
if ((IDENT_CHECK == 0) || (client->server && IsHandshake(client)) || IsUnixSocket(client))
|
|
{
|
|
ClearIdentLookupSent(client);
|
|
ClearIdentLookup(client);
|
|
return;
|
|
}
|
|
RunHook(HOOKTYPE_IDENT_LOOKUP, client);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/** Called when TCP/IP connection is established (outgoing server connect) */
|
|
void completed_connection(int fd, int revents, void *data)
|
|
{
|
|
Client *client = data;
|
|
ConfigItem_link *aconf = client->server ? client->server->conf : NULL;
|
|
|
|
if (IsHandshake(client))
|
|
{
|
|
/* Due to delayed unreal_tls_connect call */
|
|
start_server_handshake(client);
|
|
fd_setselect(fd, FD_SELECT_READ, read_packet, client);
|
|
return;
|
|
}
|
|
|
|
SetHandshake(client);
|
|
|
|
if (!aconf)
|
|
{
|
|
unreal_log(ULOG_ERROR, "link", "BUG_LOST_CONFIGURATION_ON_CONNECT", client,
|
|
"Lost configuration while connecting to $client.details");
|
|
return;
|
|
}
|
|
|
|
if (!client->local->ssl && !(aconf->outgoing.options & CONNECT_INSECURE))
|
|
{
|
|
sendto_one(client, NULL, "STARTTLS");
|
|
} else
|
|
{
|
|
start_server_handshake(client);
|
|
}
|
|
|
|
if (!IsDeadSocket(client))
|
|
consider_ident_lookup(client);
|
|
|
|
fd_setselect(fd, FD_SELECT_READ, read_packet, client);
|
|
}
|
|
|
|
/** Close the physical connection.
|
|
* @param client The client connection to close (LOCAL!)
|
|
*/
|
|
void close_connection(Client *client)
|
|
{
|
|
RunHook(HOOKTYPE_CLOSE_CONNECTION, client);
|
|
/* This function must make MyConnect(client) == FALSE,
|
|
* and set client->direction == NULL.
|
|
*/
|
|
if (IsServer(client))
|
|
{
|
|
ircstats.is_sv++;
|
|
ircstats.is_sti += TStime() - client->local->creationtime;
|
|
}
|
|
else if (IsUser(client))
|
|
{
|
|
ircstats.is_cl++;
|
|
ircstats.is_cti += TStime() - client->local->creationtime;
|
|
}
|
|
else
|
|
ircstats.is_ni++;
|
|
|
|
/*
|
|
* remove outstanding DNS queries.
|
|
*/
|
|
unrealdns_delreq_bycptr(client);
|
|
|
|
if (client->local->authfd >= 0)
|
|
{
|
|
fd_close(client->local->authfd);
|
|
client->local->authfd = -1;
|
|
--OpenFiles;
|
|
}
|
|
|
|
if (client->local->fd >= 0)
|
|
{
|
|
send_queued(client);
|
|
if (IsTLS(client) && client->local->ssl) {
|
|
SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
|
|
SSL_smart_shutdown(client->local->ssl);
|
|
SSL_free(client->local->ssl);
|
|
client->local->ssl = NULL;
|
|
}
|
|
fd_close(client->local->fd);
|
|
client->local->fd = -2;
|
|
--OpenFiles;
|
|
DBufClear(&client->local->sendQ);
|
|
DBufClear(&client->local->recvQ);
|
|
|
|
}
|
|
|
|
client->direction = NULL;
|
|
}
|
|
|
|
/** Set IPv6 socket options, if possible. */
|
|
void set_ipv6_opts(int fd)
|
|
{
|
|
#if defined(IPV6_V6ONLY)
|
|
int opt = 1;
|
|
(void)setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *)&opt, sizeof(opt));
|
|
#endif
|
|
}
|
|
|
|
/** This sets the *OS* socket buffers.
|
|
* This shouldn't be needed anymore, but I've left the function here.
|
|
*/
|
|
void set_socket_buffers(int fd, int rcvbuf, int sndbuf)
|
|
{
|
|
int opt;
|
|
|
|
opt = rcvbuf;
|
|
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&opt, sizeof(opt));
|
|
|
|
opt = sndbuf;
|
|
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&opt, sizeof(opt));
|
|
}
|
|
|
|
/** Set the appropriate socket options */
|
|
void set_sock_opts(int fd, Client *client, SocketType socket_type)
|
|
{
|
|
int opt;
|
|
|
|
if (socket_type == SOCKET_TYPE_IPV6)
|
|
set_ipv6_opts(fd);
|
|
|
|
if ((socket_type == SOCKET_TYPE_IPV4) || (socket_type == SOCKET_TYPE_IPV6))
|
|
{
|
|
#ifdef SO_REUSEADDR
|
|
opt = 1;
|
|
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)) < 0)
|
|
{
|
|
unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
|
|
"Could not setsockopt(SO_REUSEADDR): $socket_error",
|
|
log_data_socket_error(-1));
|
|
}
|
|
#endif
|
|
|
|
#if defined(SO_USELOOPBACK) && !defined(_WIN32)
|
|
opt = 1;
|
|
if (setsockopt(fd, SOL_SOCKET, SO_USELOOPBACK, (void *)&opt, sizeof(opt)) < 0)
|
|
{
|
|
unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
|
|
"Could not setsockopt(SO_USELOOPBACK): $socket_error",
|
|
log_data_socket_error(-1));
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
/* The following code applies to all socket types: IPv4, IPv6, UNIX domain sockets */
|
|
|
|
/* Set to non blocking: */
|
|
#if !defined(_WIN32)
|
|
if ((opt = fcntl(fd, F_GETFL, 0)) == -1)
|
|
{
|
|
if (client)
|
|
{
|
|
unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
|
|
"Could not get socket options (F_GETFL): $socket_error",
|
|
log_data_socket_error(-1));
|
|
}
|
|
}
|
|
else if (fcntl(fd, F_SETFL, opt | O_NONBLOCK) == -1)
|
|
{
|
|
if (client)
|
|
{
|
|
unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
|
|
"Could not get socket options (F_SETFL): $socket_error",
|
|
log_data_socket_error(-1));
|
|
}
|
|
}
|
|
#else
|
|
opt = 1;
|
|
if (ioctlsocket(fd, FIONBIO, &opt) < 0)
|
|
{
|
|
if (client)
|
|
{
|
|
unreal_log(ULOG_WARNING, "socket", "SOCKET_ERROR_SETSOCKOPTS", client,
|
|
"Could not ioctlsocket FIONBIO: $socket_error",
|
|
log_data_socket_error(-1));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Returns 1 if using a loopback IP (127.0.0.1) or
|
|
* using a local IP number on the same machine (effectively the same;
|
|
* no network traffic travels outside this machine).
|
|
* @param ip The IP address to check
|
|
* @returns 1 if loopback, 0 if not.
|
|
*/
|
|
int is_loopback_ip(char *ip)
|
|
{
|
|
ConfigItem_listen *e;
|
|
|
|
if (!strcmp(ip, "127.0.0.1") || !strcmp(ip, "0:0:0:0:0:0:0:1") || !strcmp(ip, "0:0:0:0:0:ffff:127.0.0.1"))
|
|
return 1;
|
|
|
|
for (e = conf_listen; e; e = e->next)
|
|
{
|
|
if ((e->options & LISTENER_BOUND) && e->ip && !strcmp(ip, e->ip))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Retrieve the remote IP address and port of a socket.
|
|
* @param client Client to check
|
|
* @param fd File descriptor
|
|
* @param port Remote port (will be written)
|
|
* @returns The IP address
|
|
*/
|
|
const char *getpeerip(Client *client, int fd, int *port)
|
|
{
|
|
static char ret[HOSTLEN+1];
|
|
|
|
if (IsIPV6(client))
|
|
{
|
|
struct sockaddr_in6 addr;
|
|
int len = sizeof(addr);
|
|
|
|
if (getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
|
|
return NULL;
|
|
*port = ntohs(addr.sin6_port);
|
|
return inetntop(AF_INET6, &addr.sin6_addr.s6_addr, ret, sizeof(ret));
|
|
} else
|
|
{
|
|
struct sockaddr_in addr;
|
|
int len = sizeof(addr);
|
|
|
|
if (getpeername(fd, (struct sockaddr *)&addr, &len) < 0)
|
|
return NULL;
|
|
*port = ntohs(addr.sin_port);
|
|
return inetntop(AF_INET, &addr.sin_addr.s_addr, ret, sizeof(ret));
|
|
}
|
|
}
|
|
|
|
/** This checks set::max-unknown-connections-per-ip,
|
|
* which is an important safety feature.
|
|
*/
|
|
static int check_too_many_unknown_connections(Client *client)
|
|
{
|
|
int cnt = 1;
|
|
Client *c;
|
|
|
|
if (!find_tkl_exception(TKL_CONNECT_FLOOD, client))
|
|
{
|
|
list_for_each_entry(c, &unknown_list, lclient_node)
|
|
{
|
|
if (!strcmp(client->ip,GetIP(c)))
|
|
{
|
|
cnt++;
|
|
if (cnt > iConf.max_unknown_connections_per_ip)
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Process the incoming connection which has just been accepted.
|
|
* This creates a client structure for the user.
|
|
* The sockhost field is initialized with the ip# of the host.
|
|
* The client is added to the linked list of clients but isnt added to any
|
|
* hash tables yuet since it doesnt have a name.
|
|
* @param listener The listen { } block on which the client was accepted.
|
|
* @param fd The file descriptor of the client
|
|
* @returns The new client, or NULL in case of trouble.
|
|
* @note When NULL is returned, the client at socket 'fd' will be
|
|
* closed by this function and OpenFiles is adjusted appropriately.
|
|
*/
|
|
Client *add_connection(ConfigItem_listen *listener, int fd)
|
|
{
|
|
Client *client;
|
|
const char *ip;
|
|
int port = 0;
|
|
|
|
client = make_client(NULL, &me);
|
|
client->local->socket_type = listener->socket_type;
|
|
|
|
if (listener->socket_type == SOCKET_TYPE_UNIX)
|
|
ip = "127.0.0.1";
|
|
else
|
|
ip = getpeerip(client, fd, &port);
|
|
|
|
if (!ip)
|
|
{
|
|
/* On Linux 2.4 and FreeBSD the socket may just have been disconnected
|
|
* so it's not a serious error and can happen quite frequently -- Syzop
|
|
*/
|
|
if (ERRNO != P_ENOTCONN)
|
|
{
|
|
unreal_log(ULOG_ERROR, "listen", "ACCEPT_ERROR", NULL,
|
|
"Failed to accept new client: unable to get IP address: $socket_error",
|
|
log_data_socket_error(fd),
|
|
log_data_string("listen_ip", listener->ip),
|
|
log_data_integer("listen_port", listener->port));
|
|
}
|
|
refuse_client:
|
|
ircstats.is_ref++;
|
|
client->local->fd = -2;
|
|
free_client(client);
|
|
fd_close(fd);
|
|
--OpenFiles;
|
|
return NULL;
|
|
}
|
|
|
|
/* Fill in sockhost & ip ASAP */
|
|
set_sockhost(client, ip);
|
|
safe_strdup(client->ip, ip);
|
|
client->local->port = port;
|
|
client->local->fd = fd;
|
|
|
|
/* Tag loopback connections */
|
|
if (is_loopback_ip(client->ip))
|
|
{
|
|
ircstats.is_loc++;
|
|
SetLocalhost(client);
|
|
}
|
|
|
|
if (!(listener->options & LISTENER_CONTROL))
|
|
{
|
|
/* Check set::max-unknown-connections-per-ip */
|
|
if (check_too_many_unknown_connections(client))
|
|
{
|
|
ircsnprintf(zlinebuf, sizeof(zlinebuf),
|
|
"ERROR :Closing Link: [%s] (Too many unknown connections from your IP)\r\n",
|
|
client->ip);
|
|
(void)send(fd, zlinebuf, strlen(zlinebuf), 0);
|
|
goto refuse_client;
|
|
}
|
|
|
|
/* Check (G)Z-Lines and set::anti-flood::connect-flood */
|
|
if (check_banned(client, NO_EXIT_CLIENT))
|
|
goto refuse_client;
|
|
}
|
|
|
|
client->local->listener = listener;
|
|
if (client->local->listener != NULL)
|
|
client->local->listener->clients++;
|
|
add_client_to_list(client);
|
|
|
|
if (!(listener->options & LISTENER_CONTROL))
|
|
{
|
|
/* IRC: unknown connection */
|
|
irccounts.unknown++;
|
|
client->status = CLIENT_STATUS_UNKNOWN;
|
|
list_add(&client->lclient_node, &unknown_list);
|
|
} else {
|
|
client->status = CLIENT_STATUS_CONTROL;
|
|
list_add(&client->lclient_node, &control_list);
|
|
}
|
|
|
|
if ((listener->options & LISTENER_TLS) && ctx_server)
|
|
{
|
|
SSL_CTX *ctx = listener->ssl_ctx ? listener->ssl_ctx : ctx_server;
|
|
|
|
if (ctx)
|
|
{
|
|
SetTLSAcceptHandshake(client);
|
|
if ((client->local->ssl = SSL_new(ctx)) == NULL)
|
|
{
|
|
goto refuse_client;
|
|
}
|
|
SetTLS(client);
|
|
SSL_set_fd(client->local->ssl, fd);
|
|
SSL_set_nonblocking(client->local->ssl);
|
|
SSL_set_ex_data(client->local->ssl, tls_client_index, client);
|
|
if (!unreal_tls_accept(client, fd))
|
|
{
|
|
SSL_set_shutdown(client->local->ssl, SSL_RECEIVED_SHUTDOWN);
|
|
SSL_smart_shutdown(client->local->ssl);
|
|
SSL_free(client->local->ssl);
|
|
goto refuse_client;
|
|
}
|
|
}
|
|
} else
|
|
if (listener->options & LISTENER_CONTROL)
|
|
start_of_control_client_handshake(client);
|
|
else
|
|
start_of_normal_client_handshake(client);
|
|
return client;
|
|
}
|
|
|
|
/** Start of normal client handshake - DNS and ident lookups, etc.
|
|
* @param client The client
|
|
* @note This is called directly after accept() -> add_connection() for plaintext.
|
|
* For TLS connections this is called after the TLS handshake is completed.
|
|
*/
|
|
void start_of_normal_client_handshake(Client *client)
|
|
{
|
|
struct hostent *he;
|
|
|
|
client->status = CLIENT_STATUS_UNKNOWN; /* reset, to be sure (TLS handshake has ended) */
|
|
|
|
RunHook(HOOKTYPE_HANDSHAKE, client);
|
|
|
|
if (!DONT_RESOLVE && !IsUnixSocket(client))
|
|
{
|
|
if (should_show_connect_info(client))
|
|
sendto_one(client, NULL, ":%s %s", me.name, REPORT_DO_DNS);
|
|
he = unrealdns_doclient(client);
|
|
|
|
if (client->local->hostp)
|
|
goto doauth; /* Race condition detected, DNS has been done, continue with auth */
|
|
|
|
if (!he)
|
|
{
|
|
/* Resolving in progress */
|
|
SetDNSLookup(client);
|
|
} else {
|
|
/* Host was in our cache */
|
|
client->local->hostp = he;
|
|
if (should_show_connect_info(client))
|
|
sendto_one(client, NULL, ":%s %s", me.name, REPORT_FIN_DNSC);
|
|
}
|
|
}
|
|
|
|
doauth:
|
|
consider_ident_lookup(client);
|
|
fd_setselect(client->local->fd, FD_SELECT_READ, read_packet, client);
|
|
}
|
|
|
|
/** Called when DNS lookup has been completed and we can proceed with the client handshake.
|
|
* @param client The client
|
|
* @param he The resolved or unresolved host
|
|
*/
|
|
void proceed_normal_client_handshake(Client *client, struct hostent *he)
|
|
{
|
|
ClearDNSLookup(client);
|
|
client->local->hostp = he;
|
|
if (should_show_connect_info(client))
|
|
{
|
|
sendto_one(client, NULL, ":%s %s",
|
|
me.name,
|
|
client->local->hostp ? REPORT_FIN_DNS : REPORT_FAIL_DNS);
|
|
}
|
|
}
|
|
|
|
/** Read a packet from a client.
|
|
* @param fd File descriptor
|
|
* @param revents Read events (ignored)
|
|
* @param data Associated data (the client)
|
|
*/
|
|
void read_packet(int fd, int revents, void *data)
|
|
{
|
|
Client *client = data;
|
|
int length = 0;
|
|
time_t now = TStime();
|
|
Hook *h;
|
|
int processdata;
|
|
|
|
/* Don't read from dead sockets */
|
|
if (IsDeadSocket(client))
|
|
{
|
|
fd_setselect(fd, FD_SELECT_READ, NULL, client);
|
|
return;
|
|
}
|
|
|
|
SET_ERRNO(0);
|
|
|
|
fd_setselect(fd, FD_SELECT_READ, read_packet, client);
|
|
/* Restore handling of writes towards send_queued_cb(), since
|
|
* it may be overwritten in an earlier call to read_packet(),
|
|
* to handle (TLS) writes by read_packet(), see below under
|
|
* SSL_ERROR_WANT_WRITE.
|
|
*/
|
|
fd_setselect(fd, FD_SELECT_WRITE, send_queued_cb, client);
|
|
|
|
while (1)
|
|
{
|
|
if (IsTLS(client) && client->local->ssl != NULL)
|
|
{
|
|
length = SSL_read(client->local->ssl, readbuf, sizeof(readbuf));
|
|
|
|
if (length < 0)
|
|
{
|
|
int err = SSL_get_error(client->local->ssl, length);
|
|
|
|
switch (err)
|
|
{
|
|
case SSL_ERROR_WANT_WRITE:
|
|
fd_setselect(fd, FD_SELECT_READ, NULL, client);
|
|
fd_setselect(fd, FD_SELECT_WRITE, read_packet, client);
|
|
length = -1;
|
|
SET_ERRNO(P_EWOULDBLOCK);
|
|
break;
|
|
case SSL_ERROR_WANT_READ:
|
|
fd_setselect(fd, FD_SELECT_READ, read_packet, client);
|
|
length = -1;
|
|
SET_ERRNO(P_EWOULDBLOCK);
|
|
break;
|
|
case SSL_ERROR_SYSCALL:
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
if (ERRNO == P_EAGAIN)
|
|
break;
|
|
default:
|
|
/*length = 0;
|
|
SET_ERRNO(0);
|
|
^^ why this? we should error. -- todo: is errno correct?
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
length = recv(client->local->fd, readbuf, sizeof(readbuf), 0);
|
|
|
|
if (length <= 0)
|
|
{
|
|
if (length < 0 && ((ERRNO == P_EWOULDBLOCK) || (ERRNO == P_EAGAIN) || (ERRNO == P_EINTR)))
|
|
return;
|
|
|
|
if (IsServer(client) || client->server) /* server or outgoing connection */
|
|
lost_server_link(client, NULL);
|
|
|
|
exit_client(client, NULL, ERRNO ? "Read error" : "Connection closed");
|
|
return;
|
|
}
|
|
|
|
client->local->last_msg_received = now;
|
|
if (client->local->last_msg_received > client->local->fake_lag)
|
|
client->local->fake_lag = client->local->last_msg_received;
|
|
/* FIXME: Is this correct? I have my doubts. */
|
|
ClearPingSent(client);
|
|
|
|
ClearPingWarning(client);
|
|
|
|
processdata = 1;
|
|
for (h = Hooks[HOOKTYPE_RAWPACKET_IN]; h; h = h->next)
|
|
{
|
|
processdata = (*(h->func.intfunc))(client, readbuf, &length);
|
|
if (processdata == 0)
|
|
break; /* if hook tells to ignore the data, then break now */
|
|
if (processdata < 0)
|
|
return; /* if hook tells client is dead, return now */
|
|
}
|
|
|
|
if (processdata && !process_packet(client, readbuf, length, 0))
|
|
return;
|
|
|
|
/* bail on short read! */
|
|
if (length < sizeof(readbuf))
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** Process input from clients that may have been deliberately delayed due to fake lag */
|
|
void process_clients(void)
|
|
{
|
|
Client *client;
|
|
|
|
/* Problem:
|
|
* When processing a client, that current client may exit due to eg QUIT.
|
|
* Similarly, current->next may be killed due to /KILL.
|
|
* When a client is killed, in the past we were not allowed to touch it anymore
|
|
* so that was a bit problematic. Now we can touch current->next, but it may
|
|
* have been removed from the lclient_list or unknown_list.
|
|
* In other words, current->next->next may be NULL even though there are more
|
|
* clients on the list.
|
|
* This is why the whole thing is wrapped in an additional do { } while() loop
|
|
* to make sure we re-run the list if we ended prematurely.
|
|
* We could use some kind of 'tagging' to mark already processed clients.
|
|
* However, parse_client_queued() already takes care not to read (fake) lagged
|
|
* clients, and we don't actually read/recv anything in the meantime, so clients
|
|
* in the beginning of the list won't benefit, they won't get higher prio.
|
|
* Another alternative is not to run the loop again, but that WOULD be
|
|
* unfair to clients later in the list which wouldn't be processed then
|
|
* under a heavy (kill) load scenario.
|
|
* I think the chosen solution is best, though it remains silly. -- Syzop
|
|
*/
|
|
|
|
do {
|
|
list_for_each_entry(client, &lclient_list, lclient_node)
|
|
{
|
|
if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
|
|
{
|
|
parse_client_queued(client);
|
|
if (IsDead(client))
|
|
break;
|
|
}
|
|
}
|
|
} while(&client->lclient_node != &lclient_list);
|
|
|
|
do {
|
|
list_for_each_entry(client, &unknown_list, lclient_node)
|
|
{
|
|
if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
|
|
{
|
|
parse_client_queued(client);
|
|
if (IsDead(client) || (client->status > CLIENT_STATUS_UNKNOWN))
|
|
break;
|
|
}
|
|
}
|
|
} while(&client->lclient_node != &unknown_list);
|
|
|
|
do {
|
|
list_for_each_entry(client, &control_list, lclient_node)
|
|
{
|
|
if ((client->local->fd >= 0) && DBufLength(&client->local->recvQ) && !IsDead(client))
|
|
{
|
|
parse_client_queued(client);
|
|
if (IsDead(client))
|
|
break;
|
|
}
|
|
}
|
|
} while(&client->lclient_node != &control_list);
|
|
|
|
|
|
}
|
|
|
|
/** Check if 'ip' is a valid IP address, and if so what type.
|
|
* @param ip The IP address
|
|
* @retval 4 Valid IPv4 address
|
|
* @retval 6 Valid IPv6 address
|
|
* @retval 0 Invalid IP address (eg: a hostname)
|
|
*/
|
|
int is_valid_ip(const char *ip)
|
|
{
|
|
char scratch[64];
|
|
|
|
if (BadPtr(ip))
|
|
return 0;
|
|
|
|
if (inet_pton(AF_INET, ip, scratch) == 1)
|
|
return 4; /* IPv4 */
|
|
|
|
if (inet_pton(AF_INET6, ip, scratch) == 1)
|
|
return 6; /* IPv6 */
|
|
|
|
return 0; /* not an IP address */
|
|
}
|
|
|
|
/** Checks if the system is IPv6 capable.
|
|
* IPv6 is always available at compile time (libs, headers), but the OS may
|
|
* not have IPv6 enabled (or ipv6 kernel module not loaded). So we better check..
|
|
*/
|
|
int ipv6_capable(void)
|
|
{
|
|
int s = socket(AF_INET6, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
return 0; /* NO ipv6 */
|
|
|
|
CLOSE_SOCK(s);
|
|
return 1; /* YES */
|
|
}
|
|
|
|
/** Attempt to deliver data to a client.
|
|
* This function is only called from send_queued() and will deal
|
|
* with sending to the TLS or plaintext connection.
|
|
* @param cptr The client
|
|
* @param str The string to send
|
|
* @param len The length of the string
|
|
* @param want_read In case of TLS it may happen that SSL_write()
|
|
* needs to READ data. If this happens then this
|
|
* function will set *want_read to 1.
|
|
* The upper layer should then call us again when
|
|
* there is data ready to be READ.
|
|
* @retval <0 Some fatal error occurred, (but not EWOULDBLOCK).
|
|
* This return is a request to close the socket and
|
|
* clean up the link.
|
|
* @retval >=0 No real error occurred, returns the number of
|
|
* bytes actually transferred. EWOULDBLOCK and other
|
|
* possibly similar conditions should be mapped to
|
|
* zero return. Upper level routine will have to
|
|
* decide what to do with those unwritten bytes...
|
|
*/
|
|
int deliver_it(Client *client, char *str, int len, int *want_read)
|
|
{
|
|
int retval;
|
|
|
|
*want_read = 0;
|
|
|
|
if (IsDeadSocket(client) ||
|
|
(!IsServer(client) && !IsUser(client) && !IsHandshake(client) &&
|
|
!IsTLSHandshake(client) && !IsUnknown(client)))
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (IsTLS(client) && client->local->ssl != NULL)
|
|
{
|
|
retval = SSL_write(client->local->ssl, str, len);
|
|
|
|
if (retval < 0)
|
|
{
|
|
switch (SSL_get_error(client->local->ssl, retval))
|
|
{
|
|
case SSL_ERROR_WANT_READ:
|
|
SET_ERRNO(P_EWOULDBLOCK);
|
|
*want_read = 1;
|
|
return 0;
|
|
case SSL_ERROR_WANT_WRITE:
|
|
SET_ERRNO(P_EWOULDBLOCK);
|
|
break;
|
|
case SSL_ERROR_SYSCALL:
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
if (ERRNO == P_EAGAIN)
|
|
break;
|
|
/* FALLTHROUGH */
|
|
default:
|
|
return -1; /* hm.. why was this 0?? we have an error! */
|
|
}
|
|
}
|
|
}
|
|
else
|
|
retval = send(client->local->fd, str, len, 0);
|
|
/*
|
|
** Convert WOULDBLOCK to a return of "0 bytes moved". This
|
|
** should occur only if socket was non-blocking. Note, that
|
|
** all is Ok, if the 'write' just returns '0' instead of an
|
|
** error and errno=EWOULDBLOCK.
|
|
**
|
|
** ...now, would this work on VMS too? --msa
|
|
*/
|
|
# ifndef _WIN32
|
|
if (retval < 0 && (errno == EWOULDBLOCK || errno == EAGAIN ||
|
|
errno == ENOBUFS))
|
|
# else
|
|
if (retval < 0 && (WSAGetLastError() == WSAEWOULDBLOCK ||
|
|
WSAGetLastError() == WSAENOBUFS))
|
|
# endif
|
|
retval = 0;
|
|
|
|
if (retval > 0)
|
|
{
|
|
client->local->traffic.bytes_sent += retval;
|
|
me.local->traffic.bytes_sent += retval;
|
|
}
|
|
|
|
return (retval);
|
|
}
|
|
|
|
/** Initiate an outgoing connection, the actual connect() call. */
|
|
int unreal_connect(int fd, const char *ip, int port, int ipv6)
|
|
{
|
|
int n;
|
|
|
|
if (ipv6)
|
|
{
|
|
struct sockaddr_in6 server;
|
|
memset(&server, 0, sizeof(server));
|
|
server.sin6_family = AF_INET6;
|
|
inet_pton(AF_INET6, ip, &server.sin6_addr);
|
|
server.sin6_port = htons(port);
|
|
n = connect(fd, (struct sockaddr *)&server, sizeof(server));
|
|
} else {
|
|
struct sockaddr_in server;
|
|
memset(&server, 0, sizeof(server));
|
|
server.sin_family = AF_INET;
|
|
inet_pton(AF_INET, ip, &server.sin_addr);
|
|
server.sin_port = htons(port);
|
|
n = connect(fd, (struct sockaddr *)&server, sizeof(server));
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
if (n < 0 && (errno != EINPROGRESS))
|
|
#else
|
|
if (n < 0 && (WSAGetLastError() != WSAEINPROGRESS) && (WSAGetLastError() != WSAEWOULDBLOCK))
|
|
#endif
|
|
{
|
|
return 0; /* FATAL ERROR */
|
|
}
|
|
|
|
return 1; /* SUCCESS (probably still in progress) */
|
|
}
|
|
|
|
/** Bind to an IP/port (port may be 0 for auto).
|
|
* @returns 0 on failure, other on success.
|
|
*/
|
|
int unreal_bind(int fd, const char *ip, int port, SocketType socket_type)
|
|
{
|
|
if (socket_type == SOCKET_TYPE_IPV4)
|
|
{
|
|
struct sockaddr_in server;
|
|
memset(&server, 0, sizeof(server));
|
|
server.sin_family = AF_INET;
|
|
server.sin_port = htons(port);
|
|
if (inet_pton(AF_INET, ip, &server.sin_addr.s_addr) != 1)
|
|
return 0;
|
|
return !bind(fd, (struct sockaddr *)&server, sizeof(server));
|
|
}
|
|
else if (socket_type == SOCKET_TYPE_IPV6)
|
|
{
|
|
struct sockaddr_in6 server;
|
|
memset(&server, 0, sizeof(server));
|
|
server.sin6_family = AF_INET6;
|
|
server.sin6_port = htons(port);
|
|
if (inet_pton(AF_INET6, ip, &server.sin6_addr.s6_addr) != 1)
|
|
return 0;
|
|
return !bind(fd, (struct sockaddr *)&server, sizeof(server));
|
|
} else
|
|
{
|
|
struct sockaddr_un server;
|
|
mode_t saved_umask;
|
|
int ret;
|
|
|
|
unlink(ip); /* (ignore errors) */
|
|
|
|
memset(&server, 0, sizeof(server));
|
|
server.sun_family = AF_UNIX;
|
|
strlcpy(server.sun_path, ip, sizeof(server.sun_path));
|
|
saved_umask = umask(077); // TODO: make this configurable
|
|
ret = !bind(fd, (struct sockaddr *)&server, sizeof(server));
|
|
umask(saved_umask);
|
|
|
|
return ret;
|
|
}
|
|
}
|