#define _ALL_SOURCE /* required by Interix */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <errno.h>
#ifdef HAVE_MMAP
#include <sys/mman.h>
#endif // HAVE_MMAP
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h> // TCP_NODELAY
#include <netinet/in.h> // sockaddr_in


// compile with options -lm for math library
// example:  gcc quoted.c -lm -DHAVE_MMAP -o quoted


#define QOTD_DEFAULT_PORT 17
#define LISTEN_BACKLOG 4 /* up to 4 pending connections */

#ifndef NULL
#define NULL ((void *)0)
#endif // NULL

const char *pszAppName = NULL;  // assigned by 'main()'
int bKillFlag = 0;              // set to '1' if the a daemon should shut down

extern char *optarg;  // for getopt
extern int optind;
extern int optopt;
extern int opterr;
extern int optreset;

#ifndef FALSE
#define FALSE 0
#endif // FALSE
#ifndef TRUE
#define TRUE !FALSE
#endif // TRUE

int usage();
int DoQuote(int hOut, int nQuote, struct sockaddr_in *pSA);
int Daemonize(int bDaemon, int iPort, long nQuote);
void SignalProc(int iSig);

#ifndef MAX
#define MAX(X,Y) ((X) >= (Y) ? (X) : (Y))
#endif // MAX

int main(int argc, char *argv[], char *envp[])
{
int iRval, iPort = QOTD_DEFAULT_PORT;
long nQuote = -1;
int nOpt, bDaemon = FALSE, bDebug = FALSE;

  pszAppName = argv[0];  // always capture the application name

  // step 1:  get options

  while((nOpt = getopt(argc, argv, "dDp:h")) != -1)
  {
    switch(nOpt)
    {
      case 'd':  // daemonize
        bDaemon = TRUE;
        break;

      case 'D':  // 'debug' daemonize
        bDebug = TRUE;  // like daemonize, but no daemon
        break;

      case 'p':  // port to listen on (only if 'daemonize' is in effect
        if(!optarg)
          iPort = -1;
        else if(*optarg = ':')  // normally this will happen
          iPort = atoi(optarg + 1);
        else
          iPort = atoi(optarg);

        if(iPort < 1 || iPort > 65535)
        {
          fprintf(stderr, "Error:  illegal port specified (%d)\n", iPort);
          return(usage());
        }
        break;

      case 'h':
        return(usage());

      default:
        fprintf(stderr, "Illegal or unrecognized option\n");
        return(usage());
    }
  }

  argc -= optind;
  argv += optind;  // updates them so that the next arg is after the switches

  if(argc > 0)  // anything left?
  {
    // did we specify a quote number?
    nQuote = atoi(argv[0]);

    if(nQuote <= 0)
    {
      return(usage());
    }
  }

  iRval = 1;                 // set non-blocking mode
  ioctl(0, FIONBIO, &iRval); // should help UDP

  if(!bDaemon && !bDebug)
  {
    int iOpt, nOptLen;

    // see if I'm a UDP socket, and if I am, I'll need to do a 'recvfrom'
    // and pass that information to 'DoQuote'

    nOptLen = sizeof(iOpt);
    if(!getsockopt(0, SOL_SOCKET, SO_TYPE, &iOpt, &nOptLen) &&
       iOpt == SOCK_DGRAM)
    {
      struct sockaddr_in saR;
      bzero(&saR, sizeof(saR));
      nOptLen = sizeof(saR);

      if((iRval = recvfrom(0, &iOpt, 1, MSG_PEEK, (struct sockaddr *)&saR, &nOptLen)) >= 0)
      {
        if(iRval > 0)
        {
          // eat all incoming packets (or inetd will have trouble with it)

          while(recv(0, &iOpt, 1, MSG_PEEK) > 0)
            recv(0, &iOpt, 1, 0);
        }

        iRval = DoQuote(1, nQuote, &saR);
      }
      else
        iRval = -1;
    }
    else
      iRval = DoQuote(1, nQuote, NULL);

    return(iRval);
  }

  // daemonize

  signal(SIGHUP, SignalProc);
  signal(SIGABRT, SignalProc);
  signal(SIGTERM, SignalProc);
  signal(SIGKILL, SignalProc);
  signal(SIGCHLD, SignalProc);
//  signal(SIGIO, SignalProc);

  // TODO:  daemonize

  iRval = Daemonize(bDaemon, iPort, nQuote);

  // restore signal handlers

  signal(SIGHUP, SIG_DFL);
  signal(SIGABRT, SIG_DFL);
  signal(SIGTERM, SIG_DFL);
  signal(SIGKILL, SIG_DFL);
  signal(SIGCHLD, SIG_DFL);
//  signal(SIGIO, SIG_DFL);

  return(iRval);  // exit
}

int usage()
{
  fprintf(stderr, "USAGE:  quoted [-h][-[d|D] [-p:port]] [n]\n"
          " where\n"
          "        'n' is a 1-based integer specifying the quote number\n"
          "  and\n"
          "        '-d' instructs 'quoted' to run as a daemon\n"
          "  and\n"
          "        '-D' is like '-d' but the process does not detach (for test purposes)\n"
          "  and\n"
          "        '-p:port' indicates which port to listen on (default is 17)\n"
          "  and\n"
          "        '-h' prints this message\n"
          "\n"
          "When 'quoted' runs as a daemon it will listen on both tcp and udp sockets.\n"
          "\n"
          "To run 'quoted' as a service for inetd, you can add the following to inetd.conf:\n"
          "qotd    stream  tcp     nowait  NULL    /usr/local/bin/quoted   quoted\n"
          "qotd    dgram   udp     wait    NULL    /usr/local/bin/quoted   quoted\n"
          "\n");
  return(1);
}

void SignalProc(int iSig)
{
int i1;
pid_t pid1;

  // signal handling is required for daemons

  switch(iSig)
  {
    case SIGCHLD:  // child process just did something...
      if((pid1 = waitpid((pid_t)-1, &i1, WNOHANG)) > 0)
      {
        // TODO:  log process ID pid1 returns code i1
      }
      break;

    case SIGABRT:
    case SIGTERM:
    case SIGKILL:
    case SIGHUP:
      bKillFlag = TRUE;
      break;

    case SIGIO:
      // TODO:  something?
      break;
  }
}

void WriteStr(int hOut, const char *szString, struct sockaddr_in *pSA)
{
  if(pSA)
    sendto(hOut, szString, strlen(szString), 0, (struct sockaddr *)pSA, sizeof(*pSA));
  else
    write(hOut, szString, strlen(szString));
}

int DoQuote(int hOut, int nQuote, struct sockaddr_in *pSA)
{
int hQuotes = -1;  // handle to 'quotes' file
const char *pBuf = NULL, *p1, *p2, *pEnd = NULL;
long nBytes, nPercents;

  hQuotes = open(QUOTES_FILE_PATH,O_RDONLY);
    // this file is not locked in any way

  if(hQuotes == -1) // error - try /usr/local/etc
    hQuotes = open("/usr/local/etc/quotes", O_RDONLY);

  if(hQuotes == -1) // error - try /etc
    hQuotes = open("/etc/quotes", O_RDONLY);

  if(hQuotes == -1)
  {
    WriteStr(hOut, "Quotes file missing\n", pSA);
    return(2);
  }

  nBytes = lseek(hQuotes, 0, SEEK_END);  // get file size
  lseek(hQuotes, 0, SEEK_SET); // back to beginning

  // memory map the file so that I can get a list of '%' and
  // randomly select the one I want.  This will be way faster.

#ifdef HAVE_MMAP
  pBuf = (const char *)mmap(NULL, nBytes, PROT_READ,
                            MAP_PRIVATE, hQuotes, 0); 
#else // HAVE_MMAP
  pBuf = (const char *)malloc(nBytes);
#endif // HAVE_MMAP

  if(!pBuf)
  {
    close(hQuotes);
    WriteStr(hOut, "Insufficient memory to process request\r\n", pSA);
    return(2);
  }

#ifndef HAVE_MMAP
  if(read(hQuotes, (char *)pBuf, nBytes) != nBytes)
  {
    close(hQuotes);
    free((void *)pBuf);
    WriteStr(hOut, "Read error on 'quotes' file, unable to process request\n", pSA);
    return(3);
  }
#endif // HAVE_MMAP

  pEnd = pBuf + nBytes;

  // now, find out how many '%' symbols there are.  If the file doesn't
  // end in a '%', assume it does and add an extra...

  nPercents = 0;
  p1 = pBuf;

  while(p1 < pEnd)
  {
    // strip any blank lines

    while(p1 < pEnd && *p1 <= ' ')
    {
      const char *p2 = p1;
      while(p1 < pEnd && *p1 != '\n')
      {
        if(*p1 > ' ')  // not white space
          break;
      }
      if(p1 >= pEnd || *p1 != '\n')
      {
        p1 = p2;  // restore it
        break;
      }

      p1++;  // it must be a newline, so skip to next char 
    }

    if(p1 >= pEnd)
      break;

    if(*p1 == '%')  // percent found, so skip this entire line
    {
      while(p1 < pEnd && *p1 != '\n')
        p1++;
      if(p1 < pEnd && *p1 == '\n')
        p1++;
    }

    nPercents ++;  // increment this anyway
 
    if(p1 >= pEnd)  // end of file?
      break;        // yes, so that's all she wrote


    // now look for an ending '%'

    while(p1 < pEnd && 
          (*p1 != '%' ||
           (p1 > pBuf && *(p1 - 1) != '\n')))
    {
      p1++;  // look for '%' as first character on a line
    }

    if(p1 >= pEnd)
    {
      nPercents ++;  // increment as though one WERE there
      break;
    }
  }

//  printf("temporary:  number of quotes found is %d\n", nPercents - 1);

  // at this point 'nPercents' is 1 + the number of entries.  So I
  // can use that as my random number calculation thingy, from which I
  // shall truly get a truly random quote.

  if(nQuote <= 0)
  {
    nQuote = time(NULL);  // first get time
    nQuote = ((nQuote >> 16) & 0xffffL)
           | ((nQuote << 16) & 0xffff0000L);  // swap words

    srandom(nQuote ^ time(NULL));  // really randomize the random function 

    nQuote = 1 + (int)floor((double)(random() & LONG_MAX)
                            * (nPercents - 1)
                            / LONG_MAX );
  }

  if(nQuote < 1 || nQuote >= nPercents)
    nQuote = 1;  // default it, so nothing breaks

  p1 = pBuf;
  nPercents = 0;  // reset to zero for this next part

  while(p1 < pEnd)
  {
    // strip any blank lines

    while(p1 < pEnd && *p1 <= ' ')
    {
      p2 = p1;
      while(p1 < pEnd && *p1 != '\n')
      {
        if(*p1 > ' ')  // not white space
          break;
      }
      if(p1 >= pEnd || *p1 != '\n')
      {
        p1 = p2;  // restore it
        break;
      }

      p1++;  // it must be a newline, so skip to next char 
    }

    if(p1 >= pEnd)
    {
      WriteStr(hOut, "Error:  quote number out of range - sorry\n", pSA);
      break;
    }

    if(*p1 == '%')  // percent found, so skip this entire line
    {
      while(p1 < pEnd && *p1 != '\n')
        p1++;
      if(p1 < pEnd && *p1 == '\n')
        p1++;
    }

    nPercents ++;  // increment this anyway

    if(p1 >= pEnd)  // end of file?
      break;        // yes, so that's all she wrote

    p2 = p1;  // remember where it is...

    // now look for an ending '%'

    while(p1 < pEnd && 
          (*p1 != '%' ||
           (p1 > pBuf && *(p1 - 1) != '\n')))
    {
      p1++;  // look for '%' as first character on a line
    }

    if(nPercents == nQuote)  // the quote that I want
    {
      if(p1 > p2)
      {
        char *pCur, *pBuf = (char *)malloc((p1 - p2) * 2 + 1);
        pCur = pBuf;

        if(pBuf)
          *pBuf = 0;

        // write output, and translate <CR> to <CRLF>

        while(p2 < p1)
        {
          const char *p2a = p2;
          while(p2a < p1 && (*p2a != '\n' ||
                             (p2a > p2 && *(p2a - 1) == '\r')))
          {
            p2a++;
          }

          if(p2a > p2)
          {
            if(pBuf)
            {
              memcpy(pCur, p2, p2a - p2);
              pCur += p2a - p2;
              *pCur = 0;  // mark end of string
            }
            else if(pSA)
              sendto(hOut, p2, p2a - p2, 0, (struct sockaddr *)pSA, sizeof(*pSA));
            else
              write(hOut, p2, p2a - p2);
          }

          if(p2a < p1 && *p2a == '\n')
          {
            if(pBuf)
            {
              *(pCur++) = '\r';
              *(pCur++) = '\n';
              *pCur = 0;  // mark end of string
            }
            else if(pSA)
              sendto(hOut, "\r\n", 2, 0, (struct sockaddr *)pSA, sizeof(*pSA));
            else
              write(hOut, "\r\n", 2);  // send a CRLF

            p2a++;
          }

          p2 = p2a;
        }

        if(pBuf)
        {
          WriteStr(hOut, pBuf, pSA);
          free(pBuf);
        }
      }
      else
        WriteStr(hOut, "The 'quotes' file has a blank line for this entry\r\n", pSA);

      break;
    }
  }

  // cleanup

#ifndef HAVE_MMAP
  free((void *)pBuf);
#else // HAVE_MMAP
  munmap((void *)pBuf, nBytes);
#endif // HAVE_MAP

  close(hQuotes);

  return(0);
}

