#include "irc_conn.h"
#include "irc_cmd.h"
#include "log.h"
#include <stdlib.h>
#include <string.h>
/*
* "Welcome to the Internet Relay Network <nick>!<user>@<host>"
*/
static void on_RPL_WELCOME (struct irc_conn *conn, const struct irc_line *line, void *arg)
{
(void) line;
(void) arg;
// update state
conn->registered = true;
log_info("registered");
}
/*
* PING <server1> [ <server2> ]
*
* Send a 'PONG <server1>` reply right away.
*/
static void on_PING (struct irc_conn *conn, const struct irc_line *line, void *arg)
{
(void) arg;
// just reply
irc_conn_PONG(conn, line->args[0]);
}
/*
* Our command 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;
// log
log_debug("%s", line_buf);
// parse
if ((err = irc_line_parse(&line, line_buf))) {
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);
// ...only one per chain
break;
}
}
}
}
err_t irc_conn_create (struct irc_conn **conn_ptr, struct sock_stream *sock, const struct irc_conn_config *config, struct error_info *err)
{
struct irc_conn *conn;
// alloc new state struct
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);
// send the initial messages
if (
irc_conn_NICK(conn, config->nickname)
|| irc_conn_USER(conn, config->username, config->realname)
)
return ERROR_CODE(err);
// ok
*conn_ptr = conn;
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];
err_t err;
int ret;
// format
if ((err = irc_line_build(line, line_buf)))
return err;
// log
log_debug("%s", line_buf);
// add CRLF
strcat(line_buf, "\r\n");
// send using line_proto
// XXX: ignore output-buffering
return (ret = line_proto_send(conn->lp, line_buf)) < 0 ? -ret : SUCCESS;
}
err_t irc_conn_NICK (struct irc_conn *conn, const char *nickname)
{
// NICK <nickname>
struct irc_line line = {
NULL, "NICK", { nickname, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_USER (struct irc_conn *conn, const char *username, const char *realname)
{
// USER <user> <mode> <unused> <realname>
struct irc_line line = {
NULL, "USER", { username, "0", "*", realname, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_PONG (struct irc_conn *conn, const char *target)
{
// PONG <server> [ <server2> ]
// XXX: params are actually the wrong way around now, but nobody cares
struct irc_line line = {
NULL, "PONG", { target, NULL }
};
return irc_conn_send(conn, &line);
}
err_t irc_conn_JOIN (struct irc_conn *conn, const char *channel)
{
// JOIN ( <channel> *( "," <channel> ) [ <key> *( "," <key> ) ] ) / "0"
struct irc_line line = {
NULL, "JOIN", { channel, NULL }
};
return irc_conn_send(conn, &line);
}