#include "irc.h" #include #include typedef struct IrcMessage IrcMessage; typedef struct CommandInfo CommandInfo; typedef enum { /* Errors */ ERR_NOSUCHNICK = 401, ERR_NOSUCHSERVER = 402, ERR_NOSUCHCHANNEL = 403, ERR_CANNOTSENDTOCHAN = 404, ERR_TOOMANYCHANNELS = 405, ERR_WASNOSUCHNICK = 406, ERR_TOOMANYTARGETS = 407, ERR_NOORIGIN = 409, ERR_NORECIPIENT = 411, ERR_NOTEXTTOSEND = 412, ERR_NOTOPLEVEL = 413, ERR_WILDTOPLEVEL = 414, ERR_UNKNOWNCOMMAND = 421, ERR_NOMOTD = 422, ERR_NOADMININFO = 423, ERR_FILEERROR = 424, ERR_NONICKNAMEGIVEN = 431, ERR_ERRONEUSNICKNAME = 432, ERR_NICKNAMEINUSE = 433, ERR_NICKCOLLISION = 436, ERR_USERNOTINCHANNEL = 441, ERR_NOTONCHANNEL = 442, ERR_USERONCHANNEL = 443, ERR_NOLOGIN = 444, ERR_SUMMONDISABLED = 445, ERR_USERSDISABLED = 446, ERR_NOTREGISTERED = 451, ERR_NEEDMOREPARAMS = 461, ERR_ALREADYREGISTRED = 462, ERR_NOPERMFORHOST = 463, ERR_PASSWDMISMATCH = 464, ERR_YOUREBANNEDCREEP = 465, ERR_KEYSET = 467, ERR_CHANNELISFULL = 471, ERR_UNKNOWNMODE = 472, ERR_INVITEONLYCHAN = 473, ERR_BANNEDFROMCHAN = 474, ERR_BADCHANNELKEY = 475, ERR_NOPRIVILEGES = 481, ERR_CHANOPRIVSNEEDED = 482, ERR_CANTKILLSERVER = 483, ERR_NOOPERHOST = 491, ERR_UMODEUNKNOWNFLAG = 501, ERR_USERSDONTMATCH = 502, /* Replies */ RPL_NONE = 300, RPL_USERHOST = 302, RPL_ISON = 303, RPL_AWAY = 301, RPL_UNAWAY = 305, RPL_NOWAWAY = 306, RPL_WHOISUSER = 311, RPL_WHOISSERVER = 312, RPL_WHOISOPERATOR = 313, RPL_WHOISIDLE = 317, RPL_ENDOFWHOIS = 318, RPL_WHOWASUSER = 314, RPL_ENDOFWHOWAS = 369, RPL_LISTSTART = 321, RPL_LIST = 322, RPL_LISTEND = 323, RPL_CHANNELMODEIS = 324, RPL_NOTOPIC = 331, RPL_TOPIC = 332, RPL_INVITING = 341, RPL_SUMMONING = 342, RPL_VERSION = 351, RPL_WHOREPLY = 352, RPL_ENDOFWHO = 315, RPL_NAMREPLY = 353, RPL_ENDOFNAMES = 366, RPL_LINKS = 364, RPL_ENDOFLINKS = 365, RPL_BANLIST = 367, RPL_ENDOFBANLIST = 368, RPL_INFO = 371, RPL_ENDOFINFO = 374, RPL_MOTDSTART = 375, RPL_MOTD = 372, RPL_ENDOFMOTD = 376, RPL_YOUREOPER = 381, RPL_REHASHING = 382, RPL_TIME = 391, RPL_USERSSTART = 392, RPL_USERS = 393, RPL_ENDOFUSERS = 394, RPL_NOUSERS = 395, RPL_TRACELINK = 200, RPL_TRACECONNECTING = 201, RPL_TRACEHANDSHAKE = 202, RPL_TRACEUNKNOWN = 203, RPL_TRACEOPERATOR = 204, RPL_TRACEUSER = 205, RPL_TRACESERVER = 206, RPL_TRACENEWTYPE = 208, RPL_TRACELOG = 261, RPL_STATSLINKINFO = 211, RPL_STATSCOMMANDS = 212, RPL_STATSCLINE = 213, RPL_STATSNLINE = 214, RPL_STATSILINE = 215, RPL_STATSKLINE = 216, RPL_STATSYLINE = 218, RPL_ENDOFSTATS = 219, RPL_STATSLLINE = 241, RPL_STATSUPTIME = 242, RPL_STATSOLINE = 243, RPL_STATSHLINE = 244, RPL_UMODEIS = 221, RPL_LUSERCLIENT = 251, RPL_LUSEROP = 252, RPL_LUSERUNKNOWN = 253, RPL_LUSERCHANNELS = 254, RPL_LUSERME = 255, RPL_ADMINME = 256, RPL_ADMINLOC1 = 257, RPL_ADMINLOC2 = 258, RPL_ADMINEMAIL = 259, /* These codes are not specified by the RFC spec, just * used internally. (IRC can't use them, because they * have more than three digits) */ CMD_NOTICE = 9001, CMD_PRIVMSG = 9002, CMD_PASS = 9003, CMD_NICK = 9004, CMD_USER = 9005, CMD_UNKNOWN = 10000, /* message not understood */ } ReplyCode; #define CR 0x0d #define LF 0x0a #define NUL 0x00 #define BELL 0x07 #define SPACE 0x20 #define COMMA 0x2c static const gchar CRLF[] = { CR, LF, '\0' }; struct CommandInfo { const ReplyCode code; const char *text; }; static const CommandInfo commands[] = { { CMD_NOTICE, "NOTICE" }, { CMD_PRIVMSG, "PRIVMSG" }, { CMD_USER, "USER" }, { CMD_PASS, "PASS" }, { CMD_NICK, "NICK" }, { CMD_UNKNOWN, "Unknown command" } }; static ReplyCode command_to_code (const char *start, const char *end) { char *command = g_strndup (start, end - start); ReplyCode code = CMD_UNKNOWN; int i; for (i = 0; i < G_N_ELEMENTS (commands); ++i) { if (strcmp (command, commands[i].text) == 0) { code = commands[i].code; break; } } return code; } static const char * code_to_command (ReplyCode code) { const char *cmd = NULL; int i; for (i = 0; i < G_N_ELEMENTS (commands); ++i) { if (commands[i].code == code) { cmd = commands[i].text; break; } } return cmd; } struct IrcMessage { char * prefix; ReplyCode code; GQueue * parameters; GList * free_list; }; static IrcMessage *message_new_parsed (const char **input, int *length); static void message_free (IrcMessage *msg); static gpointer trace (gpointer data, gpointer free_func, GList **freelist); /* * Message parser */ static int eat_spaces (const char **input, int *length) { int n_spaces; n_spaces = 0; while (**input == SPACE) { *input += 1; *length -= 1; n_spaces++; } return n_spaces; } static const char * find_char (const char *input, int length, char c) { int i; for (i = 0; i < length; ++i) { if (input[i] == c) return input + i; } return NULL; } static char * parse_prefix (const char **input, int *length, GList **free_list, gboolean *error) { const char *space; space = find_char (*input, *length, SPACE); if (!space || space == *input) { *error = TRUE; return NULL; } else { int prefix_len = space - *input; char *result = g_strndup (*input, prefix_len); *input += prefix_len; *length -= prefix_len; return trace (result, g_free, free_list); } } static ReplyCode parse_command (const char **input, int *length, GList **free_list, gboolean *err) { ReplyCode result; if (*length < 1) goto error; if (g_ascii_isalpha (**input)) { const char *start = *input; while (*length > 0 && g_ascii_isalpha (**input)) { *input += 1; *length -= 1; } return command_to_code (start, *input); } else if (g_ascii_isdigit (**input)); { int d1, d2, d3; if (*length < 3) goto error; d1 = (*input)[0] - '0'; d2 = (*input)[1] - '0'; d3 = (*input)[2] - '0'; if (d1 < 0 || d1 > 9 || d2 < 0 || d2 > 9 || d3 < 0 || d3 > 9) { goto error; } *input += 3; *length -= 3; return (ReplyCode) d1 * 100 + d2 * 10 + d3; } return result; error: *err = TRUE; return -1; } static gboolean is_trailing_char (char c) { return c != NUL && c != CR && c != LF; } static gboolean is_middle_char (char c) { return c != SPACE && c != NUL && c != CR && c != LF; } static char * parse_trailing (const char **input, int *length, GList **free_list, gboolean *err) { const char *start = *input; while (*length > 0 && is_trailing_char (**input)) { *input += 1; *length -= 1; } if (*input != start) return trace (g_strndup (start, *input - start), g_free, free_list); else return NULL; } static char * parse_middle (const char **input, int *length, GList **free_list, gboolean *err) { const char *start = *input; while (*length > 0 && is_middle_char (**input)) { *input += 1; *length -= 1; } if (*input == start) goto error; return trace (g_strndup (start, *input - start), g_free, free_list); error: *err = TRUE; return NUL; } static GQueue * parse_params (const char **input, int *length, GList **free_list, gboolean *err) { GQueue *params = NULL; if (*length < 1) goto error; if (**input == SPACE) eat_spaces (input, length); if (*length > 0) { char *trailing; if (**input == ':') { *input += 1; *length -= 1; /* */ trailing = parse_trailing (input, length, free_list, err); if (*err) goto error; if (trailing) { params = trace (g_queue_new(), g_queue_free, free_list); g_queue_push_tail (params, trailing); } } else if (is_middle_char (**input)) { char *middle; /* */ middle = parse_middle (input, length, free_list, err); if (*err) goto error; /* */ /* The RFC states that parameters always start with SPACE, * but that is a lie: The final params following a * can be the empty string */ if (**input == SPACE) { params = parse_params (input, length, free_list, err); if (*err) goto error; } else { params = trace (g_queue_new(), g_queue_free, free_list); g_queue_push_head (params, middle); } } } if (!params) params = trace (g_queue_new(), g_queue_free, free_list); return params; error: *err = TRUE; return NULL; } /* Returns NULL if *input is not a valid message. This will also * happen if we just haven't received enough data yet. */ static IrcMessage * message_new_parsed (const char **input, int *length) { IrcMessage *msg = g_new (IrcMessage, 1); const char *crlf; gboolean err = FALSE; msg->free_list = NULL; if (*length < 2) goto error; crlf = strstr (*input, CRLF); if (!crlf) goto error; if (crlf - *input > 510) goto error; msg->prefix = NULL; if (**input == ':') { *input += 1; *length -= 1; msg->prefix = parse_prefix (input, length, &msg->free_list, &err); if (err) goto error; if (eat_spaces (input, length) < 1) goto error; } msg->code = parse_command (input, length, &msg->free_list, &err); if (err) goto error; msg->parameters = parse_params (input, length, &msg->free_list, &err); if (err) goto error; if (*length < 2) goto error; if (strncmp (*input, CRLF, 2) != 0) goto error; *input += 2; *length -= 2; return msg; error: message_free (msg); return NULL; } static void message_free (IrcMessage *msg) { GList *list; for (list = msg->free_list; list != NULL; list = list->next->next) { GDestroyNotify free_func = list->data; gpointer alloc = list->next->data; (* free_func) (alloc); } g_list_free (msg->free_list); g_free (msg); } typedef enum { NOT_CONNECTED, CONNECTING, CONNECTED } IrcServerState; struct IrcServer { /* information about server */ char * hostname; gint port; /* information about user */ char * local_user_name; char * local_host_name; char * local_domain_name; char * real_name; char * nickname; char * password; /* state */ IrcServerState state; LacConnection * connection; IrcServerFunc func; gpointer data; GString * unparsed; GQueue * messages; }; IrcServer * irc_server_new (const char *hostname, gint port, IrcServerFunc func, gpointer data) { IrcServer *server; g_return_val_if_fail (hostname != NULL, NULL); g_return_val_if_fail (port > 0, NULL); g_return_val_if_fail (func != NULL, NULL); server = g_new (IrcServer, 1); server->hostname = g_strdup (hostname); server->port = port; server->local_user_name = NULL; server->local_host_name = NULL; server->local_domain_name = NULL; server->real_name = NULL; server->nickname = NULL; server->password = NULL; server->state = NOT_CONNECTED; server->connection = NULL; server->func = func; server->data = data; server->unparsed = g_string_new (""); server->messages = g_queue_new (); return server; } gpointer irc_server_get_data (IrcServer *server) { g_return_val_if_fail (server != NULL, NULL); return server->data; } static void server_emit_dns_complete (IrcServer *server) { } static void server_emit_error (IrcServer *server, const GError *err) { } static void server_emit_connect (IrcServer *server) { } static void server_emit_close (IrcServer *server) { } static void emit_malformed_error (IrcServer *server, const char *msg) { GError *error = NULL; g_set_error (&error, IRC_ERROR, IRC_ERROR_MALFORMED_MESSAGE, "Server %s sent an unparsable message (%s) " "(This is most likely a bug in ChatterBox)", server->hostname, msg); server_emit_error (server, error); g_error_free (error); } static gboolean process_data (IrcServer *server) { char *crlf = strstr (server->unparsed->str, CRLF); if (!crlf && server->unparsed->len > 510) { /* We have seen 510 bytes in a row without * a CRLF. This means the server is sending * garbage */ emit_malformed_error (server, "No CRLF"); lac_connection_close (server->connection); return FALSE; } else if (crlf) { IrcMessage *msg; const char *s = server->unparsed->str; int len = server->unparsed->len; msg = message_new_parsed (&s, &len); if (!msg) { emit_malformed_error (server, "parse error"); lac_connection_close (server->connection); return FALSE; } g_string_erase (server->unparsed, 0, server->unparsed->len - len); /* process message */ #if 0 g_print ("\ncommand: %s - %d ", msg->prefix? msg->prefix : "", msg->code); for (list = msg->parameters->head; list != NULL; list = list->next) { g_print ("%s ", (char *)list->data); } g_print ("\n"); #endif message_free (msg); return TRUE; } else { g_print ("no data/not enough data\n"); /* No CRLF and less than 510 bytes: not enough * data yet */ return FALSE; } } static void server_handle_read (IrcServer *server, const LacConnectionReadEvent *read) { int i; for (i = 0; i < read->len; ++i) g_print ("%c", read->data[i]); g_string_append_len (server->unparsed, read->data, read->len); while (process_data (server)) ; } static void server_handle_disconnect (IrcServer *server) { server->state = NOT_CONNECTED; lac_connection_unref (server->connection); server->connection = NULL; } static void server_send_command (IrcServer *server, const char *prefix, ReplyCode code, const char *param, ...) { va_list var_args; GQueue *params = g_queue_new (); const char *command; GList *list; GString *message; command = code_to_command (code); g_return_if_fail (command != NULL); va_start (var_args, param); while (param != NULL) { g_queue_push_tail (params, (gpointer)param); param = va_arg (var_args, char *); } va_end (var_args); message = g_string_new (""); if (prefix) { g_string_append (message, ":"); g_string_append (message, prefix); g_string_append (message, " "); } g_string_append (message, command); for (list = params->head; list != NULL; list = list->next) { g_string_append (message, " "); if (!list->next) g_string_append (message, ":"); g_string_append (message, list->data); } g_string_append (message, CRLF); lac_connection_write (server->connection, message->str, message->len); g_string_free (message, TRUE); } static void server_send_registration (IrcServer *server) { server_send_command (server, NULL, CMD_PASS, server->password, NULL); server_send_command (server, NULL, CMD_NICK, server->nickname, NULL); server_send_command ( server, NULL, CMD_USER, server->local_user_name? server->local_user_name : "unknown", server->local_host_name? server->local_host_name : "unknown", server->local_domain_name? server->local_domain_name : "unknown", server->real_name? server->real_name : "Unknown", NULL); } static void connection_callback (LacConnection *connection, const LacConnectionEvent *event) { IrcServer *server = lac_connection_get_data (connection); g_assert (server); switch (event->type) { case LAC_CONNECTION_EVENT_CONNECT: /* connect successful */ server_emit_connect (server); server->state = CONNECTED; server_send_registration (server); break; case LAC_CONNECTION_EVENT_READ: server_handle_read (server, &(event->read)); break; /* data to read */ case LAC_CONNECTION_EVENT_CLOSE: /* Connection closed */ if (event->close.remote_closed) server_emit_close (server); server_handle_disconnect (server); break; case LAC_CONNECTION_EVENT_ERROR: /* error */ server_emit_error (server, event->error.err); server_handle_disconnect (server); break; } } static void address_callback (const LacAddress * address, gpointer data, const GError * err) { IrcServer *server = data; if (err) { server_emit_error (server, err); } else { server_emit_dns_complete (server); server->connection = lac_connection_new ( address, server->port, connection_callback, server); } } static char * generate_password (void) { return g_strdup_printf ("CH%x%x%x", g_random_int(), g_random_int(), g_random_int()); } void irc_server_connect (IrcServer *server, const char *nick, const char *password) { char *orig_pw, *orig_nick; g_return_if_fail (server != NULL); g_return_if_fail (server->state == NOT_CONNECTED); g_return_if_fail (nick != NULL); if (server->state != NOT_CONNECTED) return; orig_pw = server->password; orig_nick = server->nickname; if (password) server->password = g_strdup (password); else server->password = generate_password (); server->nickname = g_strdup (nick); if (orig_pw) g_free (orig_pw); if (orig_nick) g_free (orig_nick); server->state = CONNECTING; lac_address_new_lookup_from_name ( server->hostname, address_callback, server); } void irc_server_disconnect (IrcServer *server) { } /* Tracing memory */ static gpointer trace (gpointer data, gpointer free_func, GList **free_list) { *free_list = g_list_prepend (*free_list, data); *free_list = g_list_prepend (*free_list, free_func); return data; } GQuark irc_error_quark (void) { static GQuark q = 0; if (q == 0) q = g_quark_from_static_string ("irc-error-quark"); return q; }