WIP: add a stub IRC daemon named `kike'
As it is going to share a great lot of functionality with ZyklonB, I have decided to extract the common parts into `common.c' and make the two subprojects include this file. The Single Compile Unit concept has proven valuable (sub-second compile times, dead code warnings, almost no need for function declarations, whole-program optimizations), and the sources aren't that long so far anyway. I am probably going to add CMake support later but so far it's not a priority. This project is meant to be portable and freely relocatable (ie. no hardcoded paths if I can avoid it).
This commit is contained in:
		
							parent
							
								
									165a19da21
								
							
						
					
					
						commit
						705c0e943e
					
				
							
								
								
									
										11
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,17 +1,20 @@ | ||||
| SHELL = /bin/sh | ||||
| CC = clang | ||||
| CFLAGS = -ggdb -Wall -Wextra -std=c99 | ||||
| LDFLAGS = `pkg-config --libs libssl` | ||||
| # -lpthread is only there for debugging (gdb & errno)
 | ||||
| LDFLAGS = `pkg-config --libs libssl` -lpthread | ||||
| 
 | ||||
| .PHONY: all clean | ||||
| 
 | ||||
| targets = zyklonb | ||||
| targets = zyklonb kike | ||||
| 
 | ||||
| all: $(targets) | ||||
| 
 | ||||
| clean: | ||||
| 	rm -f $(targets) | ||||
| 
 | ||||
| zyklonb: src/zyklonb.c src/siphash.c | ||||
| 	$(CC) $^ -o $@ $(CFLAGS) $(LDFLAGS) | ||||
| zyklonb: src/zyklonb.c src/common.c src/siphash.c | ||||
| 	$(CC) src/zyklonb.c src/siphash.c -o $@ $(CFLAGS) $(LDFLAGS) | ||||
| kike: src/kike.c src/common.c src/siphash.c | ||||
| 	$(CC) src/kike.c src/siphash.c -o $@ $(CFLAGS) $(LDFLAGS) | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										11
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								README
									
									
									
									
									
								
							| @ -10,6 +10,17 @@ While originally intended to be a simple C99 rewrite of the original bot, which | ||||
| was written in the GNU dialect of AWK, it fairly quickly became a playground | ||||
| where I added everything that seemed nice. | ||||
| 
 | ||||
| kike | ||||
| ---- | ||||
| Also included is a simple IRC daemon that mostly follows the RFC's but is | ||||
| limited to single-server networks, due to the protocol being incredibly ugly | ||||
| and tricky to implement correctly.  Even so, it took me a ridiculous amount of | ||||
| time to write.  (But it was a valuable exercise and I can reuse the code.) | ||||
| 
 | ||||
| Disclaimer | ||||
| ---------- | ||||
| I am not an antisemitist, I'm just being an offensive asshole with the naming. | ||||
| 
 | ||||
