/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: metaserver.c,v 1.18 2001/02/15 08:39:45 drscholl Exp $ */

/* a simple napster metaserver.  redirects clients to a specific set of
   servers */

/* Modified 29/05/01 : Added include "confdefs.h" since we are not using the
      for Amiga port : CONFIGURE script

                       Added define for htons(), which is not defined in the
                       Amiga GCC 2.7.0 includes

                       Added value for __auto_socket_vers to permit automatic
                       opening/closing of bsdsocket.library by socket.o

                       Added include "extrasocket.h" with inlines for a few
                       bsdsocket.library functions not emulated by ixemul

                       Removed openlog() and closelog() calls, not needed by
                       bsdsocket.library

            30/05/01 : Added varargs stub syslog() outside extrasocket.h. I
                       intended to have this in extrasocket.h itself, but in
                       another file there is a conflicting declaration of
                       syslog().
*/

#include "confdefs.h"

#define htons(x) (x)

unsigned long __auto_socket_vers = 4;

#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#if HAVE_POLL
#include <sys/poll.h>
#endif /* HAVE_POLL */
#include <syslog.h>

#include "extrasocket.h"

/* varargs stub for syslog() */

void syslog(int level, const char * format, ...)
{
    vsyslog(level, format, (&format) + 1);
}

#ifdef __CYGWIN__
extern char *optarg;
extern int optind;
#endif

char    metafile[_POSIX_PATH_MAX];
char   *hosts[64];
int     numhosts = 0;
volatile int sig_meta = 0;

#ifndef HAVE_SOCKLEN_T
typedef unsigned int socklen_t;
#endif

static void
handler (int sig)
{
    if (sig == SIGHUP)
        sig_meta++;
}

static void
usage (void)
{
    puts
        ("usage: metaserver [ -fsv ] [ -c CONFIG ] [ -l IP ] [ -p <port> ] [ host:port ... ]");
    puts ("  -c CONFIG  use list of servers in file CONFIG");
    puts ("  -f         run the metaserver in the background");
    puts ("  -v         display version number and exit");
    puts ("  -l IP              listen only on interface for IP");
    puts ("  -p <port>  listen for connection on <port> (default is 8875)");
    puts ("  -s         disable syslog messages");
    puts ("\n  if no arguments are given, defaults to 127.0.0.1:8888");
    exit (1);
}

static void
read_metafile (char *filename)
{
    char    buffer[90];

    FILE   *fp;

    fp = fopen (filename, "r");
    if (!fp)
        return;
    while (fgets (buffer, sizeof (buffer) - 1, fp))
    {
        buffer[strlen (buffer) - 1] = 0;
        if (strlen (buffer) > 0 && buffer[0] != '#')
            hosts[numhosts++] = strdup (buffer);
    }
    fclose (fp);
}

int
main (int argc, char **argv)
{
    struct sockaddr_in sin;
    int     i, s, f, port = 8875;
    int     location = 0;
    int     sys_log = 1;
    int     background = 0;

#if HAVE_POLL
    struct pollfd ufd;
#else
    fd_set  set;
#endif /* HAVE_POLL */
    socklen_t sinsize;
    struct sigaction sa;
    unsigned int iface = INADDR_ANY;

    metafile[0] = 0;

    while ((i = getopt (argc, argv, "hfl:vp:c:s")) != EOF)
    {
        switch (i)
        {
        case 'l':
            iface = inet_addr (optarg);
            break;
        case 'p':
            port = atoi (optarg);
            break;
        case 'c':
            strcpy (metafile, optarg);
            break;
        case 'f':
            background ^= 1;
            break;
        case 's':
            sys_log ^= 1;
            break;
        case 'v':
            printf ("%s metaserver version %s\n", PACKAGE, VERSION);
            exit (1);
        default:
            usage ();
        }
    }

    /* read in the host list */
    if (!metafile[0])
    {
        if (!argv[optind])
            hosts[numhosts++] = strdup ("127.0.0.1:8888");      /* use default host */
        else
        {
            while (argv[optind])
            {
                hosts[numhosts++] = strdup (argv[optind]);
                optind++;
            }
        }
    }
    else
        read_metafile (metafile);

    /* set some signal handlers so we can shut down gracefully */
    memset (&sa, 0, sizeof (sa));
    sa.sa_handler = handler;
    sigaction (SIGINT, &sa, 0);
    sigaction (SIGTERM, &sa, 0);

#ifndef __CYGWIN__
    sa.sa_flags = SA_RESTART;
#endif
    sigaction (SIGHUP, &sa, NULL);

    memset (&sin, 0, sizeof (sin));
    sin.sin_port = htons (port);
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = iface;

    s = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (s < 0)
    {
        perror ("socket");
        exit (1);
    }
    if (bind (s, (struct sockaddr *) &sin, sizeof (sin)) < 0)
    {
        perror ("bind");
        exit (1);
    }
    if (listen (s, 50) < 0)
    {
        perror ("listen");
        exit (1);
    }

    if (background && fork ())
        _exit (0);

/*    if (sys_log)
        openlog ("metaserver", LOG_PID, LOG_LOCAL6); */

    location = 0;
#if HAVE_POLL
    memset (&ufd, 0, sizeof (ufd));
    ufd.fd = s;
    ufd.events = POLLIN | POLLHUP;
#endif /* HAVE_POLL */
    for (;;)
    {
#if HAVE_POLL
        i = poll (&ufd, 1, -1);
#else
        FD_ZERO (&set);
        FD_SET (s, &set);
        i = select (s + 1, &set, 0, 0, 0);
#endif /* HAVE_POLL */
        if (i == -1)
        {
            /* re-read configuration files */
            if (sig_meta)
            {
                for (i = 0; i < numhosts; i++)
                    free (hosts[i]);
                numhosts = 0;
                location = 0;
                sig_meta = 0;
                read_metafile (metafile);
                continue;
            }
            perror (
#if HAVE_POLL
                       "poll"
#else
                       "select"
#endif
                );
            break;
        }

        sinsize = sizeof (sin);
        f = accept (s, (struct sockaddr *) &sin, &sinsize);
        if (f < 0)
        {
            perror ("accept");
            break;
        }
        write (f, hosts[location], strlen (hosts[location]));
        write (f, "\n", 1);
        if (sys_log)
            syslog (LOG_ERR, "Redirecting %s to %s", inet_ntoa (sin.sin_addr),
                    hosts[location]);
        location = (location + 1) % numhosts;
        close (f);
    }
    close (s);
/*    closelog (); */
    exit (0);
}
