/* Quake UDP proxy
  Original ipx->udp proxy by [email protected]
  Adapted for udp->udp, other changes by [email protected]

  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, or (at your option)
  any later version.

  compile with "gcc -O2 -o qudproxy qudproxy.c"
  version 1.0
  todo: translate broadcasts, server list

  Note: This program properly handles proxying for multiple clients, but
  as of Quake 0.92 the quake server only allows one client from each IP
  address, so only one player can use the proxy at a time; if another
  player connects to the proxy, the remote server will disconnect the
  first player.  I've complained about this to id.
*/

#include <sys/types.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/wait.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

extern char *optarg;
extern int optind;

#define VERSION "1.0"

#define MAX 4096
unsigned char       msg[MAX];
char *name;

void usage ()
{
 fprintf (stderr, "usage: %s [-l localport] [-r remoteport]", name);
 fprintf (stderr, " [-d[d]] [-h] server\n");
}

int                 main (int argc, char *argv[])
{
 int sfd, cfd, c2fd, n, ucl, i, pid, opt;
 fd_set fds;
 struct sockaddr_in cremote_addr, clocal_addr;
 struct sockaddr_in sremote_addr, slocal_addr;
 struct hostent *host;
 struct timeval tv, *tvp;
 int localport = 26000, remoteport = 26000, debug = 0;

 name = argv[0];

 while (opt = getopt (argc, argv, "l:r:dhv"), opt != -1)
   {
     switch (opt)
       {
       case 'l':
         localport = atoi (optarg);
         break;

       case 'r':
         remoteport = atoi (optarg);
         break;

       case 'd':
         ++debug;
         break;

       case 'v':
         fprintf (stderr, "qudproxy version %s", VERSION);
         return 0;

       default:
         usage ();
         return opt != 'h';
       }
   }

 if (optind != argc - 1)
   {
     usage ();
     return 1;
   }

 ucl = sizeof (struct sockaddr_in);

 bzero (&sremote_addr, ucl);
 sremote_addr.sin_family = AF_INET;
 sremote_addr.sin_port = htons (remoteport);
 n = inet_addr (argv[optind]);
 if (n != -1)
   memcpy (&sremote_addr.sin_addr, &n, sizeof(n));
 else
   {
     host = gethostbyname (argv[optind]);
     if (host == NULL)
       return (-1);
     memcpy (&sremote_addr.sin_addr, host->h_addr, host->h_length);
   }

 bzero (&clocal_addr, ucl);
 clocal_addr.sin_family = AF_INET;
 clocal_addr.sin_port = htons (localport);

 /* Surely there's a better way to determine our IP address.  */
 gethostname (msg, MAX);
 host = gethostbyname (msg);
 memcpy (&clocal_addr.sin_addr, host->h_addr, host->h_length);

 bzero (&slocal_addr, ucl);
 slocal_addr.sin_family = AF_INET;
 slocal_addr.sin_addr.s_addr = htonl(INADDR_ANY);

 cfd = socket (AF_INET, SOCK_DGRAM, 0);
 bind (cfd, (struct sockaddr *) &clocal_addr, ucl);

 sfd = socket (AF_INET, SOCK_DGRAM, 0);
 bind (sfd, (struct sockaddr *) &slocal_addr, ucl);

 tvp = NULL;

 for (;;)
   {
     int i;

     /* this is to exit the spawned processes */
     if (tvp != NULL)
       {
         tv.tv_sec = 15;
         tv.tv_usec = 0;
       }

     FD_ZERO (&fds);
     FD_SET (sfd, &fds);
     FD_SET (cfd, &fds);

     if (0 >= select (1 + (cfd > sfd ? cfd : sfd), &fds, NULL, NULL, tvp))
       return 0;

     /* this should be the 12 byte connect request broadcast */
     if (FD_ISSET (cfd, &fds))
       {
         n = recvfrom (cfd, msg, MAX, 0,
                       (struct sockaddr *) &cremote_addr, &ucl);
         if (n == -1)
           {
             perror ("qudproxy");
             return 1;
           }
         if (debug > 1)
           {
             fprintf (stderr, "client@%d: ", ntohs (cremote_addr.sin_port));
             for (i = 0; i < n; ++i)
               fprintf (stderr, "%x ", msg[i]);
             fflush (stderr);
             write (2, "(", 1);
             write (2, msg, n);
             write (2, ")\n", 2);
           }
         sendto (sfd, msg, n, 0, (struct sockaddr *) &sremote_addr, ucl);
       }

     if (FD_ISSET (sfd, &fds))
       {
         /* SunOS 4.1.3_U1 requires that we pass in &ucl.  Odd.  */
         n = recvfrom (sfd, msg, MAX, 0, NULL, &ucl);
         if (n == -1)
           {
             perror ("qudproxy");
             return 1;
           }
         if (debug > 1)
           {
             fprintf (stderr, "server@%d: ", ntohs (sremote_addr.sin_port));
             for (i = 0; i < n; ++i)
               fprintf (stderr, "%x ", msg[i]);
             fflush (stderr);
             write (2, "(", 1);
             write (2, msg, n);
             write (2, ")\n", 2);
           }

         /* this might be the connect response */
         if (tvp == NULL && msg[0] == 0x80 && msg[1] == 00
             && msg[4] == 0x81)
           {
#if 1
             /* FORK HERE */
             pid = fork ();
             if (!pid)
               {
                 /* spawn again */
                 if (pid = fork (), pid)
                   {
                     if (debug)
                       fprintf (stderr,
                                "Spawning Quake Proxy Handler Pid %d\n",
                                pid);
                     return 0;
                     /* if parent, exit (so init gets grandkid) */
                   }
               }
             else
               {
                 /* wait for kid to exit and continue */
                 waitpid (pid, NULL, 0);
                 /* create new udp socket for next connections */
                 close (sfd);
                 sfd = socket (AF_INET, SOCK_DGRAM, 0);
                 bind (sfd, (struct sockaddr *) &slocal_addr, ucl);
                 continue;
               }
             /* we are in grandkid */
#endif
             sremote_addr.sin_port = htons (msg[5] + (msg[6] << 8));

             clocal_addr.sin_port = 0;
             c2fd = socket (AF_INET, SOCK_DGRAM, 0);
             bind (c2fd, (struct sockaddr *) &clocal_addr, ucl);
             getsockname (c2fd, (struct sockaddr *) &clocal_addr, &ucl);
             msg[5] = ntohs (clocal_addr.sin_port) & 0xff;
             msg[6] = ntohs (clocal_addr.sin_port) >> 8;

             if (debug)
               {
                 fprintf (stderr, "Quake UDP Proxy to %s:%d",
                          inet_ntoa (sremote_addr.sin_addr),
                          ntohs (sremote_addr.sin_port));
#if 0
                 /* We haven't asked for this information with
                    getsockname.  */
                 fprintf (stderr, " from %s:%d",
                          inet_ntoa (slocal_addr.sin_addr),
                          ntohs (slocal_addr.sin_port));
#endif
                 fprintf (stderr, " for %s:%d",
                          inet_ntoa (cremote_addr.sin_addr),
                          ntohs (cremote_addr.sin_port));
                 fprintf (stderr, " at %s:%d\n",
                          inet_ntoa (clocal_addr.sin_addr),
                          msg[5] + (msg[6] << 8));
                 fflush (stderr);
               }

             tvp = &tv;
             sendto (cfd, msg, n, 0, (struct sockaddr *) &cremote_addr, ucl);
             close (cfd);
             cfd = c2fd;
           }
         else
           sendto (cfd, msg, n, 0, (struct sockaddr *) &cremote_addr, ucl);
       }
   }
}