| License | ||||
| ------- | ||||
| `ZyklonB' is written by Přemysl Janouch <p.janouch@gmail.com>. | ||||
|  | ||||
							
								
								
									
										1772
									
								
								src/common.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1772
									
								
								src/common.c
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										796
									
								
								src/kike.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										796
									
								
								src/kike.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,796 @@ | ||||
| /*
 | ||||
|  * kike.c: the experimental IRC daemon | ||||
|  * | ||||
|  * Copyright (c) 2014, Přemysl Janouch <p.janouch@gmail.com> | ||||
|  * All rights reserved. | ||||
|  * | ||||
|  * Permission to use, copy, modify, and/or distribute this software for any | ||||
|  * purpose with or without fee is hereby granted, provided that the above | ||||
|  * copyright notice and this permission notice appear in all copies. | ||||
|  * | ||||
|  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||||
|  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||||
|  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | ||||
|  * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||||
|  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | ||||
|  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | ||||
|  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||||
|  * | ||||
|  */ | ||||
| 
 | ||||
| #define PROGRAM_NAME "kike" | ||||
| #define PROGRAM_VERSION "alpha" | ||||
| 
 | ||||
| #include "common.c" | ||||
| 
 | ||||
| // --- Configuration (application-specific) ------------------------------------
 | ||||
| 
 | ||||
| static struct config_item g_config_table[] = | ||||
| { | ||||
| 	{ "server_name",     NULL,              "Server name"                    }, | ||||
| 	{ "bind_host",       NULL,              "Address of the IRC server"      }, | ||||
| 	{ "bind_port",       "6667",            "Port of the IRC server"         }, | ||||
| 	{ "ssl_cert",        NULL,              "Server SSL certificate (PEM)"   }, | ||||
| 	{ "ssl_key",         NULL,              "Server SSL private key (PEM)"   }, | ||||
| 
 | ||||
| 	{ "max_connections", NULL,              "Maximum client connections"     }, | ||||
| 	{ NULL,              NULL,              NULL                             } | ||||
| }; | ||||
| 
 | ||||
| // --- Signals -----------------------------------------------------------------
 | ||||
| 
 | ||||
| static int g_signal_pipe[2];            ///< A pipe used to signal... signals
 | ||||
| 
 | ||||
| /// Program termination has been requested by a signal
 | ||||
| static volatile sig_atomic_t g_termination_requested; | ||||
| 
 | ||||
| static void | ||||
| sigterm_handler (int signum) | ||||
| { | ||||
| 	(void) signum; | ||||
| 
 | ||||
| 	g_termination_requested = true; | ||||
| 
 | ||||
| 	int original_errno = errno; | ||||
| 	if (write (g_signal_pipe[1], "t", 1) == -1) | ||||
| 		soft_assert (errno == EAGAIN); | ||||
| 	errno = original_errno; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| setup_signal_handlers (void) | ||||
| { | ||||
| 	if (pipe (g_signal_pipe) == -1) | ||||
| 	{ | ||||
| 		print_fatal ("pipe: %s", strerror (errno)); | ||||
| 		exit (EXIT_FAILURE); | ||||
| 	} | ||||
| 
 | ||||
| 	set_cloexec (g_signal_pipe[0]); | ||||
| 	set_cloexec (g_signal_pipe[1]); | ||||
| 
 | ||||
| 	// So that the pipe cannot overflow; it would make write() block within
 | ||||
| 	// the signal handler, which is something we really don't want to happen.
 | ||||
| 	// The same holds true for read().
 | ||||
| 	set_blocking (g_signal_pipe[0], false); | ||||
| 	set_blocking (g_signal_pipe[1], false); | ||||
| 
 | ||||
| 	signal (SIGPIPE, SIG_IGN); | ||||
| 
 | ||||
| 	struct sigaction sa; | ||||
| 	sa.sa_flags = SA_RESTART; | ||||
| 	sigemptyset (&sa.sa_mask); | ||||
| 	sa.sa_handler = sigterm_handler; | ||||
| 	if (sigaction (SIGINT, &sa, NULL) == -1 | ||||
| 	 || sigaction (SIGTERM, &sa, NULL) == -1) | ||||
| 	{ | ||||
| 		print_error ("sigaction: %s", strerror (errno)); | ||||
| 		exit (EXIT_FAILURE); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // --- Application data --------------------------------------------------------
 | ||||
| 
 | ||||
| enum | ||||
| { | ||||
| 	IRC_USER_MODE_INVISIBLE          = (1 << 0), | ||||
| 	IRC_USER_MODE_RX_WALLOPS         = (1 << 1), | ||||
| 	IRC_USER_MODE_RESTRICTED         = (1 << 2), | ||||
| 	IRC_USER_MODE_OPERATOR           = (1 << 3), | ||||
| 	IRC_USER_MODE_RX_SERVER_NOTICES  = (1 << 4) | ||||
| }; | ||||
| 
 | ||||
| struct connection | ||||
| { | ||||
| 	struct connection *next;            ///< The next link in a chain
 | ||||
| 	struct connection *prev;            ///< The previous link in a chain
 | ||||
| 
 | ||||
| 	struct server_context *ctx;         ///< Server context
 | ||||
| 
 | ||||
| 	int socket_fd;                      ///< The TCP socket
 | ||||
| 	struct str read_buffer;             ///< Unprocessed input
 | ||||
| 	struct str write_buffer;            ///< Output yet to be sent out
 | ||||
| 
 | ||||
| 	unsigned initialized    : 1;        ///< Has any data been received yet?
 | ||||
| 	unsigned ssl_rx_want_tx : 1;        ///< SSL_read() wants to write
 | ||||
| 	unsigned ssl_tx_want_rx : 1;        ///< SSL_write() wants to read
 | ||||
| 
 | ||||
| 	SSL_CTX *ssl_ctx;                   ///< SSL context
 | ||||
| 	SSL *ssl;                           ///< SSL connection
 | ||||
| 
 | ||||
| 	char *nickname;                     ///< IRC nickname (main identifier)
 | ||||
| 	char *username;                     ///< IRC username
 | ||||
| 	char *fullname;                     ///< IRC fullname (e-mail)
 | ||||
| 
 | ||||
| 	char *hostname;                     ///< Hostname shown to the network
 | ||||
| 
 | ||||
| 	unsigned mode;                      ///< User's mode
 | ||||
| 	char *away_message;                 ///< Away message
 | ||||
| }; | ||||
| 
 | ||||
| static void | ||||
| connection_init (struct connection *self) | ||||
| { | ||||
| 	memset (self, 0, sizeof *self); | ||||
| 
 | ||||
| 	self->socket_fd = -1; | ||||
| 	str_init (&self->read_buffer); | ||||
| 	str_init (&self->write_buffer); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| connection_free (struct connection *self) | ||||
| { | ||||
| 	if (!soft_assert (self->socket_fd == -1)) | ||||
| 		xclose (self->socket_fd); | ||||
| 	if (self->ssl_ctx) | ||||
| 		SSL_CTX_free (self->ssl_ctx); | ||||
| 	if (self->ssl) | ||||
| 		SSL_free (self->ssl); | ||||
| 
 | ||||
| 	str_free (&self->read_buffer); | ||||
| 	str_free (&self->write_buffer); | ||||
| 
 | ||||
| 	free (self->nickname); | ||||
| 	free (self->username); | ||||
| 	free (self->fullname); | ||||
| 
 | ||||
| 	free (self->hostname); | ||||
| 	free (self->away_message); | ||||
| } | ||||
| 
 | ||||
| enum | ||||
| { | ||||
| 	IRC_CHAN_MODE_INVITE_ONLY      = (1 << 0), | ||||
| 	IRC_CHAN_MODE_MODERATED        = (1 << 1), | ||||
| 	IRC_CHAN_MODE_NO_OUTSIDE_MSGS  = (1 << 2), | ||||
| 	IRC_CHAN_MODE_SECRET           = (1 << 3), | ||||
| 	IRC_CHAN_MODE_PRIVATE          = (1 << 4), | ||||
| 	IRC_CHAN_MODE_PROTECTED_TOPIC  = (1 << 5), | ||||
| 	IRC_CHAN_MODE_QUIET            = (1 << 6) | ||||
| }; | ||||
| 
 | ||||
| struct channel | ||||
| { | ||||
| 	struct server_context *ctx;         ///< Server context
 | ||||
| 
 | ||||
| 	char *name;                         ///< Channel name
 | ||||
| 	unsigned modes;                     ///< Channel modes
 | ||||
| 	char *key;                          ///< Channel key
 | ||||
| 	long user_limit;                    ///< User limit or -1
 | ||||
| 
 | ||||
| 	struct str_vector ban_list;         ///< Ban list
 | ||||
| 	struct str_vector exception_list;   ///< Exceptions from bans
 | ||||
| 	struct str_vector invite_list;      ///< Exceptions from +I
 | ||||
| }; | ||||
| 
 | ||||
| static void | ||||
| channel_init (struct channel *self) | ||||
| { | ||||
| 	memset (self, 0, sizeof *self); | ||||
| 
 | ||||
| 	str_vector_init (&self->ban_list); | ||||
| 	str_vector_init (&self->exception_list); | ||||
| 	str_vector_init (&self->invite_list); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| channel_free (struct channel *self) | ||||
| { | ||||
| 	free (self->name); | ||||
| 	free (self->key); | ||||
| 
 | ||||
| 	str_vector_free (&self->ban_list); | ||||
| 	str_vector_free (&self->exception_list); | ||||
| 	str_vector_free (&self->invite_list); | ||||
| } | ||||
| 
 | ||||
| struct server_context | ||||
| { | ||||
| 	struct str_map config;              ///< Server configuration
 | ||||
| 
 | ||||
| 	int listen_fd;                      ///< Listening socket FD
 | ||||
| 	struct connection *clients;         ///< Client connections
 | ||||
| 
 | ||||
| 	struct str_map users;               ///< Maps nicknames to connections
 | ||||
| 	struct str_map channels;            ///< Maps channel names to data
 | ||||
| 
 | ||||
| 	struct poller poller;               ///< Manages polled description
 | ||||
| 	bool polling;                       ///< The event loop is running
 | ||||
| }; | ||||
| 
 | ||||
| static void | ||||
| server_context_init (struct server_context *self) | ||||
| { | ||||
| 	str_map_init (&self->config); | ||||
| 	self->config.free = free; | ||||
| 	load_config_defaults (&self->config, g_config_table); | ||||
| 
 | ||||
| 	self->listen_fd = -1; | ||||
| 	self->clients = NULL; | ||||
| 
 | ||||
| 	str_map_init (&self->users); | ||||
| 	// TODO: set channel_free() as the free function?
 | ||||
| 	str_map_init (&self->channels); | ||||
| 
 | ||||
| 	poller_init (&self->poller); | ||||
| 	self->polling = false; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| server_context_free (struct server_context *self) | ||||
| { | ||||
| 	str_map_free (&self->config); | ||||
| 
 | ||||
| 	if (self->listen_fd != -1) | ||||
| 		xclose (self->listen_fd); | ||||
| 
 | ||||
| 	// TODO: terminate the connections properly before this is called
 | ||||
| 	struct connection *link, *tmp; | ||||
| 	for (link = self->clients; link; link = tmp) | ||||
| 	{ | ||||
| 		tmp = link->next; | ||||
| 		connection_free (link); | ||||
| 		free (link); | ||||
| 	} | ||||
| 
 | ||||
| 	str_map_free (&self->users); | ||||
| 	str_map_free (&self->channels); | ||||
| 	poller_free (&self->poller); | ||||
| } | ||||
| 
 | ||||
| // --- Main program ------------------------------------------------------------
 | ||||
| 
 | ||||
| static size_t network_error_domain_tag; | ||||
| #define NETWORK_ERROR (error_resolve_domain (&network_error_domain_tag)) | ||||
| 
 | ||||
| enum | ||||
| { | ||||
| 	NETWORK_ERROR_INVALID_CONFIGURATION, | ||||
| 	NETWORK_ERROR_FAILED | ||||
| }; | ||||
| 
 | ||||
| static bool | ||||
| irc_autodetect_ssl (struct connection *conn) | ||||
| { | ||||
| 	// Trivial SSL/TLS autodetection.  The first block of data returned by
 | ||||
| 	// recv() must be at least three bytes long for this to work reliably,
 | ||||
| 	// but that should not pose a problem in practice.
 | ||||
| 	//
 | ||||
| 	// SSL2:      1xxx xxxx | xxxx xxxx |    <1>
 | ||||
| 	//               (message length)  (client hello)
 | ||||
| 	// SSL3/TLS:    <22>    |    <3>    | xxxx xxxx
 | ||||
| 	//           (handshake)|  (protocol version)
 | ||||
| 	//
 | ||||
| 	// Such byte sequences should never occur at the beginning of regular IRC
 | ||||
| 	// communication, which usually begins with USER/NICK/PASS/SERVICE.
 | ||||
| 
 | ||||
| 	char buf[3]; | ||||
| start: | ||||
| 	switch (recv (conn->socket_fd, buf, sizeof buf, MSG_PEEK)) | ||||
| 	{ | ||||
| 	case 3: | ||||
| 		if ((buf[0] & 0x80) && buf[2] == 1) | ||||
| 			return true; | ||||
| 	case 2: | ||||
| 		if (buf[0] == 22 && buf[1] == 3) | ||||
| 			return true; | ||||
| 		break; | ||||
| 	case 1: | ||||
| 		if (buf[0] == 22) | ||||
| 			return true; | ||||
| 		break; | ||||
| 	case 0: | ||||
| 		break; | ||||
| 	default: | ||||
| 		if (errno == EINTR) | ||||
| 			goto start; | ||||
| 	} | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static int | ||||
| irc_ssl_verify_callback (int verify_ok, X509_STORE_CTX *ctx) | ||||
| { | ||||
| 	(void) verify_ok; | ||||
| 	(void) ctx; | ||||
| 
 | ||||
| 	// We only want to provide additional privileges based on the client's
 | ||||
| 	// certificate, so let's not terminate the connection because of a failure.
 | ||||
| 	return 1; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| irc_initialize_ssl (struct connection *conn) | ||||
| { | ||||
| 	struct server_context *ctx = conn->ctx; | ||||
| 
 | ||||
| 	conn->ssl_ctx = SSL_CTX_new (SSLv23_server_method ()); | ||||
| 	if (!conn->ssl_ctx) | ||||
| 		goto error_ssl_1; | ||||
| 	SSL_CTX_set_verify (conn->ssl_ctx, | ||||
| 		SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, irc_ssl_verify_callback); | ||||
| 	// XXX: maybe we should call SSL_CTX_set_options() for some workarounds
 | ||||
| 
 | ||||
| 	conn->ssl = SSL_new (conn->ssl_ctx); | ||||
| 	if (!conn->ssl) | ||||
| 		goto error_ssl_2; | ||||
| 
 | ||||
| 	const char *ssl_cert = str_map_find (&ctx->config, "ssl_cert"); | ||||
| 	if (ssl_cert | ||||
| 	 && !SSL_CTX_use_certificate_chain_file (conn->ssl_ctx, ssl_cert)) | ||||
| 	{ | ||||
| 		// XXX: perhaps we should read the file ourselves for better messages
 | ||||
| 		print_error ("%s: %s", "setting the SSL client certificate failed", | ||||
| 			ERR_error_string (ERR_get_error (), NULL)); | ||||
| 	} | ||||
| 
 | ||||
| 	const char *ssl_key = str_map_find (&ctx->config, "ssl_key"); | ||||
| 	if (ssl_key | ||||
| 	 && !SSL_use_PrivateKey_file (conn->ssl, ssl_key, SSL_FILETYPE_PEM)) | ||||
| 	{ | ||||
| 		// XXX: perhaps we should read the file ourselves for better messages
 | ||||
| 		print_error ("%s: %s", "setting the SSL private key failed", | ||||
| 			ERR_error_string (ERR_get_error (), NULL)); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: SSL_check_private_key(conn->ssl)?  It is has probably already been
 | ||||
| 	//   checked by SSL_use_PrivateKey_file() above.
 | ||||
| 
 | ||||
| 	SSL_set_accept_state (conn->ssl); | ||||
| 	if (!SSL_set_fd (conn->ssl, conn->socket_fd)) | ||||
| 		goto error_ssl_3; | ||||
| 	// Gah, spare me your awkward semantics, I just want to push data!
 | ||||
| 	// XXX: do we want SSL_MODE_AUTO_RETRY as well?  I guess not.
 | ||||
| 	SSL_set_mode (conn->ssl, | ||||
| 		SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE); | ||||
| 	return true; | ||||
| 
 | ||||
| error_ssl_3: | ||||
| 	SSL_free (conn->ssl); | ||||
| 	conn->ssl = NULL; | ||||
| error_ssl_2: | ||||
| 	SSL_CTX_free (conn->ssl_ctx); | ||||
| 	conn->ssl_ctx = NULL; | ||||
| error_ssl_1: | ||||
| 	// XXX: these error strings are really nasty; also there could be
 | ||||
| 	//   multiple errors on the OpenSSL stack.
 | ||||
| 	print_error ("%s: %s", "could not initialize SSL", | ||||
| 		ERR_error_string (ERR_get_error (), NULL)); | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| connection_abort (struct connection *conn, const char *reason) | ||||
| { | ||||
| 	// TODO: send a QUIT message with `reason' || "Client exited"
 | ||||
