/* puturl.c - micro command line server with authorization
   by tz@execpc.com

 */

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <arpa/nameser.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <resolv.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/fcntl.h>

#ifdef ENABLE_DIRS
#include <dirent.h>
DIR *dfile;
struct dirent *dent;

#ifndef NAME_MAX
#define NAME_MAX 64
#endif
#endif

#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif

#define XBUFSIZ 32768
#define TBUFSIZ 4096
#define SADR (struct sockaddr *)

char mimetab[65] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

void usage()
{
  fprintf(stderr, "usage: upput [options] \n"
          "\t-a ip.address           use this address to listen"
          "\t-d                      debug (print more messages)\n"
          "\t-l path/                prepend to posted files\n"
          "\t-p port                 Use specific port\n"
          "\t-t content/type         use mime type when transmitting\n"
          "\t-u                      Require auth with u:p from ~/.upputauth\n"
          "\t-v                      Log the request headers to stderr\n"
          "\t-z                      strip headers from POSTed files\n");
  exit(-1);
}

static char contbuf[TBUFSIZ];
static char xferbuf[XBUFSIZ];
static char headbuf[4096];

int net;
int debugflag = 0;

#include <sys/signal.h>
#ifdef LINUX
__sighandler_t sighandle(int signum, __sighandler_t h)
#else
#define __sighandler_t void *
void *sighandle(int signum)
#endif
{
  close(net);
  exit(-1);
}

int main(int argc, char *argv[])
{
  char authstr[256];
  char path[1024];
  char postpath[1024];
  char *ip, *op;
  char contype[128];
  int n, c, argp;
  int net2;
  int siteport = 0;
  int headers = 1;
  time_t now;
  struct timeval tv;
  fd_set fds;
  struct sockaddr_in sin;
  unsigned long temp;
  FILE *fp = NULL;

  int logflag = 0;
  int tfile;

  struct stat filestats;

  /* set sane defaults */
  authstr[0] = 0;
  memset((char *) &sin, 0, sizeof(sin));
  sin.sin_addr.s_addr = INADDR_ANY;
  sin.sin_family = AF_INET;     /* host->h_addrtype; */
  strcpy(postpath, "./");
  strcpy(contype, "application/octet-stream");

  argp = 1;
  while (argp < argc) {
    if (argv[argp][0] == '-')
      switch (argv[argp][1]) {
      case 'a':
        sin.sin_addr.s_addr = inet_addr(argv[++argp]);
        argp++;
        break;
      case 'd':
        argp++;
        debugflag = 1;
        break;
      case 'h':
        usage();
        exit(0);
      case 'l':
        argp++;
        strcpy(postpath, argv[argp++]);
        break;
      case 'p':
        argp++;
        siteport = atoi(argv[argp++]);
        break;
      case 't':
        argp++;
        strcpy(contype, argv[argp++]);
        break;
      case 'u':
        /* create authorization string */
        argp++;
        ip = getenv("HOME");
        sprintf(authstr, "%s/.upputauth", ip);
        fp = fopen(authstr, "r");
        if (fp == NULL) {
          fprintf(stderr, "file %s not found\n", authstr);
          exit(-1);
        }
        fscanf(fp, "%s", authstr);
        fclose(fp);

        /* convert into MIME bin64 */
#if 0
        ip = argv[argp++];
#else
        ip = authstr;
#endif
        op = contbuf;
        c = strlen(ip);

        for (temp = 0; temp < c; temp += 3) {
          *op++ = mimetab[*ip >> 2];
          *op++ = mimetab[((*ip << 4) & 0x30) | ((ip[1] >> 4) & 0x0f)];
          *op++ = mimetab[((ip[1] << 2) & 0x3c) | ((ip[2] >> 6) & 0x03)];
          *op++ = mimetab[ip[2] & 0x3f];
          ip += 3;
        }

        if (temp >= c + 1)
          op[-1] = '=';
        if (temp == c + 2)
          op[-2] = '=';
        *op = '\0';

        /* create full auth string */
        strcpy(authstr, "Authorization: Basic ");
        strcat(authstr, contbuf);
        strcat(authstr, "\r\n");

        break;
      case 'v':
        argp++;
        logflag = 1;
        break;
      case 'z':
        argp++;
        headers = 0;
        break;
      default:

        usage();
    } else
      usage();
  }

  sin.sin_port = htons(siteport);
  if (debugflag)
    fprintf(stderr, "Port: %d\n", htons(sin.sin_port));

  /* enable the secure socket layer */

  net = socket(AF_INET, SOCK_STREAM, 0);
  if (net < 0)
    return net;

  if (setsockopt(net, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(int)) < 0)
      exit(-1);

  if (bind(net, SADR & sin, sizeof(sin)) < 0)
    exit(-1);

  n = sizeof(sin);
  getsockname(net, SADR & sin, &n);
  fprintf(stderr, "Using TCP Port %u\n", htons(sin.sin_port));
#ifdef LINUX
  signal(SIGINT, (__sighandler_t) sighandle);
#endif
  listen(net, 1);

  for (;;) {
    fprintf(stderr, "Waiting for connection...\n");
    net2 = accept(net, SADR & sin, &n);
    fprintf(stderr, "Connect from %s:%u\n",
            inet_ntoa(sin.sin_addr),
            htons(sin.sin_port)
      );

    xferbuf[0] = 0;
    headbuf[0] = 0;
    c = 0;
    while (NULL == strstr(xferbuf, "\r\n\r\n")) {
      FD_ZERO(&fds);
      FD_SET(net2, &fds);
      tv.tv_sec = 15;
      tv.tv_usec = 0;
      {
        n = select(net2 + 1, &fds, NULL, NULL, &tv);
        if (n <= 0)
          break;
        n = read(net2, &xferbuf[c], XBUFSIZ - c);
      }
      if (n <= 0)
        break;
      c += n;
      xferbuf[c] = 0;
    }

    ip = strstr(xferbuf, "\r\n\r\n");
    if (ip != NULL) {
      if (logflag) {
        *ip = 0;
        fprintf(stderr, "Got Header -----\n%s\n\n", xferbuf);
        *ip = '\r';
      }
    } else {
      fprintf(stderr, "Connection broken before header received\n");
      headbuf[0] = 0;
      xferbuf[0] = 0;
    }

    if (!strncmp("GET /", xferbuf, 5) ||
        !strncmp("POST /", xferbuf, 6)) {

      if (strlen(authstr) && strstr(xferbuf, authstr) == NULL) {
        fprintf(stderr, "authorization failed\n");

        sprintf(headbuf,
                "HTTP/1.0 401 Access Denied\r\n"
                "WWW-Authenticate: Basic realm=\"upput\"\r\n"
                "Content-Length: 24\r\n\r\n"
                "Error: Access is Denied.");
        xferbuf[0] = 0;
      }
    } else {
      fprintf(stderr, "Unknown Request:\n%80.80s\n", xferbuf);
      sprintf(headbuf, "HTTP/1.0 400 Bad Request\r\n\r\n");
    }
    if (!strncmp("GET /", xferbuf, 5)) {
      strncpy(path, &xferbuf[5], 1024);
      op = strstr(path, " ");
      *op = 0;
      if (path[0] == 0)
        strcpy(path, ".");

      if (stat(path, &filestats) < 0)
        sprintf(headbuf, "HTTP/1.0 404 Not Found\r\n\r\n");
      else {
#ifdef ENABLE_DIRS
        if (S_ISDIR(filestats.st_mode)) {

          dfile = opendir(path);
          if (dfile == NULL)
            sprintf(headbuf, "HTTP/1.0 403 Forbidden\r\n\r\n");
          else {
            n = 0;
            sprintf(&xferbuf[n],
                    "<HTML><HEAD><TITLE>Directory of %s</TITLE>"
                    "</HEAD><BODY><UL>\r\n", path);
            n = strlen(xferbuf);
            for (;;) {
              dent = readdir(dfile);
              if (dent == NULL)
                break;
              sprintf(&xferbuf[n], "<LI><A href=/%s/%s>%s</A>\r\n",
                      path, dent->d_name, dent->d_name);
              n = strlen(xferbuf);
              if (n + NAME_MAX + 64 > XBUFSIZ)
                break;
            }
            sprintf(&xferbuf[n], "</UL></BODY></HTML>\r\n");
            n = strlen(xferbuf);
            closedir(dfile);
          }

          time(&now);
          sprintf(headbuf,
                  "HTTP/1.0 200 OK\r\n"
                  "Date: %s"
                  "Server: UPPut 0.6\r\n"
                  "Content-type: text/html\n"
                  "Last-Modified: %s"
                  "Content-length: %d\r\n\r\n",
                  ctime(&now), ctime(&(filestats.st_mtime)), n);

          if (debugflag)
            fprintf(stderr, "Sending dir :\n%s", headbuf);
          else
            fprintf(stderr, "Sending dir : %s\n", path);
          /* write the header to the remote site */
          headbuf[0] = 0;
          write(net2, headbuf, strlen(headbuf));
          write(net2, xferbuf, n);
        } else
#endif /* ENABLE_DIRS */
        {
          tfile = open(path, O_RDONLY);
          if (tfile < 0)
            sprintf(headbuf, "HTTP/1.0 403 Forbidden\r\n\r\n");
          else {
            time(&now);
            sprintf(headbuf,
                    "HTTP/1.0 200 OK\r\n"
                    "Date: %s"
                    "Server: UPPut 0.6\r\n"
                    "Content-type: %s\n"
                    "Last-Modified: %s"
                    "Content-length: %d\r\n\r\n",
                    ctime(&now), contype, ctime(&(filestats.st_mtime)),
                    (int) filestats.st_size);

            if (debugflag)
              fprintf(stderr, "Sending:\n%s", headbuf);
            else
              fprintf(stderr, "Sending: %s\n", path);
            /* write the header to the remote site */
            write(net2, headbuf, strlen(headbuf));
            headbuf[0] = 0;
            n = XBUFSIZ;
            for (;;) {
              n = read(tfile, xferbuf, XBUFSIZ);
              if (n <= 0)
                break;
              write(net2, xferbuf, n);
            }
            close(tfile);
          }
        }
      }
    }
    if (!strncmp("POST /", xferbuf, 6)) {
      strcpy(path, postpath);
      strncat(path, &xferbuf[6], 1024);
      op = strstr(path, " ");
      *op = 0;

      tfile = open(path, O_CREAT | O_WRONLY | O_EXCL, 0777);
      if (tfile < 0)
        sprintf(headbuf, "HTTP/1.0 403 Forbidden\r\n\r\n");
      else {
        n = c;
        ip = 6 + strstr(xferbuf, "ength:");
        sscanf(ip, " %d", &c);
        fprintf(stderr, "Receiving: %s (%d)\n", path, c);
        ip = 4 + strstr(xferbuf, "\r\n\r\n");
        c += ip - xferbuf;
        c -= n;
        if (headers)
          write(tfile, xferbuf, n);
        else
          write(tfile, ip, n - (ip - xferbuf));

        while (c) {
          FD_ZERO(&fds);
          FD_SET(net2, &fds);
          tv.tv_sec = 15;
          tv.tv_usec = 0;
          {
            n = select(net2 + 1, &fds, NULL, NULL, &tv);
            if (n <= 0)
              break;
            n = read(net2, xferbuf, XBUFSIZ);
          }
          if (n <= 0)
            break;
          write(tfile, xferbuf, n);
          c -= n;
        }
        close(tfile);
        sprintf(headbuf, "HTTP/1.0 202 Accepted\r\n\r\n");
      }
    }
    if (headbuf[0])
      write(net2, headbuf, strlen(headbuf));
    close(net2);

  }
  return 0;
}
