Implement client registration

And shuffle around some functions so that they form logical blocks (at least
I've tried; it's not that easy when you try to avoid forward declarations).
This commit is contained in:
Přemysl Eric Janouch 2014-07-14 02:35:38 +02:00
parent c7cd0c40e0
commit b58ee27362
1 changed files with 384 additions and 83 deletions

View File

@ -106,8 +106,9 @@ enum validation_result
}; };
// Everything as per RFC 2812 // Everything as per RFC 2812
#define IRC_NICKNAME_MAX 9 #define IRC_MAX_NICKNAME 9
#define IRC_HOSTNAME_MAX 63 #define IRC_MAX_HOSTNAME 63
#define IRC_MAX_MESSAGE_LENGTH 510
static bool static bool
irc_regex_match (const char *regex, const char *s) irc_regex_match (const char *regex, const char *s)
@ -156,7 +157,7 @@ irc_validate_hostname (const char *hostname)
return VALIDATION_ERROR_EMPTY; return VALIDATION_ERROR_EMPTY;
if (!irc_regex_match ("^" SN "(\\." SN ")*$", hostname)) if (!irc_regex_match ("^" SN "(\\." SN ")*$", hostname))
return VALIDATION_ERROR_INVALID; return VALIDATION_ERROR_INVALID;
if (strlen (hostname) > IRC_HOSTNAME_MAX) if (strlen (hostname) > IRC_MAX_HOSTNAME)
return VALIDATION_ERROR_TOO_LONG; return VALIDATION_ERROR_TOO_LONG;
return VALIDATION_OK; return VALIDATION_OK;
} }
@ -180,6 +181,12 @@ irc_is_valid_host (const char *host)
|| irc_is_valid_hostaddr (host); || irc_is_valid_hostaddr (host);
} }
static bool
irc_is_valid_user (const char *user)
{
return irc_regex_match ("^[^\r\n @]+$", user);
}
static bool static bool
irc_validate_nickname (const char *nickname) irc_validate_nickname (const char *nickname)
{ {
@ -187,7 +194,7 @@ irc_validate_nickname (const char *nickname)
return VALIDATION_ERROR_EMPTY; return VALIDATION_ERROR_EMPTY;
if (!irc_regex_match ("^[" LE SP "][-0-9" LE SP "]*$", nickname)) if (!irc_regex_match ("^[" LE SP "][-0-9" LE SP "]*$", nickname))
return VALIDATION_ERROR_INVALID; return VALIDATION_ERROR_INVALID;
if (strlen (nickname) > IRC_NICKNAME_MAX) if (strlen (nickname) > IRC_MAX_NICKNAME)
return VALIDATION_ERROR_TOO_LONG; return VALIDATION_ERROR_TOO_LONG;
return VALIDATION_OK; return VALIDATION_OK;
} }
@ -212,6 +219,7 @@ enum
IRC_USER_MODE_RX_SERVER_NOTICES = (1 << 4) IRC_USER_MODE_RX_SERVER_NOTICES = (1 << 4)
}; };
// TODO: rename to client?
struct connection struct connection
{ {
struct connection *next; ///< The next link in a chain struct connection *next; ///< The next link in a chain
@ -224,9 +232,10 @@ struct connection
struct str write_buffer; ///< Output yet to be sent out struct str write_buffer; ///< Output yet to be sent out
unsigned initialized : 1; ///< Has any data been received yet? unsigned initialized : 1; ///< Has any data been received yet?
unsigned registered : 1; ///< The user has registered
unsigned ssl_rx_want_tx : 1; ///< SSL_read() wants to write unsigned ssl_rx_want_tx : 1; ///< SSL_read() wants to write
unsigned ssl_tx_want_rx : 1; ///< SSL_write() wants to read unsigned ssl_tx_want_rx : 1; ///< SSL_write() wants to read
SSL *ssl; ///< SSL connection SSL *ssl; ///< SSL connection
char *nickname; ///< IRC nickname (main identifier) char *nickname; ///< IRC nickname (main identifier)
@ -348,6 +357,7 @@ struct server_context
char *server_name; ///< Our server name char *server_name; ///< Our server name
struct str_map users; ///< Maps nicknames to connections struct str_map users; ///< Maps nicknames to connections
struct str_map channels; ///< Maps channel names to data struct str_map channels; ///< Maps channel names to data
struct str_map handlers; ///< Message handlers
struct poller poller; ///< Manages polled description struct poller poller; ///< Manages polled description
bool quitting; ///< User requested quitting bool quitting; ///< User requested quitting
@ -369,8 +379,12 @@ server_context_init (struct server_context *self)
self->server_name = NULL; self->server_name = NULL;
str_map_init (&self->users); str_map_init (&self->users);
self->users.key_cmp = irc_strcmp;
// TODO: set channel_free() as the free function? // TODO: set channel_free() as the free function?
str_map_init (&self->channels); str_map_init (&self->channels);
self->channels.key_cmp = irc_strcmp;
str_map_init (&self->handlers);
self->handlers.key_cmp = irc_strcmp;
poller_init (&self->poller); poller_init (&self->poller);
self->quitting = false; self->quitting = false;
@ -402,6 +416,7 @@ server_context_free (struct server_context *self)
free (self->server_name); free (self->server_name);
str_map_free (&self->users); str_map_free (&self->users);
str_map_free (&self->channels); str_map_free (&self->channels);
str_map_free (&self->handlers);
poller_free (&self->poller); poller_free (&self->poller);
str_vector_free (&self->motd); str_vector_free (&self->motd);
@ -420,83 +435,6 @@ enum
NETWORK_ERROR_FAILED 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
connection_initialize_ssl (struct connection *conn)
{
// SSL support not enabled
if (!conn->ctx->ssl_ctx)
return false;
conn->ssl = SSL_new (conn->ctx->ssl_ctx);
if (!conn->ssl)
goto error_ssl_1;
if (!SSL_set_fd (conn->ssl, conn->socket_fd))
goto error_ssl_2;
SSL_set_accept_state (conn->ssl);
return true;
error_ssl_2:
SSL_free (conn->ssl);
conn->ssl = NULL;
error_ssl_1:
// XXX: these error strings are really nasty; also there could be
// multiple errors on the OpenSSL stack.
print_debug ("%s: %s: %s", "could not initialize SSL",
conn->hostname, ERR_error_string (ERR_get_error (), NULL));
return false;
}
static void static void
connection_kill (struct connection *conn, const char *reason) connection_kill (struct connection *conn, const char *reason)
{ {
@ -517,14 +455,297 @@ connection_kill (struct connection *conn, const char *reason)
free (conn); free (conn);
} }
static void
irc_send_str (struct connection *conn, const struct str *s)
{
// TODO: kill the connection above some "SendQ" threshold (careful!)
str_append_data (&conn->write_buffer, s->str,
s->len > IRC_MAX_MESSAGE_LENGTH ? IRC_MAX_MESSAGE_LENGTH : s->len);
str_append (&conn->write_buffer, "\r\n");
}
static void irc_send (struct connection *conn,
const char *format, ...) ATTRIBUTE_PRINTF (2, 3);
static void
irc_send (struct connection *conn, const char *format, ...)
{
struct str tmp;
str_init (&tmp);
va_list ap;
va_start (ap, format);
str_append_vprintf (&tmp, format, ap);
va_end (ap);
irc_send_str (conn, &tmp);
str_free (&tmp);
}
static const char *
irc_get_text (struct server_context *ctx, int id, const char *def)
{
if (!soft_assert (def != NULL))
def = "";
if (ctx->catalog == (nl_catd) -1)
return def;
return catgets (ctx->catalog, 1, id, def);
}
// --- IRC command handling ----------------------------------------------------
enum
{
IRC_RPL_WELCOME = 1,
IRC_RPL_YOURHOST = 2,
IRC_RPL_CREATED = 3,
IRC_RPL_MYINFO = 4,
IRC_RPL_MOTD = 372,
IRC_RPL_MOTDSTART = 375,
IRC_RPL_ENDOFMOTD = 376,
IRC_ERR_NOORIGIN = 409,
IRC_ERR_UNKNOWNCOMMAND = 421,
IRC_ERR_NOMOTD = 422,
IRC_ERR_NONICKNAMEGIVEN = 431,
IRC_ERR_ERRONEOUSNICKNAME = 432,
IRC_ERR_NICKNAMEINUSE = 433,
IRC_ERR_NOTREGISTERED = 451,
IRC_ERR_NEEDMOREPARAMS = 461,
IRC_ERR_ALREADYREGISTERED = 462
};
static const char *g_default_replies[] =
{
[IRC_RPL_WELCOME] = ":Welcome to the Internet Relay Network %s!%s@%s",
[IRC_RPL_YOURHOST] = ":Your host is %s, running version %s",
[IRC_RPL_CREATED] = ":This server was created %s",
[IRC_RPL_MYINFO] = "%s %s %s %s",
[IRC_RPL_MOTD] = ":- %s",
[IRC_RPL_MOTDSTART] = ":- %s Message of the day - ",
[IRC_RPL_ENDOFMOTD] = ":End of MOTD command",
[IRC_ERR_NOORIGIN] = ":No origin specified",
[IRC_ERR_UNKNOWNCOMMAND] = "%s: Unknown command",
[IRC_ERR_NOMOTD] = ":MOTD File is missing",
[IRC_ERR_NONICKNAMEGIVEN] = ":No nickname given",
[IRC_ERR_ERRONEOUSNICKNAME] = "%s :Erroneous nickname",
[IRC_ERR_NICKNAMEINUSE] = "%s :Nickname is already in use",
[IRC_ERR_NOTREGISTERED] = "%s :You have not registered",
[IRC_ERR_NEEDMOREPARAMS] = "%s :Not enough parameters",
[IRC_ERR_ALREADYREGISTERED] = ":Unauthorized command (already registered)",
};
// XXX: this way we cannot typecheck the arguments, so we must be careful
static void
irc_send_reply (struct connection *conn, int id, ...)
{
struct str tmp;
str_init (&tmp);
va_list ap;
va_start (ap, id);
str_append_printf (&tmp, ":%s %03d %s ",
conn->ctx->server_name, id, conn->nickname ? conn->nickname : "");
str_append_vprintf (&tmp,
irc_get_text (conn->ctx, id, g_default_replies[id]), ap);
va_end (ap);
irc_send_str (conn, &tmp);
str_free (&tmp);
}
static void
irc_send_motd (struct connection *conn)
{
struct server_context *ctx = conn->ctx;
if (!ctx->motd.len)
{
irc_send_reply (conn, IRC_ERR_NOMOTD);
return;
}
irc_send_reply (conn, IRC_RPL_MOTDSTART, ctx->server_name);
for (size_t i = 0; i < ctx->motd.len; i++)
irc_send_reply (conn, IRC_RPL_MOTD, ctx->motd.vector[i]);
irc_send_reply (conn, IRC_RPL_ENDOFMOTD);
}
static void
irc_try_finish_registration (struct connection *conn)
{
if (!conn->nickname || !conn->username || !conn->realname)
return;
conn->registered = true;
irc_send_reply (conn, IRC_RPL_WELCOME,
conn->nickname, conn->username, conn->hostname);
irc_send_reply (conn, IRC_RPL_YOURHOST,
conn->ctx->server_name, PROGRAM_VERSION);
// The purpose of this message eludes me
irc_send_reply (conn, IRC_RPL_CREATED, __DATE__);
irc_send_reply (conn, IRC_RPL_MYINFO,
conn->ctx->server_name, PROGRAM_VERSION,
IRC_SUPPORTED_USER_MODES, IRC_SUPPORTED_CHAN_MODES);
// Although not strictly required, bots often need this to work
irc_send_motd (conn);
}
static void
irc_handle_pass (const struct irc_message *msg, struct connection *conn)
{
if (conn->registered)
irc_send_reply (conn, IRC_ERR_ALREADYREGISTERED);
else if (msg->params.len < 1)
irc_send_reply (conn, IRC_ERR_NEEDMOREPARAMS, msg->command);
// We have SSL client certificates for this purpose; ignoring
}
static void
irc_handle_nick (const struct irc_message *msg, struct connection *conn)
{
struct server_context *ctx = conn->ctx;
if (conn->registered)
{
irc_send_reply (conn, IRC_ERR_ALREADYREGISTERED);
return;
}
if (msg->params.len < 1)
{
irc_send_reply (conn, IRC_ERR_NONICKNAMEGIVEN);
return;
}
const char *nickname = msg->params.vector[0];
if (irc_validate_nickname (nickname) != VALIDATION_OK)
{
irc_send_reply (conn, IRC_ERR_ERRONEOUSNICKNAME, nickname);
return;
}
if (str_map_find (&ctx->users, nickname))
{
irc_send_reply (conn, IRC_ERR_NICKNAMEINUSE, nickname);
return;
}
if (conn->nickname)
{
str_map_set (&ctx->users, conn->nickname, NULL);
free (conn->nickname);
}
// Allocate the nickname
conn->nickname = xstrdup (nickname);
str_map_set (&ctx->users, nickname, conn);
irc_try_finish_registration (conn);
}
static void
irc_handle_user (const struct irc_message *msg, struct connection *conn)
{
if (conn->registered)
{
irc_send_reply (conn, IRC_ERR_ALREADYREGISTERED);
return;
}
if (msg->params.len < 4)
{
irc_send_reply (conn, IRC_ERR_NEEDMOREPARAMS, msg->command);
return;
}
const char *username = msg->params.vector[0];
const char *mode = msg->params.vector[1];
const char *realname = msg->params.vector[3];
// Unfortunately the protocol doesn't give us any means of rejecting it
if (!irc_is_valid_user (username))
username = "xxx";
free (conn->username);
conn->username = xstrdup (username);
free (conn->realname);
conn->realname = xstrdup (realname);
unsigned long m;
if (xstrtoul (&m, mode, 10))
{
if (m & 4) conn->mode |= IRC_USER_MODE_RX_WALLOPS;
if (m & 8) conn->mode |= IRC_USER_MODE_INVISIBLE;
}
irc_try_finish_registration (conn);
}
static void
irc_handle_ping (const struct irc_message *msg, struct connection *conn)
{
// XXX: the RFC is pretty incomprehensible about the exact usage
if (msg->params.len < 1)
irc_send_reply (conn, IRC_ERR_NOORIGIN);
else
irc_send (conn, ":%s PONG :%s",
conn->ctx->server_name, msg->params.vector[0]);
}
// -----------------------------------------------------------------------------
struct irc_command
{
const char *name;
bool requires_registration;
void (*handler) (const struct irc_message *, struct connection *);
};
static void
irc_register_handlers (struct server_context *ctx)
{
static const struct irc_command message_handlers[] =
{
{ "PASS", false, irc_handle_pass },
{ "NICK", false, irc_handle_nick },
{ "USER", false, irc_handle_user },
{ "PING", true, irc_handle_ping }
};
for (size_t i = 0; i < N_ELEMENTS (message_handlers); i++)
{
const struct irc_command *cmd = &message_handlers[i];
str_map_set (&ctx->handlers, cmd->name, (void *) cmd->handler);
}
}
static void static void
irc_process_message (const struct irc_message *msg, irc_process_message (const struct irc_message *msg,
const char *raw, void *user_data) const char *raw, void *user_data)
{ {
(void) raw;
// XXX: we may want to discard everything following a QUIT etc.
// We can set a flag within the connection object.
// TODO: see RFC 2812 :!
struct connection *conn = user_data; struct connection *conn = user_data;
// TODO struct irc_command *cmd = str_map_find (&conn->ctx->handlers, msg->command);
if (!cmd)
irc_send_reply (conn, IRC_ERR_UNKNOWNCOMMAND,
"%s: Unknown command", msg->command);
else if (cmd->requires_registration && !conn->registered)
irc_send_reply (conn, IRC_ERR_NOTREGISTERED);
else
cmd->handler (msg, conn);
} }
// --- Network I/O -------------------------------------------------------------
static bool static bool
irc_try_read (struct connection *conn) irc_try_read (struct connection *conn)
{ {
@ -665,6 +886,72 @@ irc_try_write_ssl (struct connection *conn)
return true; return true;
} }
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 bool
connection_initialize_ssl (struct connection *conn)
{
// SSL support not enabled
if (!conn->ctx->ssl_ctx)
return false;
conn->ssl = SSL_new (conn->ctx->ssl_ctx);
if (!conn->ssl)
goto error_ssl_1;
if (!SSL_set_fd (conn->ssl, conn->socket_fd))
goto error_ssl_2;
SSL_set_accept_state (conn->ssl);
return true;
error_ssl_2:
SSL_free (conn->ssl);
conn->ssl = NULL;
error_ssl_1:
// XXX: these error strings are really nasty; also there could be
// multiple errors on the OpenSSL stack.
print_debug ("%s: %s: %s", "could not initialize SSL",
conn->hostname, ERR_error_string (ERR_get_error (), NULL));
return false;
}
static void static void
on_irc_client_ready (const struct pollfd *pfd, void *user_data) on_irc_client_ready (const struct pollfd *pfd, void *user_data)
{ {
@ -764,6 +1051,19 @@ on_irc_connection_available (const struct pollfd *pfd, void *user_data)
} }
} }
// -----------------------------------------------------------------------------
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 static bool
irc_initialize_ssl (struct server_context *ctx) irc_initialize_ssl (struct server_context *ctx)
{ {
@ -1088,6 +1388,7 @@ main (int argc, char *argv[])
struct server_context ctx; struct server_context ctx;
server_context_init (&ctx); server_context_init (&ctx);
irc_register_handlers (&ctx);
struct error *e = NULL; struct error *e = NULL;
if (!read_config_file (&ctx.config, &e)) if (!read_config_file (&ctx.config, &e))