int Daemonize(int bDaemon, int iPort, long nQuote)
{
int sListen, sAccept, sUDP, i1, i2, iChild;
struct sockaddr_in sa, saAccept;
struct timeval tmvWait;
fd_set fds1, fds2, fds3;

  FD_ZERO(&fds1);

  // because I'm a glutton for punishment, this one forks

#ifdef WIN32
  if ((sListen = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
#else // WIN32
  if ((sListen = socket(PF_INET, SOCK_STREAM, 0)) < 0)
#endif // WIN32

  {
    fprintf(stderr, "Unable to create tcp socket, errno = %d\n", errno);
    return(1);
  }

#ifdef WIN32
  if ((sUDP = socket(PF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET)
#else // WIN32
  if ((sUDP = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
#endif // WIN32

  {
    fprintf(stderr, "Unable to create udp socket, errno = %d\n", errno);
#ifdef WIN32

    closesocket(sListen);
#else // WIN32

    close(sListen);
#endif // WIN32

    return(1);
  }

  i1 = 1;
  if(setsockopt(sListen, SOL_SOCKET, TCP_NODELAY, &i1, sizeof(int)))  // disable NAGLE algorithm
  {
    fprintf(stderr, "Warning:  'setsockopt(TCP_NODELAY)' failed, errno = %d\n", errno);
  }

  bzero(&sa, sizeof sa);
  sa.sin_family = AF_INET;
  sa.sin_port   = htons(iPort);

  if(INADDR_ANY)  // if not zero... (implementation dependent)
    sa.sin_addr.s_addr = htonl(INADDR_ANY);

  i1 = bind(sListen, (struct sockaddr *)&sa, sizeof sa);

  bzero(&sa, sizeof sa);  // I have to re-do this, 'bind' could have modified it
  sa.sin_family = AF_INET;
  sa.sin_port   = htons(iPort);

  if(INADDR_ANY)  // if not zero... (implementation dependent)
    sa.sin_addr.s_addr = htonl(INADDR_ANY);

  if(i1 < 0 || bind(sUDP, (struct sockaddr *)&sa, sizeof sa) < 0)
  {
    fprintf(stderr, "Unable to bind socket(s), errno = %d\n", errno);
#ifdef WIN32

    closesocket(sListen);
    closesocket(sUDP);
#else // WIN32

    close(sListen);
    close(sUDP);
#endif // WIN32

    return(2);
  }

  // listen on TCP socket

  if(listen(sListen, LISTEN_BACKLOG) < 0)
  {
    fprintf(stderr, "Call to 'listen()' failed, errno = %d\n", errno);
#ifdef WIN32

    closesocket(sListen);
#else // WIN32

    close(sListen);
#endif // WIN32

    return(3);
  }

  // make the sockets asynchronous

  i1 = 1;  // set non-blocking mode
  if(ioctl(sListen, FIONBIO, &i1) < 0)
  {
    fprintf(stderr, "Warning:  'ioctl(FIONBIO)' failed, errno = %d\n", errno);
  }

  i1 = 1;  // set non-blocking mode (again)
  if(ioctl(sUDP, FIONBIO, &i1) < 0)
  {
    fprintf(stderr, "Warning:  'ioctl(FIONBIO)' failed, errno = %d\n", errno);
  }

  // daemonize

  if(bDaemon && daemon(0,0))  // daemon failed?
  {
    fprintf(stderr, "Unable to daemonize, errno = %d\n", errno);
    return(-1);
  }

  while(!bKillFlag)
  {
    FD_ZERO(&fds1);
    FD_SET(sListen, &fds1);
    FD_SET(sUDP, &fds1);

    tmvWait.tv_sec = 0;
    tmvWait.tv_usec = 25000;  // up to 25 millisecs

    // the first parameter of 'select' is poorly documented.  In the BSD realm,
    // it is normally a very large number.  The 'select' library function actually
    // uses it as the MAXIMUM index for the 'fd_set', and as it turns out, it's
    // really just a bitmask.  To optimize this, I use the maximum of the socket
    // values, plus 1.  This minimizes the looping inside of 'select' while also
    // making it work.  The min value of '2' is for compatibility with what appears
    // to be how it's documented, though as I mentioned, it's documented poorly.

    i1 = select(MAX(2,MAX(sUDP + 1, sListen + 1)), &fds1, NULL, NULL, &tmvWait);
    sAccept = -1;

    if(i1 < 0)
    {
      // this can happen when a socket gets closed so ignore it
    }
    else if(i1 == 0)
    {
      // nothing came back, idling...
    }
    else // if(i1 > 0)
    {
      if(FD_ISSET(sUDP, &fds1))
      {
//        fprintf(stderr, "temporary, got something for UDP\n");

        struct sockaddr_in saR;
        bzero(&saR, sizeof(saR));

        i2 = sizeof(saR);
        if(recvfrom(sUDP, &i1, 1, 0, (struct sockaddr *)&saR, &i2) >= 0)  // something out there?
        {
          DoQuote(sUDP, nQuote, &saR);
        }
      }

      // this never seems to happen for some reason
      // but according to the docs, it's supposed to work

      if(FD_ISSET(sListen, &fds1))
      {
//        fprintf(stderr, "temporary, got something for TCP\n");

        bzero(&saAccept, sizeof(saAccept));
        i1 = sizeof(saAccept);

        sAccept = accept(sListen, (struct sockaddr *)&saAccept, &i1);
#ifdef WIN32

        if(sAccept == INVALID_SOCKET)
#else // WIN32

        if(sAccept < 0)
#endif // WIN32

        {
          // TODO:  error log??
        }
      }
    }

    if(sAccept >= 0)
    {
      iChild = fork();

      if(iChild == -1)     // error
      {
#ifdef WIN32

        closesocket(sAccept);
#else // WIN32

        close(sAccept);
#endif // WIN32


        // TODO:  report error?
      }
      else if(iChild != 0)  // main process, returns child process ID
      {
#ifdef WIN32

        closesocket(sAccept);  // must close the socket
#else // WIN32

        close(sAccept);
#endif // WIN32

      }
      else
      {
        // this is the child process.  serve up the return value
        // and then exit.

        // close the (duplicated) socket that I'm listening on
#ifdef WIN32

        closesocket(sListen);
        closesocket(sUDP);
#else // WIN32

        close(sListen);
        close(sUDP);
#endif // WIN32


        // create FILE * for stdin/stdout equivalents

        // TODO:  streams really don't associate with sockets in WIN32 land

        i1 = DoQuote(sAccept, nQuote, NULL);

#ifdef WIN32

        closesocket(sAccept);
#else // WIN32

        close(sAccept);
#endif // WIN32


        return(i1);
      }
    }
  }

  // exit point for daemon only

#ifdef WIN32

  closesocket(sListen);
  closesocket(sUDP);
#else // WIN32

  close(sListen);
  close(sUDP);
#endif // WIN32


  return(0);
}



syntax highlighted by Code2HTML, v. 0.9.1