/*
 * libmsn
 *
 * Copyright (C) 1999, Shane P. Brady <shane@jxie.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/socket.h>
#include <gtk/gtk.h>
#include <errno.h>
#include "libmsn.h"
#include "msn_commands.h"
#include "parse_utils.h"
#include "chat_lists.h"
#include "md5.h"
#include "libproxy/libproxy.h"

/*
** main MSN connection and information
*/

MSN_Conn mainMSNConnection;
ERR_CALLBACK *MSN_ErrorOut;

/*
** list of connections generated by the msn_commands file
*/

GList *msn_connections = NULL;

extern char CommandString[NUM_OF_COMMANDS][COMMAND_LENGTH];
static int GetHostAddress(char *hostname, unsigned int *number);
static int MSN_Read(int fd, char *buffer, int nItems, int *count);

/*
** Name:    ConnectToServer
** Purpose: This function connects a socket to the server
** Input:   conn   - connection structure
**          host   - hostname of dispatch server
**          port   - port number of dispatch server
** Output:  0 on success, -1 on failure 
*/

int ConnectToServer(MSN_Conn *conn, char *host, int port)
{
    unsigned int        addy;
    struct sockaddr_in  sin;

    if (GetHostAddress(host, &addy) == -1) {
        return -1;
    } 
    sin.sin_addr.s_addr = addy;
    sin.sin_family      = AF_INET;
    sin.sin_port        = htons(port);

    conn->fd = socket(AF_INET, SOCK_STREAM, 0);

    if (conn->fd > -1) {
        if (proxy_connect(conn->fd, (struct sockaddr *)&sin, sizeof(sin)) > -1)
            return 0;
    }
    return -1;

}

/*
** Name:    SetProtocol
** Purpose: This function sets up the appropiate protocol, right now MSNP2
** Input:   conn       - connection structure
**          protocol   - protocol to use
** Output:  0 on success, -1 on failure
*/

int SetProtocol(MSN_Conn *conn, char *protocol)
{
    char   commandLine[LINE_LENGTH+1];    /* command that is sent */
    char   responseLine[LINE_LENGTH+1];   /* response             */
    time_t now = time((time_t *)NULL);    /* used for TrID        */
    int    length, nwritten, nread;
    int    status = 0;
    
    length = sprintf(commandLine, "%s %lu %s\r\n", CommandString[VER], now, 
                     protocol);
    nwritten = write(conn->fd, commandLine, length);
    if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(conn);
        return -1;
    }

    commandLine[length-2] = '\0';
    status = strcasecmp(responseLine, commandLine);

    return status;
}

/*
** Name:    GetServerPolicyInfo
** Purpose: This function queries the server for the policy.  Initially, this
**          is just the authentication package
** Input:   conn       - connection structure
**          sp         - security policy returned
** Output:  0 on success, -1 on failure
*/

int GetServerPolicyInfo(MSN_Conn *conn, char *sp)
{
    char   commandLine[LINE_LENGTH+1];    /* command that is sent */
    char   responseLine[LINE_LENGTH+1];   /* response             */
    char   **args = NULL;
    int    numOfArgs;
    time_t now = time((time_t *)NULL);    /* used for TrID        */
    int    length, nwritten, nread;
       
    length = sprintf(commandLine, "%s %lu\r\n", CommandString[INF], now);
    nwritten = write(conn->fd, commandLine, length);
    if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(conn);
        return -1;
    }
    commandLine[length-2] = '\0';

   
    ParseArguments(responseLine, " ", &args, &numOfArgs);
    strcpy(sp, args[2]);
    DestroyArguments(&args, numOfArgs);
    return 0;
}

/*
** Name:    AuthenticateUserMD5
** Purpose: This function authenticates a user and completes the login process
**          MD5 hashing is used
** Input:   conn       - connection structure
**          handle     - user's handle
**          passwd     - user's password
** Output:  0 on success, -1 on failure
*/

int AuthenticateUserMD5(MSN_Conn *conn, char *handle, char *passwd)
{
    char        **args;
    char        *newHandle;
    int         numOfArgs = 0;
    char        commandLine[LINE_LENGTH+1];    /* command that is sent */
    char        responseLine[LINE_LENGTH+1];   /* response             */
    char        hashResponse[65];
    time_t      now = time((time_t *)NULL);    /* used for TrID        */
    int         length, nwritten, nread, di, xfrStatus;
    md5_state_t state;
    md5_byte_t  digest[16];
    char        hash[33]; 

    AddHotmail(handle, &newHandle);
    responseLine[0] = '\0';
    length = sprintf(commandLine, "%s %lu MD5 I %s\r\n", 
             CommandString[USR], now, newHandle);
    nwritten = write(conn->fd, commandLine, length);
    commandLine[length-2] = '\0';
    if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(conn);
        free(newHandle);
        return -1;
    }

    ParseArguments(responseLine, " ", &args, &numOfArgs);

    if (strcasecmp(args[0], CommandString[XFR]) == 0) {
        xfrStatus = HandleXFR(conn, args, numOfArgs, 0);
        DestroyArguments(&args, numOfArgs);
        if (xfrStatus == -1) {
            free(newHandle);
            return -1;
        }
        responseLine[0] = '\0';
        length = sprintf(commandLine, "%s %lu MD5 I %s\r\n", 
                         CommandString[USR], now, newHandle);
        nwritten = write(conn->fd, commandLine, length);
        commandLine[length-2] = '\0';
        if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
            KillConnection(conn);
            free(newHandle);
            return -1;
        }
        ParseArguments(responseLine, " ", &args, &numOfArgs); 

    }

    if ((numOfArgs != 5) || (strcasecmp(args[0], "USR"))) {
        DestroyArguments(&args, numOfArgs);
        free(newHandle);
        return -1;
    }

    sprintf(hashResponse, "%s%s", args[4], passwd);
    md5_init(&state);
    md5_append(&state, (const md5_byte_t *)hashResponse, strlen(hashResponse));
    md5_finish(&state, digest);

    for (di = 0; di < 16; ++di) {
        if (di == 0)
            sprintf(hash, "%02x", digest[di]);
        else
            sprintf(hash, "%s%02x", hash, digest[di]);
    }
    responseLine[0] = '\0';
    hash[32] = '\0';
    sprintf(commandLine, "%s %lu MD5 S %s\r\n", CommandString[USR], now, hash);
    nwritten = write(conn->fd, commandLine, strlen(commandLine));
    commandLine[length-2] = '\0';
    if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(conn);
        free(newHandle);
        return -1;
    }
    DestroyArguments(&args, numOfArgs);
    
    ParseArguments(responseLine, " ", &args, &numOfArgs);
    if ((numOfArgs != 5) || (strcasecmp(args[0], "USR"))) {
        DestroyArguments(&args, numOfArgs);
        free(newHandle);
        return -1;
    }
    DestroyArguments(&args, numOfArgs);
    free(newHandle);
    return 0;
}

/*
** Name:    ChangeState
** Purpose: This function changes the state of the user
** Input:   conn       - connection structure
**          state      - new state
** Output:  0 on success, -1 on failure
*/

int ChangeState(MSN_Conn *conn, char *state)
{
    char   commandLine[LINE_LENGTH+1];    /* command that is sent */
    time_t now = time((time_t *)NULL);    /* used for TrID        */
    int    length, nwritten;

    length = sprintf(commandLine, "%s %lu %s\r\n", CommandString[CHG], now, 
                     state);
    nwritten = write(conn->fd, commandLine, length);
    return 0;
}

/*
** Name:    Synchronize
** Purpose: This function changes the state of the user
** Input:   conn      - connection structure
** Output:  0 on success, -1 on failure
*/

int Synchronize(MSN_Conn *conn)
{
    char   commandLine[LINE_LENGTH+1];    /* command that is sent */
    time_t now = time((time_t *)NULL);    /* used for TrID        */
    int    length, nwritten;

    length = sprintf(commandLine, "%s %lu 1\r\n", CommandString[SYN], now);
    nwritten = write(conn->fd, commandLine, length);
    return 0;
}

/*
** Name:    HandleRing
** Purpose: This function accepts a ring and establishes the connection
**          to the switchboard server
** Input:   conn       - connection structure
**          args       - rest of RNG command to be parsed and dealt with
**          numOfArgs  - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleRing(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_Conn *newConn;
    char   commandLine[LINE_LENGTH+1];    /* command that is sent */
    char   responseLine[LINE_LENGTH+1];   /* response             */
    char   endOfResponse[LINE_LENGTH+1];  /* end of response      */
    time_t now = time((time_t *)NULL);    /* used for TrID        */
    int    length, nwritten, nread;
    char    *sessionID, *host, *sp, *authChallengeInfo, *callingUserHandle, 
            *callingUserFriendlyName, *serverAddress;
    char    **localArgs;
    int     localNumOfArgs;
    int     index;
    int     port;
    gint    lID;
    int     eor = 0;                      /* end of response */
    char    *newHandle;

    AddHotmail(conn->handle, &newHandle);

    if (numOfArgs != 7) {
        free(newHandle);
        return -1;
    }

    sessionID = args[1];
    serverAddress = args[2];
    sp = args[3];
    authChallengeInfo = args[4];
    callingUserHandle = args[5];
    callingUserFriendlyName = args[6];

    if (ParseHostPort(serverAddress, &host, &port) != 0) {
        free(newHandle);
        return -1;
    }
    
    /*
    ** Establish new connection
    */

    newConn = (MSN_Conn *)malloc(sizeof(MSN_Conn));
    InitializeMSNConnection(newConn);
    if (ConnectToServer(newConn, host, port) != 0) {
        free(newHandle);
        return -1;
    }
    newConn->serverType = SWITCHBOARD_CONN;
    newConn->chatUsers.users = NULL;
    newConn->chatUsers.numOfUsers = 0;
    newConn->cookie[0] = '\0';
    newConn->commonName[0] = '\0';

    /*
    ** answer back and get the new roster
    */
    length = sprintf(commandLine, "%s %lu %s %s %s\r\n", 
                     CommandString[ANS], now, newHandle, authChallengeInfo, 
                     sessionID);
    sprintf(endOfResponse, "%s %lu OK", CommandString[ANS], now);

    nwritten = write(newConn->fd, commandLine, length);
    if (MSN_Read(newConn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(newConn);
        free(newHandle);
        return -1;
    }

    eor = (strcasecmp(endOfResponse, responseLine) == 0);


    while (!eor) {
        ParseArguments(responseLine, " ", &localArgs, &localNumOfArgs);
        if (localNumOfArgs == 6) {
            index = atoi(localArgs[2]);
            AddUserToChatList(&newConn->chatUsers, localArgs[4], 
                              localArgs[5], index, USER_NLN); 
        } 
        DestroyArguments(&localArgs, localNumOfArgs); 
        responseLine[0] = '\0';
        if (MSN_Read(newConn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
            KillConnection(conn);
            free(newHandle);
            return -1;
        }
        eor = (strcasecmp(endOfResponse, responseLine) == 0);
    }

    lID = gdk_input_add(newConn->fd, GDK_INPUT_READ, msn_callback_handler, 
                        newConn);
    newConn->listenerID = lID;
    msn_connections = g_list_append(msn_connections, newConn);
    free(newHandle);
    return 0;
}


/*
** Name:    HandleMessage
** Purpose: This function handles an instant message from either the server
**          or another client
** Input:   conn   - connection structure
**          args   - list of arguments
**          numOfargs - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleMessage(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_InstantMessage newIm;
    char *message;
    char *mimeInfo, *im;
    int  length, nread; 

    message = NULL;
    mimeInfo = NULL;
    im = NULL; 

    if (numOfArgs != 4)
        return -1;

    length = atoi(args[3]);
    message = (char *)malloc(sizeof(char) * (length + 1));

    nread = read(conn->fd, message, length);
    message[nread] = '\0';

    if (ParseMimeHeaders(message, &mimeInfo, &im) != 0) {
        return -1;
    }
 
    if (*im != '\0') {
        if (strcasecmp(args[1], "Hotmail") != 0) {
            newIm.year = 0;
            newIm.month = 0;
            newIm.day = 0;
            newIm.hour = 0;
            newIm.minute = 0;
            newIm.sec = 0;
            newIm.msg = im;
            RemoveHotmail(args[1], &newIm.sender);
            newIm.fd = conn->fd;

            if (msn_event[MSN_MSG] != NULL)
                (*msn_event[MSN_MSG])(&newIm);

            free(newIm.sender);
        }
    }
    free(message); 
    return 0;
}

/*
** Name:    SendMessage
** Purpose: This function sends an instant message to th switchboard
**          server connection
** Input:   conn       - connection structure
**          message    - message
** Output:  0 on success, -1 on failure
*/

int SendMessage(MSN_Conn *conn, char *message)
{
    char   *commandLine;
    time_t now = time((time_t *)NULL);    /* used for TrID        */
    int    length;

    if (!message)
        return -1;

    commandLine = (char *)malloc(strlen(MIME_HEADER)+strlen(message)+25);
    length = sprintf(commandLine, "%s %lu N %d\r\n%s%s", CommandString[MSG],
                     now, strlen(message)+strlen(MIME_HEADER), MIME_HEADER, 
                     message); 

    write(conn->fd, commandLine, length);
    free(commandLine);
    return 0;
}

/*
** Name:    HandleAcknowledge
** Purpose: This function simply handles an ackknowledgement from the server
**          Right now this will prolly just do a read and ignore
** Input:   conn     - connection structure
** Output:  0 on success, -1 on failure
*/

int HandleAcknowledge(MSN_Conn *conn)
{
    int  nread;
    char responseLine[LINE_LENGTH];  

    if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(conn);
        return -1;
    }
    return 0;
}

/*
** Name:    SendBYE
** Purpose: This function sends a BYE command to any chat connection
** Input:   conn - MSN connection structure
** Output:  0 on success, -1 on failure
*/

int SendBYE(MSN_Conn *conn)
{
    char   commandLine[LINE_LENGTH+1];
    int    length;

    length = sprintf(commandLine, "%s\r\n", CommandString[OUT]);
    write(conn->fd, commandLine, length);
    return 0;     
}

/*
** Name:    SendSBYE
** Purpose: This function sends a BYE command to a switchboard connection
**          and propely disposes of the connection
** Input:   conn - MSN connection structure
** Output:  0 on success, -1 on failure
*/

