/*
 *  addr_server.c -- socket-based ip address server.
 *                   8-92 : Dwayne Fontenot : original coding
 */
#include "std.h"
#include "addr_server.h"
#include "socket_ctrl.h"
#include "file_incl.h"
#include "port.h"
#ifdef MINGW
#include <ws2tcpip.h>
#endif
#ifdef DEBUG_MACRO
int debug_level = DBG_addr_server;
#endif				/* DEBUG_MACRO */
#define DBG(x) debug(addr_server, x)
/*
 * private local variables.
 */
static connection all_conns[MAX_CONNS];
static int total_conns = 0;
static queue_element_ptr queue_head = NULL;
static queue_element_ptr queue_tail = NULL;
static queue_element_ptr stack_head = NULL;
static int queue_length = 0;
static int conn_fd;
fd_set readmask;
int name_by_ip (int, char *);
int ip_by_name (int, char *);
INLINE_STATIC void process_queue (void);
void init_conns (void);
void init_conn_sock (int, char *);
#ifdef SIGNAL_FUNC_TAKES_INT
void sigpipe_handler (int);
#else
void sigpipe_handler (void);
#endif
INLINE void aserv_process_io (int);
void enqueue_datapending (int, int);
void handle_top_event (void);
void dequeue_top_event (void);
void pop_queue_element (queue_element_ptr *);
void push_queue_element (queue_element_ptr);
void new_conn_handler (void);
void conn_data_handler (int);
int index_by_fd (int);
void terminate (int);
void debug_perror (const char *, const char *);
    void debug_perror (const char * what, const char * file) {
        if (file)
            fprintf(stderr, "System Error: %s:%s:%s\n", what, file, port_strerror(errno));
        else
            fprintf(stderr, "System Error: %s:%s\n", what, port_strerror(errno));
    }
void init_conns()
{
    int i;
    for (i = 0; i < MAX_CONNS; i++) {
        all_conns[i].fd = -1;
        all_conns[i].state = CONN_CLOSED;
        all_conns[i].sname[0] = '\0';
        /* ensure 'leftover' buffer is _always_ null terminated */
        all_conns[i].buf[0] = '\0';
        all_conns[i].buf[IN_BUF_SIZE - 1] = '\0';
        all_conns[i].leftover = 0;
    }
}
/*
 * Initialize connection socket.
 */
