terom@18: #include "irc_conn.h" terom@21: #include "irc_cmd.h" terom@39: #include "irc_proto.h" terom@22: #include "log.h" terom@18: terom@18: #include terom@18: #include terom@84: #include terom@18: terom@37: /** terom@45: * Handle an async error on this IRC connection that we could not recover from any other way, the protocol is now dead, terom@45: * and should be considered as destroyed after this returns. terom@45: * terom@45: * For conveniance, this returns the ERROR_CODE terom@21: */ terom@45: static err_t irc_conn_set_error (struct irc_conn *conn, struct error_info *err) terom@45: { terom@45: // notify user callback terom@45: conn->callbacks.on_error(conn, err, conn->cb_arg); terom@37: terom@45: return ERROR_CODE(err); terom@45: } terom@45: terom@37: terom@37: /** terom@37: * Update irc_conn.nickname terom@37: */ terom@37: static err_t irc_conn_set_nickname (struct irc_conn *conn, const char *nickname) terom@21: { terom@37: struct error_info err; terom@37: terom@171: // drop old nickname terom@171: free(conn->nickname); terom@171: terom@37: // strdup terom@37: if ((conn->nickname = strdup(nickname)) == NULL) { terom@37: SET_ERROR(&err, ERR_STRDUP); terom@37: terom@37: // notify terom@45: return irc_conn_set_error(conn, &err); terom@37: } terom@37: terom@37: // ok terom@37: return SUCCESS; terom@37: } terom@37: terom@37: /** terom@37: * 001 :Welcome to the Internet Relay Network !@ terom@37: */ terom@37: static void on_RPL_WELCOME (const struct irc_line *line, void *arg) terom@37: { terom@37: struct irc_conn *conn = arg; terom@24: terom@21: // update state terom@27: conn->registering = false; terom@21: conn->registered = true; terom@37: terom@37: // set our real nickname from the message target terom@37: if (irc_conn_set_nickname(conn, line->args[0])) terom@37: return; terom@22: terom@27: // trigger callback terom@27: if (conn->callbacks.on_registered) terom@27: conn->callbacks.on_registered(conn, conn->cb_arg); terom@21: } terom@21: terom@37: /** terom@20: * PING [ ] terom@20: * terom@20: * Send a 'PONG ` reply right away. terom@20: */ terom@37: static void on_PING (const struct irc_line *line, void *arg) terom@20: { terom@37: struct irc_conn *conn = arg; terom@24: terom@20: // just reply terom@20: irc_conn_PONG(conn, line->args[0]); terom@20: } terom@20: terom@37: /** terom@37: * NICK terom@37: * terom@37: * If the prefix is us, then update our nickname terom@37: */ terom@37: static void on_NICK (const struct irc_line *line, void *arg) terom@37: { terom@37: struct irc_conn *conn = arg; terom@37: terom@37: // ignore if it's not us terom@75: if (!line->source || irc_cmp_nick(line->source->nickname, conn->nickname)) terom@37: return; terom@37: terom@37: // update our nickname terom@75: irc_conn_set_nickname(conn, line->args[0]); terom@37: } terom@37: terom@37: /** terom@84: * CTCP ACTION handler terom@84: */ terom@84: static void irc_conn_on_CTCP_ACTION (const struct irc_line *line, void *arg) terom@84: { terom@84: struct irc_conn *conn = arg; terom@84: terom@84: // build the pseudo-line and invoke terom@84: struct irc_line action_line = { terom@84: .source = line->source, terom@84: .command = "CTCP ACTION", terom@84: .args = { terom@84: line->args[0], terom@84: line->args[1], terom@84: NULL terom@84: } terom@84: }; terom@84: terom@84: // invoke the general command handlers terom@84: irc_cmd_invoke(&conn->handlers, &action_line); terom@84: } terom@84: terom@84: /** terom@20: * Our command handlers terom@20: */ terom@84: static struct irc_cmd_handler irc_conn_handlers[] = { terom@37: { IRC_RPL_WELCOME, &on_RPL_WELCOME }, terom@37: { "PING", &on_PING }, terom@37: { "NICK", &on_NICK }, terom@37: { NULL, NULL, }, terom@84: terom@84: }, irc_conn_ctcp_handlers[] = { terom@84: { "ACTION", &irc_conn_on_CTCP_ACTION }, terom@84: { NULL, NULL } terom@20: }; terom@20: terom@33: /** terom@84: * Incoming CTCP message handler terom@84: * terom@84: * We only handle "simple" CTCP messages, i.e. those that begin and end with X-DELIM and contain a single tagged terom@84: * extended message. The full range of CTCP quoting etc specified in the "offical" spec referenced below is not terom@84: * implemented, as it is rarely used, or even implemented. terom@84: * terom@84: * http://www.irchelp.org/irchelp/rfc/ctcpspec.html terom@84: */ terom@84: static void irc_conn_on_CTCP (struct irc_conn *conn, const struct irc_line *line) terom@84: { terom@84: // copy the message data into a mutable buffer terom@101: char data_buf[strlen(line->args[1]) + 1], *data = data_buf; terom@84: strcpy(data_buf, line->args[1]); terom@84: terom@84: // should only be called when this is true... terom@84: assert(*data++ == '\001'); terom@84: terom@84: // tokenize the extended message terom@84: // XXX: do something with the trailing data? terom@84: char *msg = strsep(&data, "\001"); terom@84: terom@84: // parse the "command" tag terom@84: char *tag = strsep(&msg, " "); terom@84: terom@84: // invalid if missing terom@84: if (tag == NULL) terom@84: return log_warn("CTCP message with no tag: '%s'", data); terom@84: terom@84: // parse the CTCP "line" terom@84: struct irc_line ctcp_line = { terom@84: // the sender of the message terom@84: .source = line->source, terom@84: terom@84: // the CTCP extended message tag terom@84: .command = tag, terom@84: .args = { terom@84: // the destination of the message terom@84: line->args[0], terom@84: terom@84: // the rest of the CTCP extended message payload terom@84: msg, terom@84: terom@84: NULL terom@84: } terom@84: }; terom@84: terom@84: // invoke the CTCP command handlers terom@84: irc_cmd_invoke(&conn->ctcp_handlers, &ctcp_line); terom@84: } terom@84: terom@84: /** terom@23: * Incoming line handler terom@23: */ terom@84: static void irc_conn_on_line (char *line_buf, void *arg) terom@18: { terom@20: struct irc_conn *conn = arg; terom@18: struct irc_line line; terom@75: struct irc_nm nm; terom@18: int err; terom@18: terom@18: // log terom@22: log_debug("%s", line_buf); terom@18: terom@18: // parse terom@75: if ((err = irc_line_parse(&line, &nm, line_buf))) { terom@23: log_warn("invalid line: %s: %s\n", line_buf, error_name(err)); terom@20: return; terom@20: } terom@18: terom@84: // trap CTCP messages terom@84: if (strcasecmp(line.command, "PRIVMSG") == 0 && line.args[1][0] == '\001') terom@84: // parse and invoke the CTCP command handlers terom@84: irc_conn_on_CTCP(conn, &line); terom@84: terom@84: else terom@84: // invoke command handlers terom@84: irc_cmd_invoke(&conn->handlers, &line); terom@18: } terom@18: terom@33: /** terom@33: * Transport failed terom@33: */ terom@84: static void irc_conn_on_error (struct error_info *err, void *arg) terom@33: { terom@33: struct irc_conn *conn = arg; terom@33: terom@48: // EOF after quit? terom@163: if (ERROR_CODE(err) == ERR_EOF && conn->quitting) { terom@49: // udpate states terom@49: conn->registered = false; terom@49: conn->quitting = false; terom@49: conn->quit = true; terom@49: terom@48: // callback terom@48: if (conn->callbacks.on_quit) terom@48: conn->callbacks.on_quit(conn, conn->cb_arg); terom@48: terom@48: } else { terom@48: // log terom@172: log_error(err, "transport error"); terom@48: terom@48: // trash ourselves terom@48: irc_conn_set_error(conn, err); terom@48: } terom@33: } terom@33: terom@84: static struct line_proto_callbacks irc_conn_lp_callbacks = { terom@32: .on_line = &irc_conn_on_line, terom@33: .on_error = &irc_conn_on_error, terom@32: }; terom@32: terom@155: // XXX: ugly hack to get at an event_base terom@155: #include "sock_internal.h" terom@155: terom@155: struct event_base **ev_base_ptr = &_sock_stream_ctx.ev_base; terom@155: terom@155: err_t irc_conn_create (struct irc_conn **conn_ptr, transport_t *transport, const struct irc_conn_callbacks *callbacks, terom@155: void *cb_arg, error_t *err) terom@18: { terom@18: struct irc_conn *conn; terom@18: terom@18: // alloc new state struct terom@18: if ((conn = calloc(1, sizeof(struct irc_conn))) == NULL) terom@18: return SET_ERROR(err, ERR_CALLOC); terom@18: terom@27: // init state terom@27: conn->callbacks = *callbacks; terom@27: conn->cb_arg = cb_arg; terom@27: terom@23: // initialize command handlers terom@37: irc_cmd_init(&conn->handlers); terom@84: irc_cmd_init(&conn->ctcp_handlers); terom@23: terom@23: // add the core handlers terom@84: if ( terom@84: (ERROR_CODE(err) = irc_cmd_add(&conn->handlers, irc_conn_handlers, conn)) terom@84: || (ERROR_CODE(err) = irc_cmd_add(&conn->ctcp_handlers, irc_conn_ctcp_handlers, conn)) terom@84: ) terom@28: goto error; terom@23: terom@18: // create the line_proto, with our on_line handler terom@155: if (line_proto_create(&conn->lp, transport, IRC_LINE_MAX * 1.5, &irc_conn_lp_callbacks, conn, err)) terom@28: goto error; terom@18: terom@91: // create the outgoing line queue terom@155: if (irc_queue_create(&conn->out_queue, *ev_base_ptr, conn->lp, err)) terom@91: goto error; terom@91: terom@18: // ok terom@18: *conn_ptr = conn; terom@18: terom@18: return SUCCESS; terom@28: terom@28: error: terom@28: // release terom@28: irc_conn_destroy(conn); terom@28: terom@28: return ERROR_CODE(err); terom@28: } terom@28: terom@28: void irc_conn_destroy (struct irc_conn *conn) terom@28: { terom@47: // the line_proto terom@28: if (conn->lp) terom@156: line_proto_destroy(conn->lp); terom@91: terom@91: // the queue terom@91: if (conn->out_queue) terom@91: irc_queue_destroy(conn->out_queue); terom@37: terom@47: // the command handlers terom@171: irc_cmd_clear(&conn->handlers); terom@171: irc_cmd_clear(&conn->ctcp_handlers); terom@28: terom@47: // additional data terom@47: free(conn->nickname); terom@47: terom@47: // the irc_conn itself terom@28: free(conn); terom@18: } terom@18: terom@37: err_t irc_conn_add_cmd_handlers (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg) terom@23: { terom@37: // use the irc_cmd stuff terom@37: return irc_cmd_add(&conn->handlers, handlers, arg); terom@23: } terom@23: terom@27: err_t irc_conn_register (struct irc_conn *conn, const struct irc_conn_register_info *info) terom@27: { terom@27: err_t err; terom@27: terom@27: if (conn->registering || conn->registered) terom@27: return ERR_IRC_CONN_REGISTER_STATE; terom@27: terom@27: // send the initial messages terom@27: if ( terom@27: (err = irc_conn_NICK(conn, info->nickname)) terom@27: || (err = irc_conn_USER(conn, info->username, info->realname)) terom@27: ) terom@27: return err; terom@27: terom@27: // set state terom@27: conn->registering = true; terom@27: terom@27: // ok terom@27: return SUCCESS; terom@27: } terom@27: terom@150: bool irc_conn_self (struct irc_conn *conn, const char *nickname) terom@150: { terom@150: return conn && nickname && conn->nickname && (irc_cmp_nick(conn->nickname, nickname) == 0); terom@150: } terom@150: terom@18: err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line) terom@18: { terom@91: // just proxy off to irc_quuee terom@91: return irc_queue_process(conn->out_queue, line); terom@18: } terom@18: terom@18: err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname) terom@18: { terom@18: // NICK terom@18: struct irc_line line = { terom@97: NULL, "NICK", { nickname } terom@18: }; terom@18: terom@18: return irc_conn_send(conn, &line); terom@18: } terom@18: terom@18: err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname) terom@18: { terom@97: // USER * terom@18: struct irc_line line = { terom@97: NULL, "USER", { username, "0", "*", realname } terom@18: }; terom@18: terom@18: return irc_conn_send(conn, &line); terom@18: } terom@18: terom@20: err_t irc_conn_PONG (struct irc_conn *conn, const char *target) terom@20: { terom@20: // PONG [ ] terom@29: // params are actually the wrong way around now, but nobody cares terom@20: struct irc_line line = { terom@97: NULL, "PONG", { target } terom@20: }; terom@20: terom@20: return irc_conn_send(conn, &line); terom@20: } terom@20: terom@23: err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel) terom@23: { terom@97: // JOIN ( [ "," [ ... ] ]) [ [ "," [ ... ] ] ] terom@23: struct irc_line line = { terom@97: NULL, "JOIN", { channel } terom@97: }; terom@97: terom@97: return irc_conn_send(conn, &line); terom@97: } terom@97: terom@97: err_t irc_conn_PRIVMSG (struct irc_conn *conn, const char *target, const char *message) terom@97: { terom@97: // PRIVMSG terom@97: struct irc_line line = { terom@97: NULL, "PRIVMSG", { target, message } terom@23: }; terom@23: terom@23: return irc_conn_send(conn, &line); terom@23: } terom@23: terom@127: err_t irc_conn_NOTICE (struct irc_conn *conn, const char *target, const char *message) terom@127: { terom@127: // NOTICE terom@127: struct irc_line line = { terom@127: NULL, "NOTICE", { target, message } terom@127: }; terom@127: terom@127: return irc_conn_send(conn, &line); terom@127: } terom@127: terom@48: err_t irc_conn_QUIT (struct irc_conn *conn, const char *message) terom@48: { terom@48: err_t err; terom@48: terom@48: struct irc_line line = { terom@97: NULL, "QUIT", { message } terom@48: }; terom@48: terom@48: // state check terom@49: if (conn->quitting || conn->quit) terom@48: return ERR_IRC_CONN_QUIT_STATE; terom@48: terom@48: // try and send terom@48: if ((err = irc_conn_send(conn, &line))) terom@48: return err; terom@48: terom@48: // mark as quitting terom@48: conn->quitting = true; terom@48: terom@48: // ok terom@48: return SUCCESS; terom@48: } terom@48: