terom@25: #include "irc_net.h" terom@153: #include "irc_net_internal.h" terom@25: #include "log.h" terom@25: terom@25: #include terom@26: #include terom@74: #include terom@153: #include terom@25: terom@72: const char* irc_net_name (struct irc_net *net) terom@72: { terom@138: return net ? net->info.network : NULL; terom@72: } terom@72: terom@46: /** terom@78: * Do a lookup for an irc_user with the given nickname, and return it it found, else NULL. terom@78: * terom@78: * This does not refcount anything. terom@78: */ terom@78: static struct irc_user* irc_net_lookup_user (struct irc_net *net, const char *nickname) terom@78: { terom@78: struct irc_user *user = NULL; terom@78: terom@78: // look for it... terom@78: LIST_FOREACH(user, &net->users, net_users) { terom@78: if (irc_cmp_nick(nickname, user->nickname) == 0) terom@78: // found existing terom@78: return user; terom@78: } terom@78: terom@78: // not found terom@78: return NULL; terom@78: } terom@78: terom@78: /** terom@46: * Our irc_conn has registered. Join our channels terom@46: */ terom@27: static void irc_net_conn_registered (struct irc_conn *conn, void *arg) terom@27: { terom@27: struct irc_net *net = arg; terom@27: struct irc_chan *chan = NULL; terom@46: struct error_info err; terom@27: terom@27: (void) conn; terom@27: terom@27: // join our channels terom@86: TAILQ_FOREACH(chan, &net->channels, net_channels) { terom@153: if ((ERROR_CODE(&err) = irc_chan_join(chan))) { terom@46: // XXX: this should be some kind of irc_chan_error instead terom@153: terom@153: } terom@27: } terom@27: } terom@27: terom@46: /** terom@46: * Our irc_conn has spontaneously failed, destroy it terom@46: */ terom@45: static void irc_net_conn_error (struct irc_conn *conn, struct error_info *err, void *arg) terom@45: { terom@45: struct irc_net *net = arg; terom@45: terom@46: (void) conn; terom@46: terom@153: // log an error terom@153: log_err_info(err, "irc_conn failed"); terom@153: terom@153: // tear down state terom@154: irc_net_disconnect(net); terom@153: terom@153: // reconnect, either right away, or at the five-minute interval terom@153: if (irc_net_connect(net, (time(NULL) - net->connected_ts > IRC_NET_RECONNECT_INTERVAL), err)) terom@153: log_err_info(err, "unable to reconnect"); terom@45: } terom@45: terom@27: /** terom@48: * Our irc_conn has quit succesfully terom@48: */ terom@48: static void irc_net_conn_quit (struct irc_conn *conn, void *arg) terom@48: { terom@48: struct irc_net *net = arg; terom@154: terom@154: (void) conn; terom@48: terom@48: // clean up the conn terom@154: irc_net_disconnect(net); terom@48: terom@48: // XXX: notify user terom@48: } terom@48: terom@48: /** terom@27: * Our irc_conn_callbacks list terom@27: */ terom@153: struct irc_conn_callbacks irc_net_conn_callbacks = { terom@27: .on_registered = &irc_net_conn_registered, terom@45: .on_error = &irc_net_conn_error, terom@48: .on_quit = &irc_net_conn_quit, terom@27: }; terom@27: terom@37: /** terom@38: * Propagate the command to the appropriate irc_chan based on the given name terom@38: */ terom@38: static void irc_net_propagate_chan (struct irc_net *net, const struct irc_line *line, const char *channel) terom@37: { terom@37: struct irc_chan *chan; terom@37: terom@37: // look up channel terom@38: if ((chan = irc_net_get_chan(net, channel)) == NULL) { terom@38: log_warn("unknown channel: %s", channel); terom@37: return; terom@37: } terom@37: terom@37: // propagate terom@37: irc_cmd_invoke(&chan->handlers, line); terom@37: } terom@37: terom@37: /** terom@38: * Propagate line to irc_chan based on args[0] terom@38: */ terom@38: static void irc_net_on_chan0 (const struct irc_line *line, void *arg) terom@38: { terom@38: struct irc_net *net = arg; terom@38: terom@38: irc_net_propagate_chan(net, line, line->args[0]); terom@38: } terom@38: terom@38: /** terom@72: * Propagate line to irc_chan based on args[1] terom@72: */ terom@72: static void irc_net_on_chan1 (const struct irc_line *line, void *arg) terom@72: { terom@72: struct irc_net *net = arg; terom@72: terom@72: irc_net_propagate_chan(net, line, line->args[1]); terom@72: } terom@72: terom@72: /** terom@72: * Propagate line to irc_chan based on args[2] terom@72: */ terom@72: static void irc_net_on_chan2 (const struct irc_line *line, void *arg) terom@72: { terom@72: struct irc_net *net = arg; terom@72: terom@72: irc_net_propagate_chan(net, line, line->args[2]); terom@72: } terom@72: terom@72: /** terom@78: * Propagate line to irc_chans based on the source nickname and chan_users. terom@78: */ terom@78: static void irc_net_on_chanuser (const struct irc_line *line, void *arg) terom@78: { terom@78: struct irc_net *net = arg; terom@78: struct irc_chan *chan; terom@78: terom@78: // scan each channel terom@86: TAILQ_FOREACH(chan, &net->channels, net_channels) { terom@78: if (irc_chan_get_user(chan, line->source->nickname)) { terom@78: // propagate terom@78: irc_cmd_invoke(&chan->handlers, line); terom@78: } terom@78: } terom@78: } terom@78: terom@78: /** terom@78: * :nm NICK terom@78: * terom@78: * Look for a relevant irc_user, and rename them. terom@78: * terom@78: * Ignores if the irc_user could not be found, then propagates to the irc_chan's that the user is on, and then renames terom@78: * the irc_user. terom@78: */ terom@78: static void irc_net_on_NICK (const struct irc_line *line, void *arg) terom@78: { terom@78: struct irc_net *net = arg; terom@78: struct irc_user *user; terom@78: err_t err; terom@78: terom@78: // ignore for unknown terom@78: if ((user = irc_net_lookup_user(net, line->source->nickname)) == NULL) terom@78: return; terom@78: terom@78: // propagate to channels terom@78: irc_net_on_chanuser(line, arg); terom@78: terom@78: // rename them terom@78: if ((err = irc_user_rename(user, line->args[0]))) terom@78: // XXX: what to do? terom@78: log_err(err, "irc_user_rename failed"); terom@78: } terom@78: terom@78: /** terom@68: * :nm (PRIVMSG|NOTICE) terom@38: * terom@38: * Either propagate to channel if found, otherwise handle as a privmsg terom@38: */ terom@68: static void irc_net_on_msg (const struct irc_line *line, void *arg) terom@38: { terom@38: struct irc_net *net = arg; terom@38: terom@38: // XXX: does two lookups terom@38: if (irc_net_get_chan(net, line->args[0])) { terom@38: // short-circuit to on_chan0 terom@38: irc_net_on_chan0(line, arg); terom@38: terom@38: } else { terom@38: // XXX: callbacks for privmsgs terom@38: terom@38: } terom@38: } terom@38: terom@38: /** terom@150: * :nm MODE [ [ ... ]] terom@150: * terom@150: * If target is ourselves, handle as a private umode, otherwise, propagate to channel terom@150: */ terom@150: static void irc_net_on_MODE (const struct irc_line *line, void *arg) terom@150: { terom@150: struct irc_net *net = arg; terom@150: terom@150: if (irc_conn_self(net->conn, line->args[0])) terom@150: // don't really care about our umode... terom@150: log_info("mode set: %s -> %s %s %s...", line->source->nickname, line->args[1], line->args[2], line->args[3]); terom@150: terom@150: else terom@150: // it's a channel mode terom@150: irc_net_propagate_chan(net, line, line->args[0]); terom@150: terom@150: } terom@150: terom@150: /** terom@37: * Our irc_cmd handler list terom@37: */ terom@153: struct irc_cmd_handler irc_net_cmd_handlers[] = { terom@78: // propagate certain messages to the appropriate channel terom@78: { "QUIT", &irc_net_on_chanuser }, terom@72: { "JOIN", &irc_net_on_chan0 }, terom@72: { "PART", &irc_net_on_chan0 }, terom@150: { "MODE", &irc_net_on_MODE }, terom@72: { "TOPIC", &irc_net_on_chan0 }, terom@72: { "KICK", &irc_net_on_chan0 }, terom@78: { IRC_RPL_NAMREPLY, &irc_net_on_chan2 }, terom@78: { IRC_RPL_ENDOFNAMES, &irc_net_on_chan1 }, terom@88: { "CTCP ACTION", &irc_net_on_chan0 }, terom@78: terom@78: // special-case handling for others terom@78: { "NICK", &irc_net_on_NICK }, terom@78: // Note: no need to handle QUIT, as the channels should take care of irc_net_put_user'ing it away. terom@72: { "PRIVMSG", &irc_net_on_msg }, terom@72: { "NOTICE", &irc_net_on_msg }, terom@78: terom@72: { NULL, NULL } terom@37: }; terom@37: terom@150: err_t irc_net_create (struct irc_net **net_ptr, const struct irc_net_info *info, struct error_info *err) terom@150: { terom@150: struct irc_net *net; terom@150: terom@150: // allocate terom@150: if ((net = calloc(1, sizeof(*net))) == NULL) terom@150: return SET_ERROR(err, ERR_CALLOC); terom@150: terom@150: // initialize terom@150: // XXX: we need to copy *info's fields terom@150: net->info = *info; terom@150: TAILQ_INIT(&net->channels); terom@150: LIST_INIT(&net->users); terom@153: terom@153: // init subsystems terom@153: if (irc_net_connect_init(net, err)) terom@153: goto error; terom@150: terom@150: // initial connect terom@153: if (irc_net_connect(net, true, err)) terom@150: goto error; terom@150: terom@150: // ok terom@25: *net_ptr = net; terom@25: terom@25: return SUCCESS; terom@25: terom@25: error: terom@47: // cleanup main terom@47: irc_net_destroy(net); terom@25: terom@25: return ERROR_CODE(err); terom@25: } terom@25: terom@47: void irc_net_destroy (struct irc_net *net) terom@47: { terom@140: struct irc_chan *chan_next = TAILQ_FIRST(&net->channels), *chan; terom@147: struct irc_user *user_next, *user; terom@47: terom@47: // our conn terom@47: if (net->conn) terom@47: irc_conn_destroy(net->conn); terom@47: terom@47: // our channels terom@140: while ((chan = chan_next)) { terom@140: chan_next = TAILQ_NEXT(chan, net_channels); terom@47: terom@47: irc_chan_destroy(chan); terom@47: } terom@47: terom@140: // our users terom@140: // XXX: this disregards external refs terom@147: for (user_next = LIST_FIRST(&net->users); (user = user_next); ) { terom@140: user_next = LIST_NEXT(user, net_users); terom@140: terom@140: irc_user_destroy(user); terom@140: } terom@140: terom@140: // our info terom@140: if (net->info.ssl_cred) terom@140: sock_ssl_client_cred_put(net->info.ssl_cred); terom@153: terom@153: // misc terom@153: irc_net_connect_destroy(net); terom@72: terom@47: // ourselves terom@47: free(net); terom@47: } terom@47: terom@63: err_t irc_net_add_chan (struct irc_net *net, struct irc_chan **chan_ptr, const struct irc_chan_info *info, struct error_info *err) terom@26: { terom@26: struct irc_chan *chan; terom@26: terom@26: // create the new irc_chan struct terom@63: if (irc_chan_create(&chan, net, info, err)) terom@63: return ERROR_CODE(err); terom@26: terom@26: // add to network list terom@86: TAILQ_INSERT_TAIL(&net->channels, chan, net_channels); terom@27: terom@27: // currently connected? terom@27: if (net->conn && net->conn->registered) { terom@27: // then join terom@63: if ((ERROR_CODE(err) = irc_chan_join(chan))) terom@63: return ERROR_CODE(err); terom@27: } terom@26: terom@63: // ok terom@63: if (chan_ptr) terom@63: *chan_ptr = chan; terom@63: terom@63: return SUCCESS; terom@26: } terom@26: terom@26: struct irc_chan* irc_net_get_chan (struct irc_net *net, const char *channel) terom@26: { terom@26: struct irc_chan *chan = NULL; terom@26: terom@26: // look for it... terom@86: TAILQ_FOREACH(chan, &net->channels, net_channels) { terom@26: if (strcasecmp(chan->info.channel, channel) == 0) terom@26: // found it terom@26: return chan; terom@26: } terom@26: terom@26: // no such channel terom@26: return NULL; terom@26: } terom@26: terom@72: err_t irc_net_get_user (struct irc_net *net, struct irc_user **user_ptr, const char *nickname) terom@72: { terom@78: struct irc_user *user; terom@72: err_t err; terom@72: terom@78: // lookup terom@78: if ((user = irc_net_lookup_user(net, nickname)) == NULL) { terom@74: // create a new user struct terom@74: if ((err = irc_user_create(&user, net, nickname))) terom@74: return err; terom@72: terom@74: // add to our list terom@74: LIST_INSERT_HEAD(&net->users, user, net_users); terom@74: terom@74: } else { terom@74: // check for refcount overflow terom@74: assert(user->refcount < SIZE_MAX); terom@74: } terom@74: terom@74: // 'get' it terom@74: user->refcount++; terom@72: terom@72: // ok terom@72: *user_ptr = user; terom@72: terom@72: return SUCCESS; terom@72: } terom@72: terom@74: void irc_net_put_user (struct irc_net *net, struct irc_user *user) terom@74: { terom@74: (void) net; terom@74: terom@74: assert(user->refcount > 0); terom@74: terom@74: // nothing if it's still in use elsewhere terom@74: if (--user->refcount) terom@74: return; terom@74: terom@74: // remove from list terom@74: LIST_REMOVE(user, net_users); terom@74: terom@74: // destroy terom@74: irc_user_destroy(user); terom@74: } terom@74: terom@48: err_t irc_net_quit (struct irc_net *net, const char *message) terom@48: { terom@48: if (!net->conn) terom@97: return ERR_IRC_NET_STATE; terom@48: terom@48: // send the QUIT message, and then we can wait for the reply terom@48: return irc_conn_QUIT(net->conn, message); terom@48: }