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:
parent
c7cd0c40e0
commit
b58ee27362
467
src/kike.c
467
src/kike.c
|
@ -106,8 +106,9 @@ enum validation_result
|
|||
};
|
||||
|
||||
// Everything as per RFC 2812
|
||||
#define IRC_NICKNAME_MAX 9
|
||||
#define IRC_HOSTNAME_MAX 63
|
||||
#define IRC_MAX_NICKNAME 9
|
||||
#define IRC_MAX_HOSTNAME 63
|
||||
#define IRC_MAX_MESSAGE_LENGTH 510
|
||||
|
||||
static bool
|
||||
irc_regex_match (const char *regex, const char *s)
|
||||
|
@ -156,7 +157,7 @@ irc_validate_hostname (const char *hostname)
|
|||
return VALIDATION_ERROR_EMPTY;
|
||||
if (!irc_regex_match ("^" SN "(\\." SN ")*$", hostname))
|
||||
return VALIDATION_ERROR_INVALID;
|
||||
if (strlen (hostname) > IRC_HOSTNAME_MAX)
|
||||
if (strlen (hostname) > IRC_MAX_HOSTNAME)
|
||||
return VALIDATION_ERROR_TOO_LONG;
|
||||
return VALIDATION_OK;
|
||||
}
|
||||
|
@ -180,6 +181,12 @@ irc_is_valid_host (const char *host)
|
|||
|| irc_is_valid_hostaddr (host);
|
||||
}
|
||||
|
||||
static bool
|
||||
irc_is_valid_user (const char *user)
|
||||
{
|
||||
return irc_regex_match ("^[^\r\n @]+$", user);
|
||||
}
|
||||
|
||||
static bool
|
||||
irc_validate_nickname (const char *nickname)
|
||||
{
|
||||
|
@ -187,7 +194,7 @@ irc_validate_nickname (const char *nickname)
|
|||
return VALIDATION_ERROR_EMPTY;
|
||||
if (!irc_regex_match ("^[" LE SP "][-0-9" LE SP "]*$", nickname))
|
||||
return VALIDATION_ERROR_INVALID;
|
||||
if (strlen (nickname) > IRC_NICKNAME_MAX)
|
||||
if (strlen (nickname) > IRC_MAX_NICKNAME)
|
||||
return VALIDATION_ERROR_TOO_LONG;
|
||||
return VALIDATION_OK;
|
||||
}
|
||||
|
@ -212,6 +219,7 @@ enum
|
|||
IRC_USER_MODE_RX_SERVER_NOTICES = (1 << 4)
|
||||
};
|
||||
|
||||
// TODO: rename to client?
|
||||
struct connection
|
||||
{
|
||||
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
|
||||
|
||||
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_tx_want_rx : 1; ///< SSL_write() wants to read
|
||||
|
||||
SSL *ssl; ///< SSL connection
|
||||
|
||||
char *nickname; ///< IRC nickname (main identifier)
|
||||
|
@ -348,6 +357,7 @@ struct server_context
|
|||
char *server_name; ///< Our server name
|
||||
struct str_map users; ///< Maps nicknames to connections
|
||||
struct str_map channels; ///< Maps channel names to data
|
||||
struct str_map handlers; ///< Message handlers
|
||||
|
||||
struct poller poller; ///< Manages polled description
|
||||
bool quitting; ///< User requested quitting
|
||||
|
@ -369,8 +379,12 @@ server_context_init (struct server_context *self)
|
|||
|
||||
self->server_name = NULL;
|
||||
str_map_init (&self->users);
|
||||
self->users.key_cmp = irc_strcmp;
|
||||
// TODO: set channel_free() as the free function?
|
||||
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);
|
||||
self->quitting = false;
|
||||
|
@ -402,6 +416,7 @@ server_context_free (struct server_context *self)
|
|||
free (self->server_name);
|
||||
str_map_free (&self->users);
|
||||
str_map_free (&self->channels);
|
||||
str_map_free (&self->handlers);
|
||||
poller_free (&self->poller);
|
||||
|
||||
str_vector_free (&self->motd);
|
||||
|
@ -420,83 +435,6 @@ enum
|
|||
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
|
||||
connection_kill (struct connection *conn, const char *reason)
|
||||
{
|
||||
|
@ -517,14 +455,297 @@ connection_kill (struct connection *conn, const char *reason)
|
|||
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
|
||||
irc_process_message (const struct irc_message *msg,
|
||||
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;
|
||||
// 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
|
||||
irc_try_read (struct connection *conn)
|
||||
{
|
||||
|
@ -665,6 +886,72 @@ irc_try_write_ssl (struct connection *conn)
|
|||
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
|
||||
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
|
||||
irc_initialize_ssl (struct server_context *ctx)
|
||||
{
|
||||
|
@ -1088,6 +1388,7 @@ main (int argc, char *argv[])
|
|||
|
||||
struct server_context ctx;
|
||||
server_context_init (&ctx);
|
||||
irc_register_handlers (&ctx);
|
||||
|
||||
struct error *e = NULL;
|
||||
if (!read_config_file (&ctx.config, &e))
|
||||
|
|
Loading…
Reference in New Issue