src/irc.c
author Markus Bröker <mbroeker@largo.dyndns.tv>
Sat, 30 May 2009 21:56:21 +0200
changeset 38 c2f62d1b8d76
parent 37 85891f91096c
child 41 7260f61566b5
permissions -rw-r--r--
enumerated types for parse.c The irc section gets an enumerated type for constant values. This makes the code more readable and less error prone. committer: Markus Bröker <mbroeker@largo.homelinux.org>

/**
 *  $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 <netinet/in.h>
#include <arpa/inet.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, unsigned int port)
{
    struct hostent *he;
    char *ip;
    struct sockaddr_in ca;
    int csocket;
    FILE *stream;

    he = gethostbyname (server);
    if (he == NULL) {
        perror ("GETHOSTBYNAME");
        return NULL;
    }

    if ((ip = inet_ntoa (*((struct in_addr *)he->h_addr_list[0]))) == NULL) {
        perror ("INET_NTOA");
        return NULL;
    } else
        printf ("IP: %s\n", ip);

    ca.sin_family = AF_INET;
    ca.sin_addr.s_addr = inet_addr (ip);
    ca.sin_port = htons (port);

    csocket = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (connect (csocket, (struct sockaddr *)&ca, (socklen_t) sizeof (ca)) == -1) {
        perror ("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 (;;) {
        fgets (msg, sizeof (msg), stream);
        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 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 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->email = 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->email = strchr (message->user, '=')))
                        ++message->email;
                    if ((ptr = strchr (message->user, '!')))
                        *ptr = '\0';

                    message->channel = strtok (message->line, " ");
                    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 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;
}