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