/**
* $Id: irc.c 51 2008-01-10 00:19:39Z mbroeker $
* $URL: http://localhost/svn/c/mcbot/trunk/src/irc.c $
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <pwd.h>
#include <compat.h>
#include <irc.h>
#include "common.h"
enum command_map {
NOTICE, MODE, JOIN, PART, TOPIC, PING, ENOMEM, ERROR, VERSION, PRIVMSG, QUIT, NICK, KICK
};
#define VERSION_STRING "MCBOT on GNU/LINUX"
const char *IRC_Commands[] = {
"NOTICE", "MODE", "JOIN", "PART",
"TOPIC", "PING", "ENOMEM", "ERROR",
"VERSION", "PRIVMSG", "QUIT", "NICK",
"KICK", NULL
};
FILE *irc_connect (char *server, char *port)
{
struct addrinfo hints;
struct addrinfo *result, *rp;
int csocket = -1;
FILE *stream;
memset (&hints, 0, sizeof (struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = 0;
hints.ai_protocol = IPPROTO_TCP;
if (getaddrinfo (server, port, &hints, &result) != 0) {
perror ("getaddrinfo");
return NULL;
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
if ((csocket = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1)
continue;
if (connect (csocket, rp->ai_addr, rp->ai_addrlen) != -1) {
fprintf (stderr, "Connected via %s\n", (rp->ai_family == AF_INET6) ? "IPv6" : "IPv4");
break;
}
close (csocket);
csocket = -1;
}
if (result != NULL)
freeaddrinfo (result);
if (csocket == -1) {
perror ("Cannot connect");
return NULL;
}
/*
* rw mode,but many seek errors ...
*/
#ifdef NETBSD
/*
* BEGIN OF STREAM
*/
stream = fdopen (csocket, "r+");
#else
/*
* END OF STREAM
*/
stream = fdopen (csocket, "a+");
#endif
return stream;
}
int irc_login (FILE * stream, char *server, char *nick, char *password)
{
MSG message = { NULL, 0, 0, 0, 0, 0, 0 };
char msg[DEFAULT_BUF_SIZE];
char *user;
struct passwd *pwd;
if ((user = getenv ("USER")) == NULL)
return IRC_GENERAL_ERROR;
if ((pwd = getpwnam (user)) == NULL)
return IRC_GENERAL_ERROR;
if (stream == NULL)
return IRC_GENERAL_ERROR;
else
message.stream = stream;
fprintf (stream, "NICK %s\r\n", nick);
fprintf (stream, "USER %s 0 %s %s\r\n", user, server, pwd->pw_gecos);
if (password != NULL)
fprintf (stream, "PRIVMSG NICKSERV :IDENTIFY %s\r\n", password);
for (;;) {
if (fgets (msg, sizeof (msg), stream) == NULL)
break;
if ((user = irc_parsemessage (msg, &message)) != NULL)
printf ("%10s %s\n", user, message.line);
if (strstr (msg, "VERSION") != NULL) {
printf ("%10s %s", "VERSION", msg);
printf ("%10s %s %s :%s\n", "WRITE", message.command, message.user, VERSION_STRING);
fprintf (stream, "VERSION :%s\r\n", VERSION_STRING);
}
if (strstr (msg, ":Password accepted") != NULL) {
break;
}
if (strstr (msg, ":You are now identified for") != NULL) {
break;
}
if (strstr (msg, ":You are now logged in.") != NULL) {
break;
}
if (strstr (msg, ":Password Incorrect") != NULL) {
return IRC_LOGIN_ERROR;
}
if (strstr (msg, "ERROR :Closing Link") != NULL) {
return IRC_GENERAL_ERROR;
}
if (strstr (msg, "is not a registered") != NULL) {
return IRC_LOGIN_ERROR;
}
if (strstr (msg, ":Nickname is already in use") != NULL) {
return IRC_LOGIN_ERROR;
}
if (password == NULL)
break;
}
sleep (2);
return 0;
}
static char *irc_getmessage (const char *line, MSG * message)
{
static char *garbage_collector = NULL;
char *theLine;
char *token;
char *ptr;
if (garbage_collector != NULL) {
free (garbage_collector);
garbage_collector = NULL;
}
if ((theLine = compat_strdup (line)) == NULL)
return "ENOMEM";
else
garbage_collector = theLine;
message->user = message->domain = NULL;
message->command = NULL;
message->channel = message->line = NULL;
token = strtok (theLine, " ");
if (*token != ':') { /* SERVER MESSAGES */
if ((ptr = strtok (NULL, "\r\n")) != NULL)
++ptr;
message->line = ptr;
return token;
}
message->user = ++token;
message->command = strtok (NULL, " ");
message->line = strtok (NULL, "\r\n");
return message->command;
}
/**
* Main prints ("%10s %s %s\n", MSG->command MSG->channel MSG->line).
* This functions makes sure, that there will be someting to print...
*/
char *irc_parsemessage (const char *line, MSG * message)
{
enum command_map map;
char *command;
char *ptr;
int value;
map = 0;
if ((command = irc_getmessage (line, message)) != NULL) {
while (IRC_Commands[map] != NULL) {
if (strcmp (IRC_Commands[map], command) == 0) {
switch (map) {
case NOTICE:
if (message->line == NULL) {
message->channel = "*";
message->line = "*";
} else {
message->channel = strtok (message->line, " ");
if ((message->line = strtok (NULL, "\r\n")) != NULL)
++message->line;
}
return command;
case MODE:
message->channel = strtok (message->line, " ");
message->line = strtok (NULL, "\r\n");
return command;
case JOIN:
case PART:
if ((message->channel = strchr (message->line, ':')))
++message->channel;
message->line = message->user;
return command;
case TOPIC:
message->channel = strtok (message->line, " ");
if ((message->line = strtok (NULL, "\r\n")))
++message->line;
return command;
case PING:
#ifdef DEBUG
/*
* DONT NERVE WITH PING PONG MESSAGES
*/
printf ("%10s %s localhost\n", "PING", message->line);
#endif
fprintf (message->stream, "PONG :%s\r\n", message->line);
message->channel = "localhost";
#ifdef DEBUG
return "PONG";
#else
return NULL;
#endif
case ENOMEM:
return "ENOMEM";
case ERROR:
free (command);
return "ERROR";
case VERSION:
if ((ptr = strchr (message->user, ' ')))
*ptr = '\0';
return command;
case PRIVMSG:
if ((message->domain = strchr (message->user, '@')))
++message->domain;
if ((ptr = strchr (message->user, '!')))
*ptr = '\0';
message->channel = strtok (message->line, " ");
safe_strncpy (message->current_channel, message->channel, sizeof (message->current_channel));
message->line = strtok (NULL, "\r\n");
message->line++;
printf ("%10s %s %s :%s\n", "READ", message->command, message->channel, message->line);
return NULL;
case QUIT:
message->channel = message->user;
return command;
case NICK:
message->channel = message->user;
return command;
case KICK:
message->channel = message->user;
return command;
}
}
map++;
}
if ((value = atoi (command)) != 0) {
switch (value) {
case 1: /* CONNECTION INFOS */
case 2:
case 3:
case 4:
case 5:
case 250:
case 251: /* NUMBER OF USERS */
case 252: /* STAFF MEMBERS */
case 254: /* CHANNELS */
case 255: /* CLIENTS */
case 265: /* LOCAL USERS */
case 266: /* GLOBAL USERS */
/*
* prints as is in irc_login
*/
return command;
case 311:
case 312:
case 315: /* END OF WHO */
case 318:
message->channel = strtok (message->line, " ");
message->line = strtok (NULL, "\r\n");
return command;
case 319:
message->channel = strtok (message->line, " ");
message->line = strtok (NULL, "\r\n");
fprintf (message->stream, "PRIVMSG %s :%s\r\n", message->current_channel, message->line);
return command;
case 320:
case 328: /* INFORMATION */
case 332: /* TOPIC OF CHANNEL */
case 333: /* NAMES IN CHANNEL */
case 351: /* SVERSION */
case 352: /* WHO LIST */
case 353:
case 365:
case 366: /* END OF NAMES */
message->channel = strtok (message->line, " ");
message->line = strtok (NULL, "\r\n");
return command;
case 372: /* MOTD MESSAGES */
case 375:
case 376: /* END OF MOTD */
return command;
case 401: /* NO SUCH NICK/CHANNEL */
case 402: /* NO SUCH USER */
case 403: /* THAT CHANNEL DOESN'T EXIST */
case 412: /* NO TEXT TO SEND */
case 441: /* THEY AREN'T ON THIS CHANNEL */
message->channel = strtok (message->line, " ");
message->line = strtok (NULL, "\r\n");
return command;
case 433: /* NICK ALREADY IN USE */
case 451: /* REGISTER FIRST */
return command;
case 474:
case 475:
case 476:
case 477: /* HOW TO GET HELP */
case 486: /* MUST BE REGISTERED TO SEND PRIVATE MESSAGES */
case 482:
case 901: /* notify or some crap */
message->channel = strtok (message->line, " ");
message->line = strtok (NULL, "\r\n");
fprintf (message->stream, "PRIVMSG %s :%s\r\n", message->current_channel, message->line);
return command;
default:
printf ("DEBUG %s", line);
printf ("Unknown Command Value: %d\n", value);
}
}
printf ("DEBUG %s", line);
printf ("Unknown Command: %s\n", command);
return command;
}
printf ("NOT PARSEABLE %s", line);
return NULL;
}