int SendSBYE(MSN_Conn *conn)
{
    char   commandLine[LINE_LENGTH+1];
    int    length;

    length = sprintf(commandLine, "%s\r\n", CommandString[OUT]);
    write(conn->fd, commandLine, length);
    DestroyChatList(conn->chatUsers);
    DestroyChatList(conn->flUsers);
    DestroyChatList(conn->alUsers);
    DestroyChatList(conn->blUsers);
    DestroyChatList(conn->rlUsers);
    gdk_input_remove(conn->listenerID);
    close(conn->fd);
    msn_connections = g_list_remove(msn_connections, conn);
    free(conn);
    return 0;
}

/*
** Name:    HandleBye
** Purpose: This function simply handles the BYE command from when a user
**          ends a switchboard session.  Could end a connection to the server
**          too
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of Arguments
** Output:  0 on success, -1 on failure
*/

int HandleBye(MSN_Conn *conn, char **args, int numOfArgs)
{
    if (numOfArgs < 2)
        return -1;


    RemoveUserFromChatList(&conn->chatUsers, args[1]);
    if (conn->chatUsers.numOfUsers == 0) {
        gdk_input_remove(conn->listenerID);
        close(conn->fd);
        msn_connections = g_list_remove(msn_connections, conn);
        free(conn);
    }

    return 0;
}

/*
** Name:    HandleOUT
** Purpose: This function handles any OUT call from the server. Clears
**          all the information too
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of Arguments
** Output:  0 on success, -1 on failure
*/

int HandleOUT(MSN_Conn *conn, char **args, int numOfArgs)
{
    GList    *node = NULL;
    MSN_Conn *connNode = NULL;

    DestroyChatList(conn->chatUsers);
    DestroyChatList(conn->flUsers);
    DestroyChatList(conn->alUsers);
    DestroyChatList(conn->blUsers);
    DestroyChatList(conn->rlUsers);
    gdk_input_remove(conn->listenerID);
    close(conn->fd);

    if (conn == &mainMSNConnection) {
        node = msn_connections;
        while (node) {
           connNode = node->data;
           DestroyChatList(connNode->chatUsers);
           DestroyChatList(connNode->flUsers);
           DestroyChatList(connNode->alUsers);
           DestroyChatList(connNode->blUsers);
           DestroyChatList(connNode->rlUsers);
           gdk_input_remove(connNode->listenerID);
           close(connNode->fd);
           free(connNode);
           node = node->next;
        }
        g_list_free(msn_connections);
        msn_connections = NULL;
    }

    if (strcmp(args[1], "OTH") == 0)
        MSN_ErrorOut("You have logged onto MSN from another computer", 
                       "Other Logon");

    if (strcmp(args[1], "SSD") == 0)
        MSN_ErrorOut("Server coming down for maintenance",
                       "Maintenance");

    if ((msn_event[MSN_OUT] != NULL) && (conn = &mainMSNConnection))
        (*msn_event[MSN_OUT])(&conn); 

    return 0;
}

/*
** Name:    HandleILN
** Purpose: This function handles the initial online status of people
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleILN(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_StatusChange sc;

    if (numOfArgs != 5)
        return -1;

    RemoveHotmail(args[3], &sc.handle);
    sc.newStatus = USER_NLN;
    /* 
    ** Substate check
    */

    if      (strcasecmp(args[2], "NLN") == 0) {
        sc.newStatus = USER_NLN;
        if (ChangeUserState(&conn->flUsers, args[3], USER_NLN) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_NLN);
    }
    else if (strcasecmp(args[2], "FLN") == 0) {
        sc.newStatus = USER_FLN;
        if (ChangeUserState(&conn->flUsers, args[3], USER_FLN) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_FLN);
    }
    else if (strcasecmp(args[2], "HDN") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_HDN) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_HDN);
    }
    else if (strcasecmp(args[2], "BSY") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_BSY) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_BSY);
    }
    else if (strcasecmp(args[2], "IDL") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_IDL) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_IDL);
    }
    else if (strcasecmp(args[2], "BRB") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_BRB) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_BRB);
    }
    else if (strcasecmp(args[2], "AWY") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_AWY) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_AWY);
    }
    else if (strcasecmp(args[2], "PHN") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_PHN) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_PHN);
    }
    else if (strcasecmp(args[2], "LUN") == 0) {
        if (ChangeUserState(&conn->flUsers, args[3], USER_LUN) != 0)
            AddUserToChatList(&conn->flUsers, args[3], args[4], 0, USER_LUN);
    }

    if (msn_event[MSN_ILN] != NULL)
        (*msn_event[MSN_ILN])(&sc); 
    free(sc.handle);
    return 0;
}

/*
** Name:    HandleFLN
** Purpose: This function handles when users come offline
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleFLN(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_StatusChange sc;

    if (numOfArgs != 2)
        return -1;

    RemoveHotmail(args[1], &sc.handle);
    sc.newStatus = USER_FLN;

    RemoveUserFromChatList(&conn->flUsers, args[1]);

    if (msn_event[MSN_FLN] != NULL)
        (*msn_event[MSN_FLN])(&sc);

    free(sc.handle);
    return 0;
}

/*
** Name:    HandleNLN
** Purpose: This function handles when users come online
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleNLN(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_StatusChange sc;
    if (numOfArgs != 4)
        return -1;

    /* 
    ** Substate check
    */
    
    RemoveHotmail(args[2], &sc.handle);
    sc.newStatus = USER_NLN;

    if      (strcasecmp(args[1], "NLN") == 0) {
        sc.newStatus = USER_NLN;
        if (ChangeUserState(&conn->flUsers, args[2], USER_NLN) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_NLN);
    }
    else if (strcasecmp(args[1], "FLN") == 0) {
        sc.newStatus = USER_FLN;
        if (ChangeUserState(&conn->flUsers, args[2], USER_FLN) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_FLN);
    }
    else if (strcasecmp(args[1], "HDN") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_HDN) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_HDN);
    }
    else if (strcasecmp(args[1], "BSY") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_BSY) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_BSY);
    }
    else if (strcasecmp(args[1], "IDL") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_IDL) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_IDL);
    }
    else if (strcasecmp(args[1], "BRB") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_BRB) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_BRB);
    }
    else if (strcasecmp(args[1], "AWY") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_AWY) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_AWY);
    }
    else if (strcasecmp(args[1], "PHN") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_PHN) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_PHN);
    }
    else if (strcasecmp(args[1], "LUN") == 0) {
        if (ChangeUserState(&conn->flUsers, args[2], USER_LUN) != 0)
            AddUserToChatList(&conn->flUsers, args[2], args[3], 0, USER_LUN);
    }

    if (msn_event[MSN_NLN] != NULL)
        (*msn_event[MSN_NLN])(&sc);

    free(sc.handle); 
    return 0;
}

/*
** Name:    HandleADD
** Purpose: This function handles the ADD commands from the server that can     **          happen asyncronously
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleAdd(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_AuthMessage *am = NULL;
    ChatSession *userList = NULL;
    long trid;

    if (numOfArgs != 6)
        return -1;

    trid = atol(args[1]);
    if (trid != 0)
        return 0;

    if      (strcasecmp(args[2], "FL") == 0)
        userList = &conn->flUsers;
    else if (strcasecmp(args[2], "AL") == 0)
        userList = &conn->alUsers;
    else if (strcasecmp(args[2], "BL") == 0)
        userList = &conn->blUsers;
    else if (strcasecmp(args[2], "RL") == 0) {
        userList = &conn->rlUsers;
        if (msn_event[MSN_AUTH] != NULL) {
            am = (MSN_AuthMessage *)malloc(sizeof(MSN_AuthMessage));
            am->handle = conn->handle;
            am->requestor = strdup(args[4]);
            am->conn = conn;
            (*msn_event[MSN_AUTH])(am);
        }
    }
    AddUserToChatList(userList, args[4], args[5], 0, USER_FLN);
    return 0;
}

/*
** Name:    HandleRemove
** Purpose: This function handles the REM commands from the server that can
**          happen asyncronously
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of argyments
** Output:  0 on success, -1 on failure
*/

int HandleRemove(MSN_Conn *conn, char **args, int numOfArgs)
{
    ChatSession *userList = NULL;
    long trid;

    if (numOfArgs != 5)
        return -1;

    trid = atol(args[1]);
    if (trid != 0)
        return 0;

    if      (strcasecmp(args[2], "FL") == 0)
        userList = &conn->flUsers;
    else if (strcasecmp(args[2], "AL") == 0)
        userList = &conn->alUsers;
    else if (strcasecmp(args[2], "BL") == 0)
        userList = &conn->blUsers;
    else if (strcasecmp(args[2], "RL") == 0)
        userList = &conn->rlUsers;

    RemoveUserFromChatList(userList, args[4]);

    return 0;
}

/*
** Name:    HandleLST
** Purpose: This function handles the LST command, which deals with user
**          lists
** Input:   conn - MSN connection structure
**          args      - arguments
**          numOfArgs - number of arguments
** Output:  0 on success, -1 on failure
*/

int HandleLST(MSN_Conn *conn, char **args, int numOfArgs)
{
    MSN_AuthMessage *am = NULL;

    int i;
    if (numOfArgs != 8)
        return -1;

    i = atoi(args[4]);

    if      (strcmp(args[2], "FL") == 0) {
        AddUserToChatList(&conn->flUsers, args[6], args[7], i, USER_FLN);
    }
    else if (strcmp(args[2], "AL") == 0) {
        AddUserToChatList(&conn->alUsers, args[6], args[7], i, USER_FLN);
    }
    else if (strcmp(args[2], "BL") == 0) {
        AddUserToChatList(&conn->blUsers, args[6], args[7], i, USER_FLN);
    }
    else if (strcmp(args[2], "RL") == 0) {
        AddUserToChatList(&conn->rlUsers, args[6], args[7], i, USER_FLN);
        if (SearchForUser(conn->alUsers, args[6]) == 0)  {
            if (msn_event[MSN_AUTH] != NULL)
                am = (MSN_AuthMessage *)malloc(sizeof(MSN_AuthMessage));
                am->handle = conn->handle;
                am->requestor = strdup(args[6]);
                am->conn = conn;
                (*msn_event[MSN_AUTH])(am);
            }
    }

    return 0;
}

/*
** Name:    HandleXFR
** Purpose: This function handles an XFR event, which transfers to a new server
** Input:   conn      - connection structure
**          args      - arguments
**          numOfArgs - number of arguments
**          listener  - whether to setup a listener or not
** Output:  0 on success, -1 on failure
*/

int HandleXFR(MSN_Conn *conn, char **args, int numOfArgs, int listener)
{
    char *host;
    int  port, lID;

    if (numOfArgs != 4)
        return -1;

    if (ParseHostPort(args[3], &host, &port) != 0) {
        return -1;
    }
   
    if (conn->listenerID > 0)
        gdk_input_remove(conn->listenerID); 

    close(conn->fd);
    conn->fd = -1;

    if (ConnectToServer(conn, host, port) != 0) {
        return -1;
    }

    if (listener == 1) {
        lID = gdk_input_add(conn->fd, GDK_INPUT_READ, msn_callback_handler,
                            conn);
        conn->listenerID = lID;
    }
    return 0;
}


/*
** Name:    AddContact
** Purpose: This function adds a contact to the forward list
** Input:   conn - MSN connection structure
**          handle - handle of person to use
** Output:  0 on success, -1 on failure
*/

int AddContact(MSN_Conn *conn, char *handle)
{
    char         commandLine[LINE_LENGTH+1];
    time_t now = time((time_t *)NULL);
    int          length;
    char         *newHandle;

    if (conn->fd == 0)
        return 0;

    AddHotmail(handle, &newHandle);
    /*length = sprintf(commandLine, "%s %lu FL %s %s\r\n", 
                     CommandString[REM], now, newHandle, handle); 
    write(conn->fd, commandLine, length); */
    length = sprintf(commandLine, "%s %lu FL %s %s\r\n", 
                     CommandString[ADD], now, newHandle, handle); 
    write(conn->fd, commandLine, length);
    free(newHandle);
    return 0;
}

/*
** Name:    AuthorizeContact
** Purpose: This function authorizes a contact to be on the allow list
** Input:   conn - MSN connection structure
**          handle - handle of the person to authorize
** Output:  0 on succuess, -1 in failure
*/

int AuthorizeContact(MSN_Conn *conn, char *handle)
{
    char         commandLine[LINE_LENGTH+1];
    time_t now = time((time_t *)NULL);
    int          length;
    char         *newHandle;

    AddHotmail(handle, &newHandle);
    length = sprintf(commandLine, "%s %lu AL %s %s\r\n", 
                     CommandString[ADD], now, newHandle, handle); 
    write(conn->fd, commandLine, length);
    free(newHandle);
    return 0;
}

/*
** Name:    RemoveContact
** Purpose: This function removes a contact to the forward list
** Input:   conn - MSN connection structure
**          handle - handle of person to use
** Output:  0 on success, -1 on failure
*/

int RemoveContact(MSN_Conn *conn, char *handle)
{
    char         commandLine[LINE_LENGTH+1];
    time_t now = time((time_t *)NULL);
    int          length;
    char         *newHandle;

    if (conn->fd == 0)
        return 0;

    AddHotmail(handle, &newHandle);
    length = sprintf(commandLine, "%s %lu FL %s\r\n", 
                     CommandString[REM], now, newHandle);
    write(conn->fd, commandLine, length);
    free(newHandle);
    return 0;
}

/*
** Name:    Logout
** Purpose: This function logs out from the server
** Input:   conn - MSN connection structure
** Output:  0 on success, -1 on failure
*/

int Logout(MSN_Conn *conn)
{
    GList    *node = NULL;
    MSN_Conn *connNode = NULL;

    for (node = msn_connections; node; node=node->next) {
        connNode = node->data;
        SendBYE(connNode);
    }
    return 0;
}


/* 
** Name:    GetHostAddress
** Purpose: This function returns a host number for use in connecting to 
**          a socket
** Input:   hostname - name of host
**          number to use to connect
** Output:  0 on success, -1 on failure
** Note:    Took this from libtoc
*/

static int GetHostAddress(char *hostname, unsigned int *number)
{

    struct hostent *hp;

    if ((hp = proxy_gethostbyname(hostname))) {
        
        *number =  ((struct in_addr *)(hp->h_addr))->s_addr;
        return 0;
    }

    *number = -1;
    return -1;
}

/*
** Name:    MSN_Read
** Purpose: This function reads in the line of input on the socket up to the 
**          \r\n.  Perhaps not as efficient as it could be, will look for other
**          functions to be more efficient
** Input:   fd     - file descriptor
**          buffer - buffer to read into
**          nItems - max number of characters to read
**          count  - number of bytes read
** Output:  0 on success, -1 on failure, -2 if socket closes
** Note:    Caller must provide a large enough buffer
*/

static int MSN_Read(int fd, char *buffer, int nItems, int *count)
{
    int     loop = nItems;
    int     bcount = 0;
    char    c;
    int     nread  = 0;


    *count = 0;
    while (loop--) {
        nread = read(fd, &c, 1);
        if (nread <= 0) {
            perror("Error from read");
            buffer[bcount] = '\0';
            return -1;
        }   
        else if (c == '\r') {
            *count += 1;
        }
        else if (c == '\n') {
            buffer[bcount] = '\0';
            *count += 1;
            return 0;
        }
        else {
            buffer[bcount++] = c;
            *count += 1;
        }
        
    }
    return 0;
}            

/*
** Name:    ParseForCommand
** Purpose: This function parses the line of input from a message sent to
**          the client
** Input:   msn_conn   - a MSN connection structure
** Output:  none
*/

void ParseForCommand(MSN_Conn *msn_connection)
{
    char buffer[5000];
    int  nread;      /* number of bytes read */
    char **args;
    int  numOfArgs;
 
    if (MSN_Read(msn_connection->fd, buffer, 5000, &nread)  <= -1) {
        KillConnection(msn_connection);
        return;
    }
    ParseArguments(buffer, " ", &args, &numOfArgs);

    if (numOfArgs > 0) {

        /*
        ** Big messy if-then for testing
        */

        if      (strcasecmp(args[0], CommandString[RNG]) == 0)
            HandleRing(msn_connection, args, numOfArgs);
        else if (strcasecmp(args[0], CommandString[MSG]) == 0) 
            HandleMessage(msn_connection, args, numOfArgs);
        else if (strcasecmp(args[0], CommandString[BYE]) == 0) 
            HandleBye(msn_connection, args, numOfArgs);
       else  if (strcasecmp(args[0], CommandString[ILN]) == 0)
            HandleILN(msn_connection, args, numOfArgs); 
        else if (strcasecmp(args[0], CommandString[FLN]) == 0)
            HandleFLN(msn_connection, args, numOfArgs);
        else if (strcasecmp(args[0], CommandString[NLN]) == 0) 
            HandleNLN(msn_connection, args, numOfArgs);  
        else if (strcasecmp(args[0], CommandString[LST]) == 0) 
            HandleLST(msn_connection, args, numOfArgs);  
        else if (strcasecmp(args[0], CommandString[ADD]) == 0) 
            HandleAdd(msn_connection, args, numOfArgs);  
        else if (strcasecmp(args[0], CommandString[REM]) == 0) 
            HandleRemove(msn_connection, args, numOfArgs);  
        else if (strcasecmp(args[0], CommandString[OUT]) == 0) 
            HandleOUT(msn_connection, args, numOfArgs);  
        else if (strcasecmp(args[0], CommandString[XFR]) == 0)
            HandleXFR(msn_connection, args, numOfArgs, 1);
    }
    DestroyArguments(&args, numOfArgs);

}

/*
** Name:    RequestSwitchboardSession
** Purpose: This function requests a connection to a Switchboard server for
**          the intent of establishing an instant message session
** Input:   conn   - MSN connection structure
**          handle - handle of person
** Output:  0 on success, -1 on failure
*/

int RequestSwitchboardSession(MSN_Conn *conn, char *handle)
{
    MSN_Conn *newConn;
    char     commandLine[LINE_LENGTH+1];  
    char     responseLine[LINE_LENGTH+1];  
    char     endOfResponse[LINE_LENGTH+1];  
    time_t   now = time((time_t *)NULL);
    int      length, nread, nwritten;
    char     **localArgs;
    int      localNumOfArgs;
    int      port;
    char     *host;
    int      lID;
    char     *newHandle;

    AddHotmail(conn->handle, &newHandle); 
    length = sprintf(commandLine, "%s %lu SB\r\n", CommandString[XFR], now);
    nwritten = write(conn->fd, commandLine, length);
    if (MSN_Read(conn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(conn);
        free(newHandle);
        return -1;
    }

    ParseArguments(responseLine, " ", &localArgs, &localNumOfArgs);
    if (localNumOfArgs != 6) {
        free(newHandle);
        return -1;
    }

    if (ParseHostPort(localArgs[3], &host, &port) != 0) {
        DestroyArguments(&localArgs, localNumOfArgs);
        free(newHandle);
        return -1;
    }

    /*
    ** Establish new connection
    */

    newConn = (MSN_Conn *)malloc(sizeof(MSN_Conn));
    InitializeMSNConnection(newConn);

    if (ConnectToServer(newConn, host, port) != 0) {
        free(newHandle);
        return -1;
    }

    strcpy(newConn->cookie, localArgs[5]);

    now = time((time_t *)NULL);
    sprintf(endOfResponse, "%s %lu OK %s Friend", CommandString[USR], now, 
            newHandle);
    length = sprintf(commandLine, "%s %lu %s %s\r\n", 
                     CommandString[USR], now, newHandle, newConn->cookie);
    nwritten = write(newConn->fd, commandLine, length);
    if (MSN_Read(newConn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(newConn);
        free(newHandle);
        return -1;
    }
     

    now = time((time_t *)NULL);
    sprintf(endOfResponse, "%s %lu STATUS", CommandString[CAL], now);
    length = sprintf(commandLine, "%s %lu %s\r\n", CommandString[CAL], now,
                     handle);
    nwritten = write(newConn->fd, commandLine, length);
    if (MSN_Read(newConn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(newConn);
        free(newHandle);
        return -1;
    }
    AddUserToChatList(&newConn->chatUsers, handle, handle, 0, USER_NLN);

    if (MSN_Read(newConn->fd, responseLine, LINE_LENGTH, &nread) <= -1) {
        KillConnection(newConn);
        free(newHandle);
        return -1;
    }
    lID = gdk_input_add(newConn->fd, GDK_INPUT_READ, msn_callback_handler,
                        newConn);
    newConn->listenerID = lID;
    msn_connections = g_list_append(msn_connections, newConn);

    free(newHandle);
    return 0; 
}

/*
** Name:    FindMSNConnectionByHandle
** Purpose: This purpose searches the list of connections and made and looks
**          for the first connection containing a handle
** Input:   handle - handle of the user having a message sent to
** Output:  0 on success, -1 on failure
*/

MSN_Conn *FindMSNConnectionByHandle(char *handle)
{
    GList    *node = NULL;
    GList    *node2 = NULL;
    MSN_Conn *connNode = NULL;

    for (node = msn_connections; node; node=node->next) {
        connNode = node->data;
        node2 = g_list_find_custom(connNode->chatUsers.users, handle,
                                   CompareUserName);
        if (node2 != NULL)
            return connNode;
    }
    return NULL;
}

/*
** Name:    KillConnection
** Purpose: This function destroys a connection that has been unceremoniously
**          bet cut
** Input:   conn - MSN connection structure
** Output:  0 on success, -1 by failure
*/

int KillConnection(MSN_Conn *conn)
{
    DestroyChatList(conn->chatUsers);
    DestroyChatList(conn->flUsers);
    DestroyChatList(conn->alUsers);
    DestroyChatList(conn->blUsers);
    DestroyChatList(conn->rlUsers);
    gdk_input_remove(conn->listenerID);
    close(conn->fd);
    msn_connections = g_list_remove(msn_connections, conn);

    if (conn == &mainMSNConnection) {
        MSN_ErrorOut("You have lost your conenction to the server",
                      "Connection lost.");

        if (msn_event[MSN_OUT] != NULL) 
            (*msn_event[MSN_OUT])(&conn); 
    }
    free(conn);
    return 0;
}


#ifdef UNIT_TEST

int main(void) 
{
    char     sp[10];
    MSN_Conn msn_connection;

    msn_connection.serverType = DISPATCH_CONN;  
    msn_connection.chatUsers.users = NULL;
    msn_connection.chatUsers.numOfUsers = 0;
    msn_connection.flUsers.users = NULL;
    msn_connection.flUsers.numOfUsers = 0;
    msn_connection.alUsers.users = NULL;
    msn_connection.alUsers.numOfUsers = 0;
    msn_connection.blUsers.users = NULL;
    msn_connection.blUsers.numOfUsers = 0;
    msn_connection.rlUsers.users = NULL;
    msn_connection.rlUsers.numOfUsers = 0;
    msn_connection.cookie[0] = '\0';
    msn_connection.commonName[0] = '\0'; 
    
    if (ConnectToServer(&msn_connection, DEFAULT_HOST, DEFAULT_PORT) != 0)
        printf("Couldn't connect to server\n"); 
    if (SetProtocol(&msn_connection, DEFAULT_PROTOCOL) != 0)
        printf("Couldn't set protocol\n");
    if (GetServerPolicyInfo(&msn_connection, sp) != 0)
        printf("Couldn't retrieve security policy\n");

    AuthenticateUserMD5(&msn_connection, "JeffryX", "34tuvga");
    Synchronize(&msn_connection);
    ChangeState(&msn_connection, "NLN");

    gdk_input_add(msn_connection.fd, GDK_INPUT_READ, msn_callback_handler,
                  &msn_connection);

    gtk_main();
    exit(0);
}

#endif