| 	(void) reason; | ||||
| 
 | ||||
| 	// TODO: do further cleanup if the client has successfully registered
 | ||||
| 
 | ||||
| 	struct server_context *ctx = conn->ctx; | ||||
| 	ssize_t i = poller_find_by_fd (&ctx->poller, conn->socket_fd); | ||||
| 	if (i != -1) | ||||
| 		poller_remove_at_index (&ctx->poller, i); | ||||
| 
 | ||||
| 	xclose (conn->socket_fd); | ||||
| 	conn->socket_fd = -1; | ||||
| 	connection_free (conn); | ||||
| 	LIST_UNLINK (ctx->clients, conn); | ||||
| 	free (conn); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| irc_process_message (const struct irc_message *msg, | ||||
| 	const char *raw, void *user_data) | ||||
| { | ||||
| 	struct connection *conn = user_data; | ||||
| 	// TODO
 | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| irc_try_read (struct connection *conn) | ||||
| { | ||||
| 	// TODO
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| irc_try_read_ssl (struct connection *conn) | ||||
| { | ||||
| 	if (conn->ssl_tx_want_rx) | ||||
| 		return true; | ||||
| 
 | ||||
| 	struct str *buf = &conn->read_buffer; | ||||
| 	conn->ssl_rx_want_tx = false; | ||||
| 	while (true) | ||||
| 	{ | ||||
| 		str_ensure_space (buf, 512); | ||||
| 		int n_read = SSL_read (conn->ssl, buf->str + buf->len, | ||||
| 			buf->alloc - buf->len - 1 /* null byte */); | ||||
| 
 | ||||
| 		const char *error_info = NULL; | ||||
| 		switch (xssl_get_error (conn->ssl, n_read, &error_info)) | ||||
| 		{ | ||||
| 		case SSL_ERROR_NONE: | ||||
| 			buf->str[buf->len += n_read] = '\0'; | ||||
| 			// TODO: discard characters above the 512 character limit
 | ||||
| 			irc_process_buffer (buf, irc_process_message, conn); | ||||
| 			continue; | ||||
| 		case SSL_ERROR_ZERO_RETURN: | ||||
| 			connection_abort (conn, NULL); | ||||
| 			return false; | ||||
| 		case SSL_ERROR_WANT_READ: | ||||
| 			return true; | ||||
| 		case SSL_ERROR_WANT_WRITE: | ||||
| 			conn->ssl_rx_want_tx = true; | ||||
| 			return false; | ||||
| 		case XSSL_ERROR_TRY_AGAIN: | ||||
| 			continue; | ||||
| 		default: | ||||
| 			print_debug ("%s: %s: %s", __func__, "SSL_read", error_info); | ||||
| 			connection_abort (conn, error_info); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| irc_try_write (struct connection *conn) | ||||
| { | ||||
| 	// TODO
 | ||||
| 	return false; | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| irc_try_write_ssl (struct connection *conn) | ||||
| { | ||||
| 	if (conn->ssl_rx_want_tx) | ||||
| 		return true; | ||||
| 
 | ||||
| 	struct str *buf = &conn->write_buffer; | ||||
| 	conn->ssl_tx_want_rx = false; | ||||
| 	while (buf->len) | ||||
| 	{ | ||||
| 		int n_written = SSL_write (conn->ssl, buf->str, buf->len); | ||||
| 
 | ||||
| 		const char *error_info = NULL; | ||||
| 		switch (xssl_get_error (conn->ssl, n_written, &error_info)) | ||||
| 		{ | ||||
| 		case SSL_ERROR_NONE: | ||||
| 			str_remove_slice (buf, 0, n_written); | ||||
| 			continue; | ||||
| 		case SSL_ERROR_ZERO_RETURN: | ||||
| 			connection_abort (conn, NULL); | ||||
| 			return false; | ||||
| 		case SSL_ERROR_WANT_WRITE: | ||||
| 			return true; | ||||
| 		case SSL_ERROR_WANT_READ: | ||||
| 			conn->ssl_tx_want_rx = true; | ||||
| 			return false; | ||||
| 		case XSSL_ERROR_TRY_AGAIN: | ||||
| 			continue; | ||||
| 		default: | ||||
| 			print_debug ("%s: %s: %s", __func__, "SSL_write", error_info); | ||||
| 			connection_abort (conn, error_info); | ||||
| 			return false; | ||||
| 		} | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| on_irc_client_ready (const struct pollfd *pfd, void *user_data) | ||||
| { | ||||
| 	// XXX: check/load `ssl_cert' and `ssl_key' earlier?
 | ||||
| 	struct connection *conn = user_data; | ||||
| 	if (!conn->initialized) | ||||
| 	{ | ||||
| 		hard_assert (pfd->events == POLLIN); | ||||
| 		// XXX: what with the error from irc_initialize_ssl()?
 | ||||
| 		if (irc_autodetect_ssl (conn) && !irc_initialize_ssl (conn)) | ||||
| 		{ | ||||
| 			connection_abort (conn, NULL); | ||||
| 			return; | ||||
| 		} | ||||
| 		conn->initialized = true; | ||||
| 	} | ||||
| 
 | ||||
| 	// FIXME: aborting a connection inside try_read() will fuck things up
 | ||||
| 	int new_events = 0; | ||||
| 	if (conn->ssl) | ||||
| 	{ | ||||
| 		// Reads may want to write, writes may want to read, poll() may
 | ||||
| 		// return unexpected things in `revents'... let's try both
 | ||||
| 		irc_try_read_ssl (conn) && irc_try_write_ssl (conn); | ||||
| 
 | ||||
| 		new_events |= POLLIN; | ||||
| 		if (conn->write_buffer.len || conn->ssl_rx_want_tx) | ||||
| 			new_events |= POLLOUT; | ||||
| 
 | ||||
| 		// While we're waiting for an opposite event, we ignore the original
 | ||||
| 		if (conn->ssl_rx_want_tx)  new_events &= ~POLLIN; | ||||
| 		if (conn->ssl_tx_want_rx)  new_events &= ~POLLOUT; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		irc_try_read (conn) && irc_try_write (conn); | ||||
| 
 | ||||
| 		new_events |= POLLIN; | ||||
| 		if (conn->write_buffer.len) | ||||
| 			new_events |= POLLOUT; | ||||
| 	} | ||||
| 
 | ||||
| 	hard_assert (new_events != 0); | ||||
| 	if (pfd->events != new_events) | ||||
| 		poller_set (&conn->ctx->poller, conn->socket_fd, new_events, | ||||
| 			(poller_dispatcher_func) on_irc_client_ready, conn); | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| on_irc_connection_available (const struct pollfd *pfd, void *user_data) | ||||
| { | ||||
| 	(void) pfd; | ||||
| 	struct server_context *ctx = user_data; | ||||
| 
 | ||||
| 	// TODO: stop accepting new connections when `max_connections' is reached
 | ||||
| 
 | ||||
| 	while (true) | ||||
| 	{ | ||||
| 		// XXX: `struct sockaddr_storage' is not the most portable thing
 | ||||
| 		struct sockaddr_storage peer; | ||||
| 		socklen_t peer_len = sizeof peer; | ||||
| 
 | ||||
| 		int fd = accept (ctx->listen_fd, (struct sockaddr *) &peer, &peer_len); | ||||
| 		if (fd == -1) | ||||
| 		{ | ||||
| 			if (errno == EAGAIN) | ||||
| 				break; | ||||
| 			if (errno == EINTR) | ||||
| 				continue; | ||||
| 			if (errno == ECONNABORTED) | ||||
| 				continue; | ||||
| 
 | ||||
| 			// TODO: handle resource exhaustion (EMFILE, ENFILE) specially
 | ||||
| 			//   (stop accepting new connections and wait until we close some).
 | ||||
| 			print_fatal ("%s: %s", "accept", strerror (errno)); | ||||
| 
 | ||||
| 			// FIXME: handle this better, bring the server down cleanly.
 | ||||
| 			exit (EXIT_FAILURE); | ||||
| 		} | ||||
| 
 | ||||
| 		char host[NI_MAXHOST] = "unknown", port[NI_MAXSERV] = "unknown"; | ||||
| 		int err = getnameinfo ((struct sockaddr *) &peer, peer_len, | ||||
| 			host, sizeof host, port, sizeof port, AI_NUMERICSERV); | ||||
| 		if (err) | ||||
| 			print_debug ("%s: %s", "getnameinfo", gai_strerror (err)); | ||||
| 		print_debug ("accepted connection from %s:%s", host, port); | ||||
| 
 | ||||
| 		struct connection *conn = xmalloc (sizeof *conn); | ||||
| 		connection_init (conn); | ||||
| 		conn->socket_fd = fd; | ||||
| 		conn->hostname = xstrdup (host); | ||||
| 		LIST_PREPEND (ctx->clients, conn); | ||||
| 
 | ||||
| 		// TODO: set a timeout on the socket, something like 3 minutes, then we
 | ||||
| 		//   should terminate the connection.
 | ||||
| 		poller_set (&ctx->poller, conn->socket_fd, POLLIN, | ||||
| 			(poller_dispatcher_func) on_irc_client_ready, conn); | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| static bool | ||||
| irc_listen (struct server_context *ctx, struct error **e) | ||||
| { | ||||
| 	const char *bind_host = str_map_find (&ctx->config, "bind_host"); | ||||
| 	const char *bind_port = str_map_find (&ctx->config, "bind_port"); | ||||
| 	hard_assert (bind_port != NULL);  // We have a default value for this
 | ||||
| 
 | ||||
| 	struct addrinfo gai_hints, *gai_result, *gai_iter; | ||||
| 	memset (&gai_hints, 0, sizeof gai_hints); | ||||
| 
 | ||||
| 	gai_hints.ai_socktype = SOCK_STREAM; | ||||
| 	gai_hints.ai_flags = AI_PASSIVE; | ||||
| 
 | ||||
| 	int err = getaddrinfo (bind_host, bind_port, &gai_hints, &gai_result); | ||||
| 	if (err) | ||||
| 	{ | ||||
| 		error_set (e, NETWORK_ERROR, NETWORK_ERROR_FAILED, "%s: %s: %s", | ||||
| 			"network setup failed", "getaddrinfo", gai_strerror (err)); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	int sockfd; | ||||
| 	char real_host[NI_MAXHOST], real_port[NI_MAXSERV]; | ||||
| 
 | ||||
| 	for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next) | ||||
| 	{ | ||||
| 		sockfd = socket (gai_iter->ai_family, | ||||
| 			gai_iter->ai_socktype, gai_iter->ai_protocol); | ||||
| 		if (sockfd == -1) | ||||
| 			continue; | ||||
| 		set_cloexec (sockfd); | ||||
| 
 | ||||
| 		int yes = 1; | ||||
| 		soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, | ||||
| 			&yes, sizeof yes) != -1); | ||||
| 		soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, | ||||
| 			&yes, sizeof yes) != -1); | ||||
| 
 | ||||
| 		real_host[0] = real_port[0] = '\0'; | ||||
| 		err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen, | ||||
| 			real_host, sizeof real_host, real_port, sizeof real_port, | ||||
| 			NI_NUMERICHOST | NI_NUMERICSERV); | ||||
| 		if (err) | ||||
| 			print_debug ("%s: %s", "getnameinfo", gai_strerror (err)); | ||||
| 
 | ||||
| 		if (bind (sockfd, gai_iter->ai_addr, gai_iter->ai_addrlen)) | ||||
| 			print_error ("bind() to %s:%s failed: %s", | ||||
| 				real_host, real_port, strerror (errno)); | ||||
| 		else if (listen (sockfd, 16 /* arbitrary number */)) | ||||
| 			print_error ("listen() at %s:%s failed: %s", | ||||
| 				real_host, real_port, strerror (errno)); | ||||
| 		else | ||||
| 			break; | ||||
| 
 | ||||
| 		xclose (sockfd); | ||||
| 	} | ||||
| 
 | ||||
| 	freeaddrinfo (gai_result); | ||||
| 
 | ||||
| 	if (!gai_iter) | ||||
| 	{ | ||||
| 		error_set (e, NETWORK_ERROR, NETWORK_ERROR_FAILED, | ||||
| 			"network setup failed"); | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	ctx->listen_fd = sockfd; | ||||
| 	poller_set (&ctx->poller, ctx->listen_fd, POLLIN, | ||||
| 		(poller_dispatcher_func) on_irc_connection_available, ctx); | ||||
| 
 | ||||
| 	print_status ("listening at %s:%s", real_host, real_port); | ||||
| 	return true; | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| on_signal_pipe_readable (const struct pollfd *fd, struct server_context *ctx) | ||||
| { | ||||
| 	char *dummy; | ||||
| 	(void) read (fd->fd, &dummy, 1); | ||||
| 
 | ||||
| #if 0 | ||||
| 	// TODO
 | ||||
| 	if (g_termination_requested && !ctx->quitting) | ||||
| 	{ | ||||
| 		initiate_quit (ctx); | ||||
| 	} | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| static void | ||||
| print_usage (const char *program_name) | ||||
| { | ||||
| 	fprintf (stderr, | ||||
| 		"Usage: %s [OPTION]...\n" | ||||
| 		"Experimental IRC server.\n" | ||||
| 		"\n" | ||||
| 		"  -d, --debug     run in debug mode (do not daemonize)\n" | ||||
| 		"  -h, --help      display this help and exit\n" | ||||
| 		"  -V, --version   output version information and exit\n" | ||||
| 		"  --write-default-cfg [filename]\n" | ||||
| 		"                  write a default configuration file and exit\n", | ||||
| 		program_name); | ||||
| } | ||||
| 
 | ||||
| int | ||||
| main (int argc, char *argv[]) | ||||
| { | ||||
| 	const char *invocation_name = argv[0]; | ||||
| 
 | ||||
| 	struct error *e = NULL; | ||||
| 	static struct option opts[] = | ||||
| 	{ | ||||
| 		{ "debug",             no_argument,       NULL, 'd' }, | ||||
| 		{ "help",              no_argument,       NULL, 'h' }, | ||||
| 		{ "version",           no_argument,       NULL, 'V' }, | ||||
| 		{ "write-default-cfg", optional_argument, NULL, 'w' }, | ||||
| 		{ NULL,                0,                 NULL,  0  } | ||||
| 	}; | ||||
| 
 | ||||
| 	while (1) | ||||
| 	{ | ||||
| 		int c, opt_index; | ||||
| 
 | ||||
| 		c = getopt_long (argc, argv, "dhV", opts, &opt_index); | ||||
| 		if (c == -1) | ||||
| 			break; | ||||
| 
 | ||||
| 		switch (c) | ||||
| 		{ | ||||
| 		case 'd': | ||||
| 			g_debug_mode = true; | ||||
| 			break; | ||||
| 		case 'h': | ||||
| 			print_usage (invocation_name); | ||||
| 			exit (EXIT_SUCCESS); | ||||
| 		case 'V': | ||||
| 			printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); | ||||
| 			exit (EXIT_SUCCESS); | ||||
| 		case 'w': | ||||
| 		{ | ||||
| 			char *filename = write_default_config (optarg, g_config_table, &e); | ||||
| 			if (!filename) | ||||
| 			{ | ||||
| 				print_fatal ("%s", e->message); | ||||
| 				error_free (e); | ||||
| 				exit (EXIT_FAILURE); | ||||
| 			} | ||||
| 			print_status ("configuration written to `%s'", filename); | ||||
| 			free (filename); | ||||
| 			exit (EXIT_SUCCESS); | ||||
| 		} | ||||
| 		default: | ||||
| 			print_fatal ("error in options"); | ||||
| 			exit (EXIT_FAILURE); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); | ||||
| 	setup_signal_handlers (); | ||||
| 
 | ||||
| 	SSL_library_init (); | ||||
| 	atexit (EVP_cleanup); | ||||
| 	SSL_load_error_strings (); | ||||
| 	// XXX: ERR_load_BIO_strings()?  Anything else?
 | ||||
| 	atexit (ERR_free_strings); | ||||
| 
 | ||||
| 	struct server_context ctx; | ||||
| 	server_context_init (&ctx); | ||||
| 
 | ||||
| 	if (!read_config_file (&ctx.config, &e)) | ||||
| 	{ | ||||
| 		print_fatal ("error loading configuration: %s", e->message); | ||||
| 		error_free (e); | ||||
| 		exit (EXIT_FAILURE); | ||||
| 	} | ||||
| 
 | ||||
| 	poller_set (&ctx.poller, g_signal_pipe[0], POLLIN, | ||||
| 		(poller_dispatcher_func) on_signal_pipe_readable, &ctx); | ||||
| 
 | ||||
| 	if (!irc_listen (&ctx, &e)) | ||||
| 	{ | ||||
| 		print_error ("%s", e->message); | ||||
| 		error_free (e); | ||||
| 		exit (EXIT_FAILURE); | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: daemonize
 | ||||
| 
 | ||||
| 	ctx.polling = true; | ||||
| 	while (ctx.polling) | ||||
| 		poller_run (&ctx.poller); | ||||
| 
 | ||||
| 	server_context_free (&ctx); | ||||
| 	return EXIT_SUCCESS; | ||||
| } | ||||
							
								
								
									
										1673
									
								
								src/zyklonb.c
									
									
									
									
									
								
							
							
						
						
									
										1673
									
								
								src/zyklonb.c
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user