ref: 271c3b1647562df4a689e0d2ee5feb4c8d9a3edd
dir: /irc.c/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include <memory.h>
#include <stdarg.h>
#include "irc.h"
#include "utils.h"
#include "net.h"
void die(const char* msg) {
perror(msg);
exit(EXIT_FAILURE);
}
int irc_init_session(irc_session_t* s, const char* serv, const char* portno, const char* nick, const char* pass, irc_event_handler_set_t* es) {
memset(s, 0, sizeof(irc_session_t));
memset(es, 0, sizeof(irc_event_handler_set_t));
if (serv == NULL || portno == NULL || nick == NULL)
return -1;
s->server = strdup(serv);
s->portno = strdup(portno);
s->nick = strdup(nick);
s->password = pass? strdup(pass) : NULL;
s->buffer = (char *)malloc(IRC_MAX_BUFFER);
s->pos = 0;
s->event_handlers = *es;
assert(s->buffer);
s->connected = 0;
return 0;
}
ssize_t irc_send_raw(irc_session_t* s, const char* format, ...) {
char buffer[IRC_MAX_BUFFER];
va_list ap;
va_start(ap, format);
int n = vsnprintf(buffer, IRC_MAX_BUFFER, format, ap);
/* TODO: */
va_end(ap);
int nwrote = irc_socket_send(s->sockfd, buffer, n);
#ifdef IRC_DEBUG_MODE
udebug("irc_send_raw(): wrote %d bytes into s->sockfd.", nwrote);
#endif
return nwrote;
}
int irc_connect(irc_session_t* s) {
#ifdef IRC_DEBUG_MODE
udebug("irc_connect(): connecting to %s:%s", s->server, s->portno);
#endif
/* RFC 1459:
* The recommended order for a client to register is as follows:
* 1. Pass message
* 2. Nick message
* 3. User message */
s->sockfd = irc_socket_create(s->server, s->portno);
if (s->sockfd == -1) {
return -1; // FIXME: proper error messages
}
#ifdef IRC_DEBUG_MODE
udebug("irc_connect() registering as NICK : %s USER: %s PASS: %s", s->nick, s->nick, s->password);
#endif
if (s->password)
irc_send_raw(s, "PASS %s \r\n", s->password);
irc_send_raw(s, "NICK %s \r\n", s->nick);
irc_send_raw(s, "USER %s * * : AAAA\r\n", s->nick);
#ifdef IRC_DEBUG_MODE
udebug("irc_connect(), sucessfully connected.");
#endif
/* some irc servers require that we ping them first */
irc_send_raw(s, "PING : random\r\n");
return 0;
}
char* irc_str_skip_to(char *str, char c) {
while (*str) {
if (*str == c)
return str;
str++;
}
return NULL;
}
// returns the first cr(lf) in the string
int irc_find_crlf(const char* str, int size) {
for (int i = 0; i < size; i++) {
if (str[i] == '\r' && str[i + 1] == '\n')
return i + 1;
}
return -1;
}
// buffer must be null-terminated, complete, raw irc message(excluding lf)
int irc_parse_msg(char* buffer, irc_msg_t* msg, int lfpos) {
/*
* From RFC 1459:
* <message> ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
* <prefix> ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
* <command> ::= <letter> { <letter> } | <number> <number> <number>
* <SPACE> ::= ' ' { ' ' }
* <params> ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
* <middle> ::= <Any *non-empty* sequence of octets not including SPACE
* or NUL or CR or LF, the first of which may not be ':'>
* <trailing> ::= <Any, possibly *empty*, sequence of octets not including
* NUL or CR or LF>
*/
memset(msg, 0, sizeof(irc_msg_t));
char *p = buffer;
char *prefix;
if (*p == ':') {
char *prefix_end = irc_str_skip_to(p, ' ');
if (!prefix_end) {
#ifdef IRC_DEBUG_MODE
uerror("invalid message, parsing the prefix");
#endif
goto invalid_msg;
}
*prefix_end = '\0';
prefix = p + 1;
msg->prefix_name = prefix;
char *n = irc_str_skip_to(prefix, '!');
if (n < prefix_end && n != NULL) {
// there's ['!' <user>] in the prefix.
*n = '\0';
msg->prefix_user = n + 1;
}
n = irc_str_skip_to(prefix, '@');
if (n < prefix_end && n != NULL) {
*n = '\0';
msg->prefix_host = n + 1;
}
*prefix_end = '\0';
p = prefix_end + 1;
#ifdef IRC_DEBUG_MODE
udebug("msg->prefix_name = %s, msg->prefix_user = %s, msg->prefix_host = %s", msg->prefix_name, msg->prefix_user, msg->prefix_host);
#endif
}
char *params_start = irc_str_skip_to(p, ' ');
if (!params_start) {
#ifdef IRC_DEBUG_MODE
uerror("we don't have params_start.");
#endif
goto invalid_msg;
}
*params_start = '\0';
msg->cmd = p;
p = params_start + 1;
char *params_end = irc_str_skip_to(p, ':');
if (!params_end) {
#ifdef IRC_DEBUG_MODE
uwarn("we don't have trailing_start");
#endif
params_end = buffer + lfpos; // apparently some servers don't have <trailing> with every msg
}
char *s = p;
int parameter_index = 0;
while (p != params_end && parameter_index < IRC_MAX_PARAMS - 1) {
if (*p == ' ') {
*p = '\0';
msg->parameters[parameter_index++] = s;
s = p + 1;
}
p++;
}
if(params_end != buffer + lfpos) { // if we had ':'
msg->parameters[parameter_index++] = params_end + 1; // it's already '\0' at the end
} else { // if we didn't have ':'
msg->parameters[parameter_index++] = s;
}
msg->parameters_size = parameter_index;
#ifdef IRC_DEBUG_MODE
udebug("irc_parse_msg(); msg->cmd='%s'", msg->cmd);
if (msg->parameters_size) {
udebug("irc_parse_msg(); msg->params are:");
for (int i = 0; i < msg->parameters_size; i++) {
printf(" [%d]: '%s'\n", i, msg->parameters[i]);
}
}
#endif
return 0;
invalid_msg:
#ifdef IRC_DEBUG_MODE
uerror("invalid message(%s)", buffer);
#endif
return -1;
}
int irc_process_msg(irc_session_t *s, irc_msg_t *m) { /* call the right event handler, this is where you can handle more of the irc protocol */
int32_t cmd = WORDL(m->cmd[0], m->cmd[1], m->cmd[2], m->cmd[3]);
int ret = 0;
/* FIXME: check m->parameters_size for validity of messages, if it doesnt have a sufficent number of
* arguemtns, don't pass it to the handlers */
switch(cmd) {
case WORDL('P', 'I', 'N', 'G'):
/* apparently some IRC servers require PONG :<param> and some require PONG <param>,
* and some require both! */
if (s->event_handlers.ping_handler)
ret = s->event_handlers.ping_handler(s, m);
if (m->parameters_size > 1)
irc_send_raw(s, "PONG %s :%s\r\n", m->parameters[0], m->parameters[1]);
else
irc_send_raw(s, "PONG :%s\r\n", m->parameters[0]);
break;
case WORDL('P', 'R', 'I', 'V'):
if (s->event_handlers.privmsg_handler)
ret = s->event_handlers.privmsg_handler(s, m);
break;
case WORDL('N', 'I', 'C', 'K'):
if (s->event_handlers.nick_handler)
ret = s->event_handlers.nick_handler(s, m);
break;
case WORDL('I', 'N', 'V', 'I'):
if (s->event_handlers.invite_handler)
ret = s->event_handlers.invite_handler(s, m);
break;
case WORDL('J', 'O', 'I', 'N'):
if (s->event_handlers.join_handler)
ret = s->event_handlers.join_handler(s, m);
break;
}
return ret;
}
int irc_handle_incoming_data(irc_session_t* s, int lfpos) {
irc_msg_t msg;
s->buffer[lfpos - 1] = '\0';
if (irc_parse_msg(s->buffer, &msg, lfpos) == -1) {
#ifdef IRC_DEBUG_MODE
uerror("irc_handle_incoming_adta(): invalid server command. \n");
#endif
return -1;
}
int r = irc_process_msg(s, &msg); // call the right callbacks.
if (r != 0) {
#ifdef IRC_DEBUG_MODE
uerror("irc_process_msg() wasn't sucessfull, ignoring the server message.");
#endif
return -1;
}
return 0;
}
void irc_main_loop(irc_session_t* s) {
while (1) {
#ifdef IRC_DEBUG_MODE
udebug("irc_main_loop();");
#endif
int nread = irc_socket_recv(s->sockfd, s->buffer + s->pos, IRC_MAX_BUFFER - s->pos);
if (nread <= 0) {
#ifdef IRC_DEBUG_MODE
uerror("irc_socket_recv() problem in irc_main_loop(), exiting for now.\n");
// FIXME proper error messages
#endif
exit(EXIT_FAILURE);
}
s->pos += nread;
int lfpos;
while ((lfpos = irc_find_crlf(s->buffer,s->pos)) != -1) {
irc_handle_incoming_data(s, lfpos);
memmove(s->buffer, s->buffer + lfpos + 1, s->pos - lfpos);
s->pos = s->pos - (lfpos + 1);
}
}
}
void irc_send_join(irc_session_t* s, const char *channel) {
irc_send_raw(s, "JOIN %s\r\n", channel);
}
void irc_send_privmsg(irc_session_t* s, const char *guy, const char *msg) {
irc_send_raw(s, "PRIVMSG %s :%s\r\n", guy, msg);
}
void irc_send_nick(irc_session_t* s, const char *nick) {
irc_send_raw(s, "NICK %s : \r\n", s, nick);
}
void irc_sendv_privmsg(irc_session_t* s, const char *guy, const char *format, ...) {
char buffer[IRC_MAX_MSG]; // FIXME
va_list ap;
va_start(ap, format);
vsnprintf(buffer, sizeof(buffer), format, ap);
irc_send_privmsg(s, guy, buffer);
va_end(ap);
}