# HG changeset patch # User Tero Marttila # Date 1236525457 -7200 # Node ID 542c73d07d3ccd171dfa6f58d5f946e091272b01 # Parent c339c020fd3344785562663d4cdc7b1c928f9e9f add a simple irc_log module (with evsql code) that joins a channel and log_info's PRIVMSGs diff -r c339c020fd33 -r 542c73d07d3c Makefile --- a/Makefile Sun Mar 01 02:02:48 2009 +0200 +++ b/Makefile Sun Mar 08 17:17:37 2009 +0200 @@ -24,6 +24,10 @@ GNUTLS_CFLAGS = $(shell pkg-config ${GNUTLS_NAME} --cflags) GNUTLS_LDFLAGS = $(shell pkg-config ${GNUTLS_NAME} --libs) +# evsql stuff +EVSQL_CFLAGS = +EVSQL_LDFLAGS = -levsql -lpq + BIN_NAMES = nexus BIN_PATHS = $(addprefix bin/,$(BIN_NAMES)) @@ -35,6 +39,7 @@ SOCK_GNUTLS_OBJS = obj/sock_gnutls.o LINEPROTO_OBJS = obj/line_proto.o IRC_OBJS = obj/irc_line.o obj/irc_conn.o +IRC_LOG_OBJS = obj/irc_log.o # XXX: not yet there #CORE_OBJS = obj/lib/log.o obj/lib/signals.o @@ -43,11 +48,11 @@ all: ${BIN_PATHS} # binaries -bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS} +bin/nexus: ${CORE_OBJS} ${SOCK_OBJS} ${SOCK_GNUTLS_OBJS} ${LINEPROTO_OBJS} ${IRC_OBJS} ${IRC_LOG_OBJS} # computed -CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} -LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS} +CFLAGS = ${MODE_CFLAGS} ${FIXED_CFLAGS} ${LIBEVENT_CFLAGS} ${GNUTLS_CFLAGS} ${EVSQL_CFLAGS} +LDFLAGS = ${LIBEVENT_LDFLAGS} ${GNUTLS_LDFLAGS} ${EVSQL_LDFLAGS} # XXX: is this valid? CPPFLAGS = ${CFLAGS} diff -r c339c020fd33 -r 542c73d07d3c src/error.h --- a/src/error.h Sun Mar 01 02:02:48 2009 +0200 +++ b/src/error.h Sun Mar 08 17:17:37 2009 +0200 @@ -68,6 +68,9 @@ _ERROR_CODE( ERR_EVENT_NEW, 0x010201, NONE ), _ERROR_CODE( ERR_EVENT_ADD, 0x010202, NONE ), + /* Evsql errors */ + _ERROR_CODE( ERR_EVSQL_NEW_PQ, 0x010301, NONE ), + /* irc_line errors */ _ERROR_CODE( ERR_LINE_TOO_LONG, 0x100101, NONE ), _ERROR_CODE( ERR_LINE_INVALID_TOKEN, 0x100102, NONE ), diff -r c339c020fd33 -r 542c73d07d3c src/irc_cmd.h --- a/src/irc_cmd.h Sun Mar 01 02:02:48 2009 +0200 +++ b/src/irc_cmd.h Sun Mar 08 17:17:37 2009 +0200 @@ -1,6 +1,41 @@ #ifndef IRC_CMD_H #define IRC_CMD_H +/** + * @file + * + * Flexible command handlers callback lists for use with irc_lines + */ + +#include "irc_conn.h" +#include "irc_line.h" +#include + +/** + * Single command -> handler mapping for lookup + */ +struct irc_cmd_handler { + /** The command name to match */ + const char *command; + + /** The handler function */ + void (*func) (struct irc_conn *conn, const struct irc_line *line, void *arg); +}; + +/** + * List item for a chain of irc_cmd_handler entries, with the context pointer + */ +struct irc_cmd_chain { + /** The list of handler lookup entries */ + struct irc_cmd_handler *handlers; + + /** Opaque context arg */ + void *arg; + + /** Linked list */ + STAILQ_ENTRY(irc_cmd_chain) node; +}; + /* * IRC command numerics */ diff -r c339c020fd33 -r 542c73d07d3c src/irc_conn.c --- a/src/irc_conn.c Sun Mar 01 02:02:48 2009 +0200 +++ b/src/irc_conn.c Sun Mar 08 17:17:37 2009 +0200 @@ -9,7 +9,7 @@ /* * "Welcome to the Internet Relay Network !@" */ -static void on_RPL_WELCOME (struct irc_conn *conn, const struct irc_line *line) +static void on_RPL_WELCOME (struct irc_conn *conn, const struct irc_line *line, void *arg) { // update state conn->registered = true; @@ -22,7 +22,7 @@ * * Send a 'PONG ` reply right away. */ -static void on_PING (struct irc_conn *conn, const struct irc_line *line) +static void on_PING (struct irc_conn *conn, const struct irc_line *line, void *arg) { // just reply irc_conn_PONG(conn, line->args[0]); @@ -31,23 +31,20 @@ /* * Our command handlers */ -struct irc_cmd_handler { - /* The command name */ - const char *command; - - /* The handler function */ - void (*func) (struct irc_conn *conn, const struct irc_line *line); - -} _cmd_handlers[] = { +struct irc_cmd_handler _cmd_handlers[] = { { IRC_RPL_WELCOME, on_RPL_WELCOME }, { "PING", on_PING }, { NULL, NULL, }, }; +/* + * Incoming line handler + */ void irc_conn_on_line (char *line_buf, void *arg) { struct irc_conn *conn = arg; struct irc_line line; + struct irc_cmd_chain *chain; struct irc_cmd_handler *handler; int err; @@ -56,18 +53,22 @@ // parse if ((err = irc_line_parse(&line, line_buf))) { - printf("!!! Invalid line: %s: %s\n", line_buf, error_name(err)); - + log_warn("invalid line: %s: %s\n", line_buf, error_name(err)); return; } + + // run each handler chain + STAILQ_FOREACH(chain, &conn->handlers, node) { + // look up appropriate handler + for (handler = chain->handlers; handler->command; handler++) { + // the command is alpha-only, so normal case-insensitive cmp is fine + if (strcasecmp(handler->command, line.command) == 0) { + // invoke the func + handler->func(conn, &line, chain->arg); - // look up appropriate handler - for (handler = _cmd_handlers; handler->command; handler++) { - // the command is alpha-only, so normal case-insensitive cmp is fine - if (strcasecmp(handler->command, line.command) == 0) { - // invoke the func - handler->func(conn, &line); - break; + // ...only one per chain + break; + } } } } @@ -80,6 +81,13 @@ if ((conn = calloc(1, sizeof(struct irc_conn))) == NULL) return SET_ERROR(err, ERR_CALLOC); + // initialize command handlers + STAILQ_INIT(&conn->handlers); + + // add the core handlers + if ((ERROR_CODE(err) = irc_conn_register_handler_chain(conn, _cmd_handlers, NULL))) + return ERROR_CODE(err); + // create the line_proto, with our on_line handler if (line_proto_create(&conn->lp, sock, IRC_LINE_MAX * 1.5, &irc_conn_on_line, conn, err)) return ERROR_CODE(err); @@ -97,6 +105,25 @@ return SUCCESS; } +err_t irc_conn_register_handler_chain (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg) +{ + struct irc_cmd_chain *item; + + // allocate the chain item + if ((item = calloc(1, sizeof(*item))) == NULL) + return ERR_CALLOC; + + // store + item->handlers = handlers; + item->arg = arg; + + // append + STAILQ_INSERT_TAIL(&conn->handlers, item, node); + + // ok + return SUCCESS; +} + err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line) { char line_buf[IRC_LINE_MAX + 2]; @@ -148,3 +175,13 @@ return irc_conn_send(conn, &line); } +err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel) +{ + // JOIN ( *( "," ) [ *( "," ) ] ) / "0" + struct irc_line line = { + NULL, "JOIN", { channel, NULL } + }; + + return irc_conn_send(conn, &line); +} + diff -r c339c020fd33 -r 542c73d07d3c src/irc_conn.h --- a/src/irc_conn.h Sun Mar 01 02:02:48 2009 +0200 +++ b/src/irc_conn.h Sun Mar 08 17:17:37 2009 +0200 @@ -1,18 +1,25 @@ #ifndef IRC_CONN_H #define IRC_CONN_H -/* - * Per-connection IRC setup and state/protocol handling. +/** + * @file + * + * Support for connections to IRC servers, a rather fundamental thing. This holds the line_proto to handle the network + * communications, and then takes care of sending/receiving commands, as well as updating some core state like + * current nickname. */ +struct irc_conn; + +#include "error.h" #include "sock.h" #include "line_proto.h" #include "irc_line.h" -#include "error.h" +#include "irc_cmd.h" #include /* - * A connection to an IRC server. + * Connection state */ struct irc_conn { /* We are a line-based protocol */ @@ -20,6 +27,9 @@ /* Registered (as in, we have a working nickname)? */ bool registered; + + /* Command handlers */ + STAILQ_HEAD(irc_conn_handlers, irc_cmd_chain) handlers; }; // XXX: this should probably be slightly reworked @@ -34,16 +44,31 @@ const char *realname; }; -/* +/** * Create a new irc_conn using the given sock_stream, which should be connected to an IRC server. The parameters given * in \a config will be used to identify with the IRC server. * * On success, the resulting irc_conn is returned via *conn with SUCCESS. Otherwise, -ERR_* and error info is returned * via *err. + * + * @param conn the new irc_conn structure is returned via this pointer + * @param sock the socket connected to the IRC server + * @param config the basic information used to register + * @param err errors are returned via this pointer + * @return error code */ err_t irc_conn_create (struct irc_conn **conn, struct sock_stream *sock, const struct irc_conn_config *config, struct error_info *err); /** + * Add a new chain of command handler callbacks to be used to handle irc_lines from the server. The given arg will be + * passed to the callbacks as the context argument. The chain will be appended to the end of the current list of chains. + * + * @param chain the array of irc_cmd_handler structs, terminated with a NULL entry + * @param arg the context argument to use for the callbacks + */ +err_t irc_conn_register_handler_chain (struct irc_conn *conn, struct irc_cmd_handler *handlers, void *arg); + +/** * @group Simple request functions * * The error handling of these functions is such that the error return code is can be used or ignored as convenient, @@ -52,26 +77,46 @@ * @{ */ -/* +/** * Send a generic IRC message + * + * @param conn the IRC protocol connection + * @param line the irc_line protocol line to send + * @return error code */ err_t irc_conn_send (struct irc_conn *conn, const struct irc_line *line); -/* +/** * Send a NICK message + * + * @param nickname the new nickname to use */ err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname); /* * Send a USER message + * + * @param username the username to register with, may be replaced with ident reply + * @param realname the full-name to register with */ err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname); /* * Send a PONG message to the given target + * + * @param target the PING source, aka. the target to send the PONG reply to */ err_t irc_conn_PONG (struct irc_conn *conn, const char *target); +/* + * Send a JOIN message for the given channel + * + * XXX: this doesn't implement the full command options + * + * @param channel the full channel name to join + */ +err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel); + // @} #endif /* IRC_CONN_H */ diff -r c339c020fd33 -r 542c73d07d3c src/irc_line.c --- a/src/irc_line.c Sun Mar 01 02:02:48 2009 +0200 +++ b/src/irc_line.c Sun Mar 08 17:17:37 2009 +0200 @@ -55,7 +55,7 @@ len = token_len + (flags & TOK_NOSPACE ? 0 : 1) + (flags & TOK_TRAILING ? 1 : 0); // check overflow - if (*offset + len > IRC_LINE_MAX) + if (*offset + len + 1 > IRC_LINE_MAX) return ERR_LINE_TOO_LONG; // check invalid tokens @@ -112,6 +112,9 @@ } } + // terminate, output_token reserved the space for this + buf[off] = '\0'; + // done return SUCCESS; } diff -r c339c020fd33 -r 542c73d07d3c src/irc_log.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/irc_log.c Sun Mar 08 17:17:37 2009 +0200 @@ -0,0 +1,58 @@ +#include "irc_log.h" +#include "log.h" + +// XXX: fix this err_t crap +#define LIB_ERR_H +#include + +/** + * The core irc_log state + */ +static struct irc_log_ctx { + /** The database connection */ + struct evsql *db; + +} _ctx; + +static void on_PRIVMSG (struct irc_conn *conn, const struct irc_line *line, void *arg) +{ + struct irc_log_ctx *ctx = arg; + + // log it! :P + log_debug("%s: %s: %s", line->prefix, line->args[0], line->args[1]); +} + +static struct irc_cmd_handler _cmd_handlers[] = { + { "PRIVMSG", &on_PRIVMSG }, + { NULL, NULL } +}; + +err_t irc_log_init (struct event_base *ev_base, const char *db_info, struct irc_conn *irc, const char *channel) +{ + struct irc_log_ctx *ctx = &_ctx; + err_t err; + + // open the database connection + if (db_info) { + log_info("connect to database: %s", db_info); + + if ((ctx->db = evsql_new_pq(ev_base, db_info, NULL, NULL)) == NULL) + return ERR_EVSQL_NEW_PQ; + } + + if (channel) { + log_info("join channel: %s", channel); + + // join the channel + if ((err = irc_conn_JOIN(irc, channel))) + return err; + } + + // register for events + if ((err = irc_conn_register_handler_chain(irc, _cmd_handlers, ctx))) + return err; + + // ok + return SUCCESS; +} + diff -r c339c020fd33 -r 542c73d07d3c src/irc_log.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/irc_log.h Sun Mar 08 17:17:37 2009 +0200 @@ -0,0 +1,21 @@ +#ifndef IRC_LOG_H +#define IRC_LOG_H + +/** + * @file + * + * Logging IRC events to an SQL database + */ + +#include "error.h" +#include "irc_conn.h" +#include + +/** + * Initialize the global irc_log state + * + * XXX: db_info is still unused if not specified + */ +err_t irc_log_init (struct event_base *ev_base, const char *db_info, struct irc_conn *irc, const char *channel); + +#endif diff -r c339c020fd33 -r 542c73d07d3c src/nexus.c --- a/src/nexus.c Sun Mar 01 02:02:48 2009 +0200 +++ b/src/nexus.c Sun Mar 08 17:17:37 2009 +0200 @@ -2,6 +2,7 @@ #include "log.h" #include "sock.h" #include "irc_conn.h" +#include "irc_log.h" #include #include @@ -15,12 +16,21 @@ #define DEFAULT_PORT "6667" #define DEFAULT_PORT_SSL "6697" +enum option_code { + _OPT_LOG_BEGIN = 0x00ff, + + OPT_LOG_DATABASE, + OPT_LOG_CHANNEL, +}; + static struct option options[] = { - {"help", 0, NULL, 'h' }, - {"hostname", 1, NULL, 'H' }, - {"port", 1, NULL, 'P' }, - {"ssl", 0, NULL, 'S' }, - {0, 0, 0, 0 }, + {"help", 0, NULL, 'h' }, + {"hostname", 1, NULL, 'H' }, + {"port", 1, NULL, 'P' }, + {"ssl", 0, NULL, 'S' }, + {"log-database", 1, NULL, OPT_LOG_DATABASE }, + {"log-channel", 1, NULL, OPT_LOG_CHANNEL }, + {0, 0, 0, 0 }, }; void usage (const char *exe) @@ -31,6 +41,8 @@ printf(" --hostname / -H HOST set hostname to connect to\n"); printf(" --port / -P PORT set service port to connect to\n"); printf(" --ssl / -S use SSL\n"); + printf(" --log-database database connection string for logging\n"); + printf(" --log-channel channel to log\n"); } int main (int argc, char **argv) @@ -48,6 +60,7 @@ .username = "spbot-dev", .realname = "SpBot (development version)", }; + const char *log_database = NULL, *log_channel = NULL; bool port_default = true; @@ -74,6 +87,14 @@ portname = DEFAULT_PORT_SSL; break; + + case OPT_LOG_DATABASE: + log_database = optarg; + break; + + case OPT_LOG_CHANNEL: + log_channel = optarg; + break; case '?': usage(argv[0]); @@ -109,6 +130,12 @@ // create the irc connection state if (irc_conn_create(&conn, sock, &conn_config, &err)) FATAL_ERROR(&err, "irc_conn_create"); + + // logging? + if (log_database || log_channel) { + if ((ERROR_CODE(&err) = irc_log_init(ev_base, log_database, conn, log_channel))) + FATAL_ERROR(&err, "irc_log_init"); + } // run event loop if (event_base_dispatch(ev_base))