terom@57: #include "module.h" terom@57: #include "irc_chan.h" terom@57: #include "error.h" terom@23: #include "log.h" terom@23: terom@57: #include terom@55: #include terom@55: terom@57: #include terom@23: #include terom@23: terom@23: /** terom@68: * The irc_log module state. terom@23: */ terom@57: struct irc_log_ctx { terom@56: /** The nexus this module is loaded for */ terom@56: struct nexus *nexus; terom@56: terom@23: /** The database connection */ terom@23: struct evsql *db; terom@57: }; terom@23: terom@68: /** terom@68: * State required to use irc_cmd_handler with irc_chan.handlers. terom@68: */ terom@68: struct irc_log_chan { terom@68: /** The irc_log context */ terom@68: struct irc_log_ctx *ctx; terom@68: terom@68: /** The target channel */ terom@68: struct irc_chan *chan; terom@68: }; terom@68: terom@68: /** terom@68: * Our evsql result handler for irc_log_event INSERTs. terom@68: */ terom@67: static void irc_log_on_sql_result (struct evsql_result *res, void *arg) terom@67: { terom@67: struct irc_log_ctx *ctx = arg; terom@67: err_t err; terom@67: terom@67: (void) ctx; terom@67: terom@67: if ((err = evsql_result_check(res))) terom@67: log_error("irc_log_event: %s", evsql_result_error(res)); terom@67: terom@67: // ok terom@67: evsql_result_free(res); terom@67: } terom@67: terom@68: /** terom@68: * Log the event into our database. terom@68: * terom@68: * Any of source, target, message can be NULL to insert NULLs terom@68: */ terom@68: static err_t irc_log_event (struct irc_log_ctx *ctx, struct irc_chan *chan, const struct irc_nm *source, terom@67: const char *type, const char *target, const char *message) terom@67: { terom@67: struct evsql_query *query; terom@68: terom@68: // unpack the nick/user/hostname args, as source can be NULL terom@68: const char *nickname = source ? source->nickname : NULL; terom@68: const char *username = source ? source->username : NULL; terom@68: const char *hostname = source ? source->hostname : NULL; terom@67: terom@67: // our SQL query terom@67: static struct evsql_query_info sql = { terom@67: "INSERT INTO logs (" terom@67: " network, channel, nickname, username, hostname, type, target, message" terom@67: " ) VALUES (" terom@67: " $1::varchar, $2::varchar, $3::varchar, $4::varchar, $5::varchar, $6::varchar, $7::varchar, $8::varchar" terom@67: " )", { terom@67: EVSQL_TYPE(STRING), // network terom@67: EVSQL_TYPE(STRING), // channel terom@67: EVSQL_TYPE(STRING), // nickname terom@67: EVSQL_TYPE(STRING), // username terom@67: EVSQL_TYPE(STRING), // hostname terom@67: EVSQL_TYPE(STRING), // type terom@67: EVSQL_TYPE(STRING), // target terom@67: EVSQL_TYPE(STRING), // message terom@67: EVSQL_TYPE_END terom@67: } terom@67: }; terom@68: terom@68: // drop lines if not connected terom@68: if (ctx->db == NULL) { terom@68: log_warn("no database connected"); terom@68: terom@68: return ERR_MODULE_CONF; terom@68: } terom@67: terom@67: // run the INSERT terom@67: if ((query = evsql_query_exec(ctx->db, NULL, &sql, &irc_log_on_sql_result, ctx, terom@67: chan->net->info.network, irc_chan_name(chan), terom@68: nickname, username, hostname, terom@67: type, target, message terom@67: )) == NULL) { terom@67: // XXX: get evsql_query error info somehow... terom@67: log_error("evsql_query_exec failed for %s:%s - %s!%s@%s - %s -> %s - %s", terom@67: chan->net->info.network, irc_chan_name(chan), terom@68: nickname, username, hostname, terom@67: type, target, message terom@67: ); terom@67: terom@68: return ERR_EVSQL_QUERY_EXEC; terom@67: } terom@68: terom@68: // ok terom@68: log_debug("%s:%s - %s!%s@%s - %s -> %s - %s", terom@68: chan->net->info.network, irc_chan_name(chan), terom@68: nickname, username, hostname, terom@68: type, target, message terom@68: ); terom@68: terom@68: return SUCCESS; terom@67: } terom@67: terom@68: /** terom@68: * Parse the prefix into a nickmask and pass on to irc_log_event terom@68: */ terom@68: static err_t irc_log_event_prefix (struct irc_log_ctx *ctx, struct irc_chan *chan, const char *prefix, terom@68: const char *type, const char *target, const char *message) terom@23: { terom@68: char prefix_buf[IRC_PREFIX_MAX]; terom@68: struct irc_nm nm; terom@68: err_t err; terom@23: terom@68: // parse nickmask terom@68: if ((err = irc_nm_parse(&nm, prefix_buf, prefix))) terom@68: return err; terom@68: terom@68: // log terom@68: return irc_log_event(ctx, chan, &nm, type, target, message); terom@23: } terom@23: terom@68: /** terom@68: * Log a simple channel event of the form: terom@68: * terom@68: * :nm [] terom@68: */ terom@68: static void irc_log_on_chan_generic (const struct irc_line *line, void *arg) terom@68: { terom@68: struct irc_log_chan *chan_ctx = arg; terom@68: terom@68: const char *msg = line->args[1]; terom@68: terom@68: irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, NULL, msg); terom@68: } terom@68: terom@68: /** terom@68: * Log a MODE event on a channel terom@68: * terom@68: * :nm MODE [ [...]] terom@68: * terom@68: * This conacts all the modeargs together, and logs that as the message terom@68: */ terom@68: static void irc_log_on_chan_MODE (const struct irc_line *line, void *arg) terom@68: { terom@68: struct irc_log_chan *chan_ctx = arg; terom@68: char message[512], *ptr = message; terom@68: const char *cmdarg; terom@68: terom@68: // iterate over each arg terom@68: FOREACH_IRC_LINE_ARGS(line, cmdarg) { terom@68: // separate with spaces terom@68: if (cmdarg > line->args[0]) terom@68: *ptr++ = ' '; terom@68: terom@68: // append terom@68: ptr += snprintf(ptr, (message + 512 - ptr), "%s", cmdarg); terom@68: terom@68: // buffer full? terom@68: if (ptr > message + 512) terom@68: break; terom@68: } terom@68: terom@68: // log terom@68: irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, NULL, message); terom@68: } terom@68: terom@68: /** terom@68: * Log a KICK event on a channel terom@68: * terom@68: * :nm KICK [] terom@68: */ terom@68: static void irc_log_on_chan_KICK (const struct irc_line *line, void *arg) terom@68: { terom@68: struct irc_log_chan *chan_ctx = arg; terom@68: terom@68: const char *target = line->args[1]; terom@68: const char *msg = line->args[2]; terom@68: terom@68: irc_log_event_prefix(chan_ctx->ctx, chan_ctx->chan, line->prefix, line->command, target, msg); terom@68: } terom@68: terom@68: /** terom@68: * Our low-level channel-specific message handlers for logged channels. terom@68: * terom@68: * Note that these get a `struct irc_log_chan*` as an argument. terom@68: */ terom@68: static struct irc_cmd_handler _chan_cmd_handlers[] = { terom@68: { "JOIN", &irc_log_on_chan_generic }, terom@68: { "PART", &irc_log_on_chan_generic }, terom@68: { "MODE", &irc_log_on_chan_MODE }, terom@68: { "TOPIC", &irc_log_on_chan_generic }, terom@68: { "KICK", &irc_log_on_chan_KICK }, terom@68: { "PRIVMSG", &irc_log_on_chan_generic }, terom@68: { "NOTICE", &irc_log_on_chan_generic }, terom@68: { NULL, NULL } terom@68: }; terom@68: terom@68: /** terom@68: * Our high-level channel-specific callbacks for logged channels. terom@68: * terom@68: * Note that these get a `struct irc_log_chan*` as an argument. terom@68: */ terom@38: static struct irc_chan_callbacks _chan_callbacks = { terom@68: .on_self_join = NULL, terom@68: .on_msg = NULL, terom@23: }; terom@23: terom@69: /** terom@69: * Release resources associated with the given irc_log_chan without doing any clean shutdown stuff terom@69: */ terom@69: static void irc_log_chan_destroy (struct irc_log_chan *chan_ctx) terom@69: { terom@69: // remove any handlers/callbacks terom@69: irc_cmd_remove(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx); terom@69: irc_chan_remove_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx); terom@69: terom@69: // free ourselves terom@69: free(chan_ctx); terom@69: } terom@69: terom@69: /** terom@69: * Begin logging the given channel terom@69: */ terom@69: static err_t irc_log_chan (struct irc_log_ctx *ctx, struct irc_chan *chan, struct error_info *err) terom@69: { terom@69: struct irc_log_chan *chan_ctx; terom@69: terom@69: // alloc terom@69: if ((chan_ctx = calloc(1, sizeof(*chan_ctx))) == NULL) terom@69: return SET_ERROR(err, ERR_CALLOC); terom@69: terom@69: // store terom@69: chan_ctx->ctx = ctx; terom@69: chan_ctx->chan = chan; terom@69: terom@69: // add low-level handlers terom@69: if ((ERROR_CODE(err) = irc_cmd_add(&chan_ctx->chan->handlers, _chan_cmd_handlers, chan_ctx))) terom@69: goto error; terom@69: terom@69: // add channel callbacks terom@69: if ((ERROR_CODE(err) = irc_chan_add_callbacks(chan_ctx->chan, &_chan_callbacks, chan_ctx))) terom@69: goto error; terom@69: terom@69: // log an OPEN message terom@69: // XXX: move this to when we first JOIN the channel terom@69: if ((ERROR_CODE(err) = irc_log_event(ctx, chan_ctx->chan, NULL, "OPEN", NULL, NULL))) terom@69: goto error; terom@69: terom@69: // ok terom@69: log_info("logging channel %s:%s", chan_ctx->chan->net->info.network, irc_chan_name(chan_ctx->chan)); terom@69: terom@69: return SUCCESS; terom@69: terom@69: error: terom@69: // cleanup terom@69: irc_log_chan_destroy(chan_ctx); terom@69: terom@69: return ERROR_CODE(err); terom@69: } terom@69: terom@69: /** terom@69: * Allocate and initialize an irc_log_ctx. This doesn't do very much, the real magic happens in irc_log_conf. terom@69: */ terom@57: static err_t irc_log_init (struct nexus *nexus, void **ctx_ptr, struct error_info *err) terom@23: { terom@55: struct irc_log_ctx *ctx; terom@57: terom@57: // allocate terom@57: if ((ctx = calloc(1, sizeof(*ctx))) == NULL) terom@57: return SET_ERROR(err, ERR_CALLOC); terom@55: terom@56: // store terom@57: ctx->nexus = nexus; terom@56: terom@65: log_info("module initialized"); terom@65: terom@55: // ok terom@57: *ctx_ptr = ctx; terom@57: terom@69: return SUCCESS; terom@55: } terom@55: terom@68: /** terom@68: * Process the irc_log.db_info config option. terom@68: * terom@68: * Creates a new evsql handle. terom@68: * terom@68: * Fails if ctx->db is already set. terom@68: */ terom@68: static err_t irc_log_conf_db_info (struct irc_log_ctx *ctx, char *value, struct error_info *err) terom@68: { terom@68: log_info("connect to database: %s", value); terom@68: terom@68: // already connected? terom@68: if (ctx->db) terom@68: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.db_info already set"); terom@68: terom@68: // create a new evsql handle terom@68: if ((ctx->db = evsql_new_pq(ctx->nexus->ev_base, value, NULL, NULL)) == NULL) terom@68: return SET_ERROR(err, ERR_EVSQL_NEW_PQ); terom@68: terom@68: // ok terom@68: return SUCCESS; terom@68: } terom@68: terom@68: /** terom@68: * Process the irc_log.channel config option. terom@68: * terom@68: * Creates a new irc_log_chan context, looks up the channel, adds our command handlers, and logs an OPEN event. terom@68: * terom@68: * Fails if the value is invalid, we don't have a database connected, the channel doesn't exist, adding our command terom@68: * handlers/callbacks fails, or sending the initial INSERT-OPEN query fails. terom@68: */ terom@68: static err_t irc_log_conf_channel (struct irc_log_ctx *ctx, char *value, struct error_info *err) terom@68: { terom@68: const char *network, *channel; terom@69: struct irc_chan *chan; terom@68: terom@68: // parse required args terom@68: if ((network = strsep(&value, ":")) == NULL) terom@68: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid for irc_log.channel"); terom@68: terom@68: if ((channel = strsep(&value, ":")) == NULL) terom@68: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "invalid for irc_log.channel"); terom@68: terom@68: // extraneous stuff? terom@68: if (value) terom@68: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "trailing data for irc_log.channel"); terom@68: terom@68: // have a db configured? terom@68: if (!ctx->db) terom@68: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "irc_log.channel used without any irc_log.db_info"); terom@68: terom@68: // get the channel? terom@69: if ((chan = irc_client_get_chan(ctx->nexus->client, network, channel)) == NULL) terom@69: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown channel name"); terom@68: terom@69: // begin logging it terom@69: if (irc_log_chan(ctx, chan, err)) terom@69: return ERROR_CODE(err); terom@68: terom@68: // ok terom@68: return SUCCESS; terom@68: } terom@68: terom@68: /** terom@68: * Set a config option terom@68: */ terom@57: static err_t irc_log_conf (void *mod_ctx, const char *name, char *value, struct error_info *err) terom@55: { terom@56: struct irc_log_ctx *ctx = mod_ctx; terom@67: terom@55: if (strcmp(name, "db_info") == 0) { terom@68: return irc_log_conf_db_info(ctx, value, err); terom@55: terom@67: } else if (strcmp(name, "channel") == 0) { terom@68: return irc_log_conf_channel(ctx, value, err); terom@55: terom@55: } else { terom@66: RETURN_SET_ERROR_STR(err, ERR_MODULE_CONF, "unknown configuration option"); terom@23: } terom@23: terom@23: // ok terom@23: return SUCCESS; terom@23: } terom@23: terom@57: /** terom@57: * The module function table terom@57: */ terom@57: struct module_funcs irc_log_funcs = { terom@57: .init = &irc_log_init, terom@57: .conf = &irc_log_conf, terom@57: }; terom@68: