/* External socket support
 *
 * (C) 2003 Anope Team
 * Contact us at info@anope.org
 *
 * Please read COPYING and README for further details.
 *
 * Based on the original code of Epona by Lara.
 * Based on the original code of Services by Andy Church. 
 * 
 */
#include "services.h"

int svr_fd, max_fd;
fd_set master_fdset, temp_fdset;

/*******************************************************************************
 * Functions
 *******************************************************************************/
/**
 * Init function
 **/
void extsock_init(int port)
{
    int sockopt = 1;
    struct sockaddr_in svr_addr;

    FD_ZERO(&master_fdset);
    FD_ZERO(&temp_fdset);

    if ((svr_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        log_perror("socket");
        exit(1);
    }

    if (setsockopt(svr_fd, SOL_SOCKET, SO_REUSEADDR,
                   &sockopt, sizeof(int)) == -1) {
        log_perror("setsockopt");
        exit(1);
    }

    svr_addr.sin_family = AF_INET;
    svr_addr.sin_addr.s_addr = INADDR_ANY;
    svr_addr.sin_port = htons(port);
    memset(&(svr_addr.sin_zero), '\0', 8);
    if (bind(svr_fd, (struct sockaddr *) &svr_addr,
             sizeof(struct sockaddr)) == -1) {
        log_perror("bind");
        exit(1);
    }

    if (listen(svr_fd, MAXQUEUE) == -1) {
        log_perror("listen");
        exit(1);
    }

    max_fd = svr_fd;
    FD_SET(svr_fd, &master_fdset);

    alog("External socket listening on port %d.", port);
}

/**
 * Process function
 **/
void extsock_process(void)
{
    int socket_fd, clnt_fd, len, bytes;
    char buffer[BUFFERSIZE];
    struct sockaddr_in clnt_addr;
    struct timeval wait;
    char *cmd, *arg1, *c;
    User *u;

    temp_fdset = master_fdset;
    wait.tv_sec = 0;
    wait.tv_usec = 250000;

    if (select(max_fd + 1, &temp_fdset, NULL, NULL, &wait) == -1) {
        log_perror("select");
        exit(1);
    }

    for (socket_fd = 0; socket_fd <= max_fd; socket_fd++) {
        if (FD_ISSET(socket_fd, &temp_fdset)) {
            if (socket_fd == svr_fd) {
                /* Its a new connection */
                len = sizeof(struct sockaddr);
                if ((clnt_fd =
                     accept(svr_fd, (struct sockaddr *) &clnt_addr,
                            &len)) == -1) {
                    log_perror("accept");
                    exit(1);
                } else {
                    alog("new connection from %s (socket %d)",
                         inet_ntoa(clnt_addr.sin_addr), clnt_fd);

                    if (!extsock_val_clnt(clnt_addr.sin_addr)) {
                        alog("%s does not have access to the external socket", inet_ntoa(clnt_addr.sin_addr));
                        close(clnt_fd);
                        continue;
                    }

                    FD_SET(clnt_fd, &master_fdset);
                    if (clnt_fd > max_fd) {
                        max_fd = clnt_fd;
                    }
                }
            } else {
                /* Its an existing connection */
                if ((bytes =
                     recv(socket_fd, buffer, sizeof(buffer), 0)) <= 0) {
                    if (bytes == 0) {
                        alog("socket %d closed.", socket_fd);
                    } else {
                        log_perror("recv");
                    }

                    close(socket_fd);
                    FD_CLR(socket_fd, &master_fdset);
                } else {
                    buffer[bytes] = '\0';
                    if ((c = strchr(buffer, '\n')) != NULL)
                        *c = '\0';
                    if ((c = strchr(buffer, '\r')) != NULL)
                        *c = '\0';

                    alog("Recieved data from socket %d: %s", socket_fd,
                         (char *) buffer);
                    /* We really need a command table, but for prototype purposes
                     * I'll do it the easy way.
                     */
                    cmd = strtok((char *) buffer, " ");
                    if (strcmp(cmd, "ns_register") == 0) {
                        arg1 = strtok(NULL, " ");
                        /* u = new_user(arg1); */
                        /* This is not working... maybe i should not even introduce the user */
                        u = do_nick("", arg1, "*", "*", "localhost",
                                    "ExtSocket", (time_t) 0, 1, "*", "*");
                        snprintf(u->nick, NICKMAX, arg1);
                        u->isSocket = socket_fd;
                        ns_do_register(u);
                        do_quit(arg1, 0, NULL);
                    }
                }
            }
        }
    }
}

/**
 * Close function
 **/
void extsock_close(void)
{
    int sockfd;
    char *buffer[BUFFERSIZE];

    for (sockfd = 0; sockfd <= max_fd; sockfd++) {
        if (!FD_ISSET(sockfd, &temp_fdset)) {
            if (sockfd == svr_fd)
                continue;
        } else {
            *buffer = "Shutting down";
            if (send(sockfd, buffer, sizeof(buffer), 0) == -1) {
                {
                    log_perror("send");
                }
                close(sockfd);
            }
        }
    }
    close(svr_fd);
}

/**
 * Client validation function
 **/
int extsock_val_clnt(struct in_addr inet_addr)
{
    int i;

    if (SockIPNumber <= 0) {
        log_perror("The ExtSockIP configuration variable was empty");
        return EXTSOCK_FAIL;
    }

    for (i = 0; i < SockIPNumber; i++) {
        if (!strcmp(inet_ntoa(inet_addr), ExtSockIPs[i])) {
            return EXTSOCK_OK;
        }
    }

    return EXTSOCK_FAIL;
}

/* EOF */
