/*
**  L2 - OSSP Logging Library
**  Copyright (c) 2001 The OSSP Project (http://www.ossp.org/)
**  Copyright (c) 2001 Cable & Wireless Deutschland (http://www.cw.com/de/)
**
**  This file is part of OSSP L2, a flexible logging library which
**  can be found at http://www.ossp.org/pkg/l2/.
**
**  Permission to use, copy, modify, and distribute this software for
**  any purpose with or without fee is hereby granted, provided that
**  the above copyright notice and this permission notice appear in all
**  copies.
**
**  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
**  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
**  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
**  IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR
**  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
**  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
**  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
**  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
**  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
**  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
**  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
**  SUCH DAMAGE.
**
**  l2_sockmon.c: Socket monitor for use with l2_test.c
*/

#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>

#include "l2.h"
#include "l2_ut_sa.h"

#define BACKLOG              1024      /* max # pending connections */
#define BUFFSIZ               256      /* max string size           */
#define L2_SA_SOCKET_TIMEOUT   64
#define L2_SA_SOCKET_BUFSIZE 4096

char *strlowerdup(const char *);       /* prototypes */
void dowork(int);
int  myserver(int, int, int);
void die(const char *);
void closedie(sa_t *, const char *);
void usage(char *);

void die(const char *szMsg)
{
    perror(szMsg);
    exit(EXIT_FAILURE);
}

void closedie(sa_t *saSock, const char *szMsg)
{
    perror(szMsg);
    sa_destroy(saSock); /* saSock->destroy would really be nice here */
    exit(EXIT_FAILURE);
}

char *strlowerdup(const char *szIn)
{
    int  i       = 0;
    char *szTemp = NULL;

    assert(szIn != NULL);

    if ((szTemp = (char *)strdup(szIn)) == NULL)
        return NULL;
    else
    {
        for (i = 0; *(szTemp + i); i++)
            if (isupper(*(szTemp + i)))
                *(szTemp + i) = (char)tolower(*(szTemp + i));
        return szTemp;
    }
}

void dowork(int iSock)
{
    char szBuf[BUFFSIZ];
    int  cc;

    while (1) { /* Exits when read finishes */
        cc = read(iSock, szBuf, sizeof (szBuf));
        if (cc == -1)
            die("read()");

        if (cc == 0) {
            /* EOF */
            close(iSock);
            fprintf(stderr, "Connection closed\n");
            exit(EXIT_SUCCESS);
        }

        szBuf[cc + 1] = '\0';
        fprintf(stderr, "Read: %s", szBuf);

        if (write(iSock, szBuf, cc) == -1)
            die("write()");
    }
}