void init_conn_sock (int port_num, char * ipaddress)
{
#ifdef IPV6
    struct sockaddr_in6 sin;
#else
    struct sockaddr_in sin;
#endif
    socklen_t sin_len;
    int optval;
#ifdef WINSOCK
    WSADATA WSAData;
    WSAStartup(MAKEWORD(1,1), &WSAData);
    atexit(cleanup_sockets);
#endif
    /*
     * create socket of proper type.
     */
#ifdef IPV6
    if ((conn_fd = socket(AF_INET6, SOCK_STREAM, 0)) == INVALID_SOCKET) {
#else
        if ((conn_fd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
#endif
            socket_perror("init_conn_sock: socket", 0);
            exit(1);
        }
        /*
         * enable local address reuse.
         */
        optval = 1;
        if (setsockopt(conn_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &optval,
                    sizeof(optval)) == -1) {
            socket_perror("init_conn_sock: setsockopt", 0);
            exit(2);
        }
        /*
         * fill in socket address information.
         */
#ifdef IPV6
        sin.sin6_family = AF_INET6;
        if(ipaddress)
            inet_pton(AF_INET6, ipaddress, &(sin.sin6_addr));
        else
            sin.sin6_addr = in6addr_any;
        sin.sin6_port = htons((u_short) port_num);
#else
        sin.sin_family = AF_INET;
        sin.sin_addr.s_addr = (ipaddress ? inet_addr(ipaddress) : INADDR_ANY);
        sin.sin_port = htons((u_short) port_num);
#endif
        /*
         * bind name to socket.
         */
        if (bind(conn_fd, (struct sockaddr *) & sin, sizeof(sin)) == -1) {
            socket_perror("init_conn_sock: bind", 0);
            exit(3);
        }
        /*
         * get socket name.
         */
        sin_len = sizeof(sin);
        if (getsockname(conn_fd, (struct sockaddr *) & sin, &sin_len) == -1) {
            socket_perror("init_conn_sock: getsockname", 0);
            exit(4);
        }
        /*
         * register signal handler for SIGPIPE.
         */
#if defined(SIGPIPE) && defined(SIGNAL_ERROR)/* windows has no SIGPIPE */
        if (signal(SIGPIPE, sigpipe_handler) == SIGNAL_ERROR) {
            socket_perror("init_conn_sock: signal SIGPIPE", 0);
            exit(5);
        }
#endif
        /*
         * set socket non-blocking
         */
        if (set_socket_nonblocking(conn_fd, 1) == -1) {
            socket_perror("init_conn_sock: set_socket_nonblocking 1", 0);
            exit(8);
        }
        /*
         * listen on socket for connections.
         */
        if (listen(conn_fd, 128) == -1) {
            socket_perror("init_conn_sock: listen", 0);
            exit(10);
        }
        DBG(("listening for connections on port %d", port_num));
    }
    /*
     * SIGPIPE handler -- does very little for now.
     */
#ifdef SIGNAL_FUNC_TAKES_INT
    void sigpipe_handler (int sig)
    {
#else
        void sigpipe_handler()
        {
#endif
            fprintf(stderr, "SIGPIPE received.\n");
        }
        /*
         * I/O handler.
         */
        INLINE void aserv_process_io (int nb)
        {
            int i;
            switch (nb) {
                case -1:
                    debug_perror("sigio_handler: select", 0);
                    break;
                case 0:
                    break;
                default:
                    /*
                     * check for new connection.
                     */
                    if (FD_ISSET(conn_fd, &readmask)) {
                        DBG(("sigio_handler: NEW_CONN"));
                        enqueue_datapending(conn_fd, NEW_CONN);
                    }
                    /*
                     * check for data pending on established connections.
                     */
                    for (i = 0; i < MAX_CONNS; i++) {
                        if (FD_ISSET(all_conns[i].fd, &readmask)) {
                            DBG(("sigio_handler: CONN"));
                            enqueue_datapending(all_conns[i].fd, CONN);
                        }
                    }
                    break;
            }
        }
        INLINE_STATIC void process_queue()
        {
            int i;
            for (i = 0; queue_head && (i < MAX_EVENTS_TO_PROCESS); i++) {
                handle_top_event();
                dequeue_top_event();
            }
        }
        void enqueue_datapending (int fd, int fd_type)
        {
            queue_element_ptr new_queue_element;
            pop_queue_element(&new_queue_element);
            new_queue_element->event_type = fd_type;
            new_queue_element->fd = fd;
            new_queue_element->next = NULL;
            if (queue_head) {
                queue_tail->next = new_queue_element;
            } else {
                queue_head = new_queue_element;
            }
            queue_tail = new_queue_element;
        }
        void dequeue_top_event()
        {
            queue_element_ptr top_queue_element;
            if (queue_head) {
                top_queue_element = queue_head;
                queue_head = queue_head->next;
                push_queue_element(top_queue_element);
            } else {
                fprintf(stderr, "dequeue_top_event: tried to dequeue from empty queue!\n");
            }
        }
        void pop_queue_element (queue_element_ptr * the_queue_element)
        {
            if ((*the_queue_element = stack_head))
                stack_head = stack_head->next;
            else
                *the_queue_element = (queue_element_ptr) malloc(sizeof(queue_element));
            queue_length++;
        }
        void push_queue_element (queue_element_ptr the_queue_element)
        {
            the_queue_element->next = stack_head;
            stack_head = the_queue_element;
            queue_length--;
        }
        void handle_top_event()
        {
            switch (queue_head->event_type) {
                case NEW_CONN:
                    DBG(("handle_top_event: NEW_CONN"));
                    new_conn_handler();
                    break;
                case CONN:
                    DBG(("handle_top_event: CONN data on fd %d", queue_head->fd));
                    conn_data_handler(queue_head->fd);
                    break;
                default:
                    fprintf(stderr, "handle_top_event: unknown event type %d\n",
                            queue_head->event_type);
                    break;
            }
        }
        /*
         * This is the new connection handler. This function is called by the
         * event handler when data is pending on the listening socket (conn_fd).
         * If space is available, an interactive data structure is initialized and
         * the connected is established.
         */
        void new_conn_handler()
        {
#ifdef IPV6
            struct sockaddr_in6 client;
#else
            struct sockaddr_in client;
#endif
            socklen_t client_len;
            struct hostent *c_hostent;
            int new_fd;
            int conn_index;
            client_len = sizeof(client);
            new_fd = accept(conn_fd, (struct sockaddr *) & client, &client_len);
            if (new_fd == -1) {
                socket_perror("new_conn_handler: accept", 0);
                return;
            }
            if (set_socket_nonblocking(new_fd, 1) == -1) {
                socket_perror("new_conn_handler: set_socket_nonblocking 1", 0);
                OS_socket_close(new_fd);
                return;
            }
            if (total_conns >= MAX_CONNS) {
                char *message = "no available slots -- closing connection.\n";
                fprintf(stderr, "new_conn_handler: no available connection slots.\n");
                OS_socket_write(new_fd, message, strlen(message));
                if (OS_socket_close(new_fd) == -1)
                    socket_perror("new_conn_handler: close", 0);
                return;
            }
            /* get some information about new connection */
            for (conn_index = 0; conn_index < MAX_CONNS; conn_index++) {
                if (all_conns[conn_index].state == CONN_CLOSED) {
                    DBG(("new_conn_handler: opening conn index %d", conn_index));
                    /* update global data for new fd */
                    all_conns[conn_index].fd = new_fd;
                    all_conns[conn_index].state = CONN_OPEN;
                    all_conns[conn_index].addr = client;
                    char portname[256];
                    if(getnameinfo((struct sockaddr *)&client, sizeof(client), all_conns[conn_index].sname, SNAME_LEN, portname, 255, NI_NAMEREQD|NI_NUMERICHOST))
                        strcpy(all_conns[conn_index].sname, "<unknown>");
                    total_conns++;
                    return;
                }
            }
            fprintf(stderr, "new_conn_handler: sanity check failed!\n");
        }
        void conn_data_handler (int fd)
        {
            int conn_index;
            int buf_index;
            int num_bytes;
            int msgtype;
            int leftover;
            char *buf;
            int res, msglen, expecting;
            long unread_bytes;
            if ((conn_index = index_by_fd(fd)) == -1) {
                fprintf(stderr, "conn_data_handler: invalid fd.\n");
                return;
            }
            DBG(("conn_data_handler: read on fd %d", fd));
            /* append new data to end of leftover data (if any) */
            leftover = all_conns[conn_index].leftover;
            buf = (char *) &all_conns[conn_index].buf[0];
            num_bytes = OS_socket_read(fd, buf + leftover, (IN_BUF_SIZE - 1) - leftover);
            switch (num_bytes) {
                case -1:
                    switch (socket_errno) {
                        case EWOULDBLOCK:
                            DBG(("conn_data_handler: read on fd %d: Operation would block.",
                                        fd));
                            break;
                        default:
                            socket_perror("conn_data_handler: read", 0);
                            terminate(conn_index);
                            break;
                    }
                    break;
                case 0:
                    if (all_conns[conn_index].state == CONN_CLOSED)
                        fprintf(stderr, "get_user_data: tried to read from closed fd.\n");
                    terminate(conn_index);
                    break;
                default:
                    DBG(("conn_data_handler: read %d bytes on fd %d", num_bytes, fd));
                    num_bytes += leftover;
                    buf_index = 0;
                    expecting = 0;
                    while (num_bytes > sizeof(int) && buf_index < (IN_BUF_SIZE - 1)) {
                        /* get message type */
                        memcpy((char *) &msgtype, (char *) &buf[buf_index], sizeof(int));
                        DBG(("conn_data_handler: message type: %d", msgtype));
                        if (msgtype == NAMEBYIP) {
                            if (buf[buf_index + sizeof(int)] == '\0') {
                                /* no data here...resync */
                                buf_index++;
                                num_bytes--;
                                continue;
                            }
                            if (expecting && num_bytes < expecting) {
                                /*
                                 * message truncated...back up to DATALEN message; exit
                                 * loop...and try again later
                                 */
                                buf_index -= (sizeof(int) + sizeof(int));
                                num_bytes += (sizeof(int) + sizeof(int));
                                break;
                            }
                            res = name_by_ip(conn_index, &buf[buf_index]);
                        } else if (msgtype == IPBYNAME) {
                            if (buf[buf_index + sizeof(int)] == '\0') {
                                /* no data here...resync */
                                buf_index++;
                                num_bytes--;
                                continue;
                            }
                            if (expecting && num_bytes < expecting) {
                                /*
                                 * message truncated...back up to DATALEN message; exit
                                 * loop...and try again later
                                 */
                                buf_index -= (sizeof(int) + sizeof(int));
                                num_bytes += (sizeof(int) + sizeof(int));
                                break;
                            }
                            res = ip_by_name(conn_index, &buf[buf_index]);
                        } else if (msgtype == DATALEN) {
                            if (num_bytes > (sizeof(int) + sizeof(int))) {
                                memcpy((char *) &expecting, (char *) &buf[buf_index + sizeof(int)], sizeof(int));
                                /*
                                 * advance to next message
                                 */
                                buf_index += (sizeof(int) + sizeof(int));
                                num_bytes -= (sizeof(int) + sizeof(int));
                                if (expecting > IN_BUF_SIZE || expecting <= 0) {
                                    fprintf(stderr, "conn_data_handler: bad data length %d\n", expecting);
                                    expecting = 0;
                                }
                                continue;
                            } else {
                                /*
                                 * not enough bytes...assume truncated; exit loop...we'll
                                 * handle this message later
                                 */
                                break;
                            }
                        } else {
                            fprintf(stderr, "conn_data_handler: unknown message type %08x\n", msgtype);
                            /* advance through buffer */
                            buf_index++;
                            num_bytes--;
                            continue;
                        }
                        msglen = (int) (sizeof(int) + strlen(&buf[buf_index + sizeof(int)]) +1);
                        if (res) {
                            /*
                             * ok...advance to next message
                             */
                            buf_index += msglen;
                            num_bytes -= msglen;
                        } else if (msglen < num_bytes || (expecting && expecting == msglen)) {
                            /*
                             * failed...
                             */
                            /*
                             * this was a complete message...advance to the next one
                             */
                            msglen = (int) (sizeof(int) + strlen(&buf[buf_index + sizeof(int)]) +1);
                            buf_index += msglen;
                            num_bytes -= msglen;
                            expecting = 0;
                        }
                        else if (!OS_socket_ioctl(fd, FIONREAD, &unread_bytes) &&
                                unread_bytes > 0) {
                            /*
                             * msglen == num_bytes could be a complete message... if
                             * there's unread data we'll assume it was truncated
                             */
                            break;
                        } else {
                            /*
                             * nothing more?  then discard message (it was the last one)
                             */
                            buf_index = 0;
                            num_bytes = 0;
                            break;
                        }
                    }
                    /* keep track of leftover buffer contents */
                    if (num_bytes && buf_index)
                        memmove(buf, &buf[buf_index], num_bytes);
                    buf[num_bytes] = '\0';
                    all_conns[conn_index].leftover = num_bytes;
                    break;
            }
        }
#define OUT_BUF_SIZE 80
        int ip_by_name (int conn_index, char * buf)
        {
            struct hostent *hp;
            struct in_addr my_in_addr;
            static char out_buf[OUT_BUF_SIZE];
            hp = gethostbyname(&buf[sizeof(int)]);
            if (hp == NULL) {
                /* Failed :( */
                sprintf(out_buf, "%s 0\n", &buf[sizeof(int)]);
                DBG(("%s", out_buf));
                OS_socket_write(all_conns[conn_index].fd, out_buf, strlen(out_buf));
                return 0;
            } else {
                /* Success! */
                memcpy(&my_in_addr, hp->h_addr, sizeof(struct in_addr));
                sprintf(out_buf, "%s %s\n", &buf[sizeof(int)],
                        inet_ntoa(my_in_addr));
                DBG(("%s", out_buf));
                OS_socket_write(all_conns[conn_index].fd, out_buf, strlen(out_buf));
                return 1;
            }
        }				/* ip_by_name() */
        int name_by_ip (int conn_index, char * buf)
        {
            struct addrinfo hints, *res;
            int ret;
            hints.ai_family = AF_INET6;
            hints.ai_socktype = 0;
            hints.ai_protocol = 0;
#if defined(AI_V4MAPPED)
            hints.ai_flags = AI_CANONNAME| AI_V4MAPPED;
#else
            hints.ai_flags = AI_CANONNAME;
#endif
            static char out_buf[OUT_BUF_SIZE];
            if(ret = getaddrinfo(&buf[sizeof(int)], 0, &hints, &res)){
                //failed
                sprintf(out_buf, "%s 0\n", &buf[sizeof(int)]);
                DBG(("name_by_ip: malformed address request (%d).", ret));
                OS_socket_write(all_conns[conn_index].fd, out_buf, strlen(out_buf));
                return 0;
            }
            char tmpbuf[80], tmpp[80];
            if(ret = getnameinfo(res->ai_addr, res->ai_addrlen, tmpbuf, 79, tmpp, 79, NI_NAMEREQD|NI_NUMERICSERV)){
                sprintf(out_buf, "%s 0\n", &buf[sizeof(int)]);
                DBG(("%s", out_buf));
                OS_socket_write(all_conns[conn_index].fd, out_buf, strlen(out_buf));
                DBG(("name_by_ip: unable to resolve address."));
                freeaddrinfo(res);
                return 0;
            }
            sprintf(out_buf, "%s %s\n", &buf[sizeof(int)], tmpbuf);
            DBG(("%s", out_buf));
            OS_socket_write(all_conns[conn_index].fd, out_buf, strlen(out_buf));
            freeaddrinfo(res);
            return 1;
        }
        int index_by_fd (int fd)
        {
            int i;
            for (i = 0; i < MAX_CONNS; i++) {
                if ((all_conns[i].state == CONN_OPEN) && (all_conns[i].fd == fd))
                    return (i);
            }
            return (-1);
        }
        void terminate (int conn_index)
        {
            if (conn_index < 0 || conn_index >= MAX_CONNS) {
                fprintf(stderr, "terminate: conn_index %d out of range.\n", conn_index);
                return;
            }
            if (all_conns[conn_index].state == CONN_CLOSED) {
                fprintf(stderr, "terminate: connection %d already closed.\n", conn_index);
                return;
            }
            DBG(("terminating connection %d", conn_index));
            if (OS_socket_close(all_conns[conn_index].fd) == -1) {
                socket_perror("terminate: close", 0);
                return;
            }
            all_conns[conn_index].state = CONN_CLOSED;
            total_conns--;
        }
        int main (int argc, char ** argv)
        {
            int addr_server_port;
            struct timeval timeout;
            int i;
            int nb;
            char *ipaddress = 0;
            if (argc > 1) {
                if ((addr_server_port = atoi(argv[1])) == 0) {
                    fprintf(stderr, "addr_server: malformed port number.\n");
                    exit(2);
                }
                if (argc > 2) {
                    if (inet_addr((ipaddress = argv[2])) == INADDR_NONE) {
                        fprintf(stderr, "addr_server: malformed ip address.\n");
                        exit(3);
                    }
                }
            } else {
                fprintf(stderr, "addr_server: first arg must be port number.\n");
                exit(1);
            }
            init_conn_sock(addr_server_port, ipaddress);
            while (1) {
                /*
                 * use finite timeout for robustness.
                 */
                timeout.tv_sec = 2;
                timeout.tv_usec = 0;
                /*
                 * clear selectmasks.
                 */
                FD_ZERO(&readmask);
                /*
                 * set new connection accept fd in readmask.
                 */
                FD_SET(conn_fd, &readmask);
                /*
                 * set active fds in readmask.
                 */
                for (i = 0; i < MAX_CONNS; i++) {
                    if (all_conns[i].state == CONN_OPEN)
                        FD_SET(all_conns[i].fd, &readmask);
                }
#ifndef hpux
                nb = select(FD_SETSIZE, &readmask, (fd_set *) 0, (fd_set *) 0, &timeout);
#else
                nb = select(FD_SETSIZE, (int *) &readmask, (int *) 0, (int *) 0, &timeout);
#endif
                if (nb != 0)
                    aserv_process_io(nb);
                process_queue();
            }
            /* the following is to shut lint up */
            /*NOTREACHED*/
            return 0;			/* never reached */
        }
#ifdef WIN32
        void debug_message(char *fmt, ...)
        {
            static char deb_buf[1024];
            static char *deb = deb_buf;
            va_list args;
            V_START(args, fmt);
            V_VAR(char *, fmt, args);
            vfprintf(stderr, fmt, args);
            fflush(stderr);
            va_end(args);
        }
#endif