int myserver(int iFamily, int iProtocol, int iPort)
{
    sa_type_t  saType;
    sa_addr_t *saAddr;
    sa_t      *saListen, saAccept;
    int        iSock,    iSockopt;

    char                szBuf[BUFFSIZ];
    char                szAddrin4[INET_ADDRSTRLEN];
    char                szAddrin6[INET6_ADDRSTRLEN];
    struct sockaddr_in  faddr4,    caddr4;
    struct sockaddr_in6 faddr6,    caddr6;
    int                 iNewsock;
    int                 cc;
    socklen_t           faddrlen;

    /* Create socket address, the sa_addr object */
    /* starts in a state similar to INADDR_ANY   */
    if (sa_addr_create(&saAddr) != SA_OK)
        die("sa_addr_create()");

    /* Create socket */
    if (sa_create(&saListen) != SA_OK)
        closedie(saListen, "sa_create()");

    if (!(iProtocol == IPPROTO_UDP || iProtocol == IPPROTO_TCP))
        closedie(saListen, "unknown protocol");
    saType = (iProtocol == IPPROTO_UDP) ? SA_TYPE_DATAGRAM : SA_TYPE_STREAM;

    /* Configure socket parameters */
    sa_timeout(saListen, L2_SA_SOCKET_TIMEOUT, 0);
    sa_buffers(saListen, L2_SA_SOCKET_BUFSIZE, L2_SA_SOCKET_BUFSIZE);
    sa_type   (saListen, saType);

    /* Allow local addresses to be reused */
    if (sa_getfd(saListen, &iSock) != SA_OK)
        closedie(saListen, "sa_getfd()");
    iSockopt = 1;
    if (setsockopt(iSock, SOL_SOCKET, SO_REUSEADDR, (void *)&iSockopt, sizeof (iSockopt)) == -1)
        closedie(saListen, "setsockopt()");

    if (sa_bind(saListen, saAddr) != SA_OK)
        closedie(saListen, "sa_bind()");

    if (iProtocol == IPPROTO_UDP) {
        if (iFamily == AF_INET6) {
            /* initialize address structure */
            memset(&caddr6,  0, sizeof(caddr6));

            for (;;) { /* Receive messages */
                faddrlen = sizeof(caddr6);
                memset(szBuf, 0x0, BUFFSIZ); /* Init the buffer */
                cc = recvfrom(iSock, szBuf, BUFFSIZ, 0, (struct sockaddr *)\
                    &caddr6, &faddrlen);

                if (cc == -1)
                    die("recvfrom()");

                if (cc == 0) { /* EOF */
                    close(iSock);
                    fprintf(stderr, "Connection closed\n");
                    return 0;
                }

                if (cc != BUFFSIZ) {
                    szBuf[cc + 1] = '\0';
                    fprintf(stderr, "Read: %s", szBuf);

                    if (write(iSock, szBuf, cc) == -1)
                        die("write()");
                }
                else
                    return -1;
            }
        }
        else if (iFamily == AF_INET) {
            /* initialize address structure */
            memset(&caddr4, 0, sizeof(caddr4));

            for (;;) { /* Receive messages */
                faddrlen = sizeof(caddr4);
                memset(szBuf, 0x0, BUFFSIZ); /* Init the buffer */
                cc = recvfrom(iSock, szBuf, BUFFSIZ, 0, (struct sockaddr *)\
                    &caddr4, &faddrlen);

                if (cc == -1)
                    die("recvfrom()");

                if (cc == 0) { /* EOF */
                    close(iSock);
                    fprintf(stderr, "Connection closed\n");
                    return 0;
                }

                if (cc != BUFFSIZ) {
                    szBuf[cc + 1] = '\0';
                    fprintf(stderr, "Read: %s", szBuf);

                    if (write(iSock, szBuf, cc) == -1)
                        die("write()");
                }
                else
                    return -1;
            }
        }
    }
    else if (iProtocol == IPPROTO_TCP) { /* Only try to listen if we have TCP */
        if (sa_listen(saListen, BACKLOG) != SA_OK)
            closedie(saListen, "sa_listen()");

        /* Wait for a connection request */
        if (iFamily == AF_INET6) {
            /* initialize address structure */
            memset(&faddr6,  0, sizeof(faddr6));

            for (;;) {
                faddrlen = sizeof (faddr6);
                if (sa_accept(saListen, &saAddr, &saAccept) != SA_OK) {
                    sa_destroy(saAccept);
                    closedie(saListen, "sa_accept()");
                }

                if (sa_addr_a2u(saAddr, char **uri))
                if (inet_ntop(AF_INET6, &faddr6.sin6_addr, szAddrin6, sizeof(szAddrin6)) != NULL) {
                    fprintf(stderr, "Connection from %s/%d\n", szAddrin6, ntohs(faddr6.sin6_port));
                    dowork(iNewsock); /* do some sockwork */
                }
                else
                    die("inet_ntop()");
            }
        }
        else if (iFamily == AF_INET) {
            /* initialize address structure */
            memset(&faddr4,  0, sizeof(faddr4));

            for (;;) {
                faddrlen = sizeof (faddr4);
                iNewsock = accept(iSock, (struct sockaddr *)&faddr4, &faddrlen);
                if (iNewsock == -1) {
                    if (errno != EINTR && errno != ECONNABORTED) {
                        perror("accept()");
                    }
                    continue;
                }
                inet_ntop(AF_INET6, &faddr4.sin_addr, szAddrin4, sizeof(szAddrin4));
                fprintf(stderr, "Connection from %s/%d\n", szAddrin4, ntohs(faddr6.sin6_port));
                dowork(iNewsock); /* do some sockwork */
            }
        }
    } /* NOTREACHED */
    return -1; /* Failure due to non UDP/TCP request, or execution flow */
}

void usage(char *szApp)
{
    fprintf(stderr, "Usage: %s <IPv4-or-IPv6> <TCP-or-UDP> <Port>\n", szApp);
    exit(EXIT_FAILURE); /* The user doesn't know how to use this */
}

int main(int argc, char *argv[])
{
    int iFam      = -1;
    int iProto = -1;
    int iPort  = 0;

    if (argc != 4)
        usage(argv[0]);

    if (!strcmp(argv[1], "IPv6"))
        iFam = AF_INET6;
    else if (!strcmp(argv[1], "IPv4"))
        iFam = AF_INET;
    else
        usage(argv[0]);

    if (!strcmp(argv[2], "UDP"))
        iProto = IPPROTO_UDP;
    else if (!strcmp(argv[2], "TCP"))
        iProto = IPPROTO_TCP;
    else
        usage(argv[0]);

    iPort = htons(atoi(argv[3]));
    return myserver(iFam, iProto, iPort);
}
