/* Misc. functions involving the server */
/* ------------------------------------ */

#include "headers.h" /* all important stuff */

/* process data from the server */
void procData()
{
   char readbuf[MAXREADSIZE];

   memset(readbuf, 0, sizeof(readbuf));

   /* initStream(); */
   doStreaming(); /* reads SYSLOGFILE until we gets an abort */
   
   /* do something with data here.. the data could be:          */
   /* + new server information (DHCP-like)                      */
   /* + request to change server (server's load avg too high)   */
   /* checkData(); */ /* checks for stuff like "CHNGSERV", etc. */
}


/* -------------------------------------------- */


/* create directories for logs */
void createDirs()
{
   int res;
   DIR *dir;

   errno = 0;

   debug("checking where to create SRS directory...\n");

   dir = opendir("/var/log");
   if (dir == NULL)
   {
      dir = opendir("/var/adm");
      if (dir == NULL)
      {
         dir = opendir("/usr/adm");
         if (dir == NULL)
         {
            error("error finding the logs directory.. using /\n\n");
            (void)strcpy(SRSdir, "/SRS");
         }
         else
            (void)strcpy(SRSdir, "/usr/adm/SRS");

      }
      else
         (void)strcpy(SRSdir, "/var/adm/SRS");
   }
   else
      (void)strcpy(SRSdir, "/var/log/SRS");

   debug("creating %s (if it doesn't exist)\n", SRSdir);

   res = mkdir(SRSdir, (0766 & ~077));
   if (res == ERROR)
      if (errno != EEXIST)
      {
         (void)fprintf(stderr, "error creating %s: %s\n", SRSdir,
                       strerror(errno));

         exit(ERROR);
      }

   errno = 0;
   res = chdir(SRSdir);
   if (res == ERROR)
   {
      (void)fprintf(stderr, "error with chdir() to %s: %s\n", SRSdir,
                    strerror(errno));

      exit(ERROR);
   }

   res = mkdir("./certs", (0766 & ~077));
   if (res == ERROR)
      if (errno != EEXIST)
      {
         (void)fprintf(stderr, "error creating %s: %s\n", SRSdir,
                       strerror(errno));

         exit(ERROR);
      }

   res = mkdir("./spool", (0766 & ~077));
   if (res == ERROR)
      if (errno != EEXIST)
      {
         (void)fprintf(stderr, "error creating %s/spool: %s\n", SRSdir,
                       strerror(errno));

         exit(ERROR);
      }
}


/* --------------------------------------------- */


/* get our Sub-ID from the server */
void getSubID()
{
   int count = 0; /* prevent overflows */

   char subIDstr[8];
   char *subidptr, *dataptr;

   char readbuf[MAXREADSIZE];

   memset(subIDstr, 0, sizeof(subIDstr));
   subidptr = subIDstr;

   send_data("SUBID\n"); /* request server list */

   (void)signal(SIGALRM, subIDTimeout);
   (void)alarm(MAXTIMEOUT); /* wait for Sub-ID */

   /* loop until we get Sub-ID from server */
   while(1)
   {
      memset(readbuf, 0, sizeof(readbuf));
      recv_data(readbuf, sizeof(readbuf));

      debug("(in getSubID) data is: %s\n", readbuf);

      if (strncmp(readbuf, "SUBID", 5) == 0) break;
   }

   (void)alarm(0); /* reset timer */

   dataptr = strstr(readbuf, "SUBID");
   dataptr += 6; /* skip "SUBID " */

   while ((*dataptr) && (*dataptr != ' ') &&
          (isprint((int)*dataptr) != 0) && (count < (int)sizeof(subID))) 
   {
      *subidptr++ = *dataptr++;
      count++;
   }

   debug("Sub-ID = %s\n", subIDstr);

   subID = atoi(subIDstr);
}


/* ------------------------------------------------ */


/* gets current client and server version from the server */
void getVers()
{
   int  count = 0;               /* prevent overflows */
   char *cverptr, *sverptr;      /* point to data returned by server   */

   char curcver[8], cursver[8];
   char *cptr, *sptr;            /* point to curcver & cursver buffers */

   char readbuf[MAXREADSIZE];

   memset(curcver, 0, sizeof(curcver));
   memset(cursver, 0, sizeof(cursver));

   cptr = curcver, sptr = cursver;

   send_data("VERS\n"); /* request client/server version */

   /* if we timeout waiting for version */

   (void)signal(SIGALRM, verTimeout);
   (void)alarm(MAXTIMEOUT); 

   /* loop until we get both the client and server version */
   while(1)
   {
      memset(readbuf, 0, sizeof(readbuf));
      recv_data(readbuf, sizeof(readbuf));

      debug("(in getVers) data is: %s\n", readbuf);

      cverptr = strstr(readbuf, "CVER");
      sverptr = strstr(readbuf, "SVER");

      if ((cverptr != NULL) && (sverptr != NULL))
      {
         debug("got client & server version.. now parsing..\n");
         break;
      }
   }

   (void)alarm(0); /* reset alarm */

   count = 0; /* reset counter */
   cverptr += 5, sverptr += 5; /* skip "XVER " */

   /* copy client from server into local buffer */
   while ((*cverptr) && (*cverptr != ' ') && (*cverptr != ',') &&
          (isprint((int)*cverptr) != 0) && (count < (int)sizeof(curcver))) 
   {
      *cptr++ = *cverptr++;
      count++;
   }

   count = 0; /* reset counter */

   /* copy server from server into local buffer */
   while ((*sverptr) && (*sverptr != ' ') && (*sverptr != ',') &&
          (isprint((int)*sverptr) != 0) && (count < (int)sizeof(cursver))) 
   {
      *sptr++ = *sverptr++;
      count++;
   }

   /* not floating points in version.. always "X.0" */
   if (debugging == 1)
      (void)printf("%catest client version: %s.0, "
                   "latest server version: %s.0\n\n", 
                   (debugging == 1 ? 'l' : 'L'), curcver, cursver);

   if (atoi(curcver) > atoi(VER)) 
   {
      error("we have an outdated client..\n"
            "mailing %s to request a new one..\n\n", SRSMAILADDR);

      updateClient();
   }
}


/* --------------------------------------------- */


/* we need to update our client software */
void updateClient()
{
   FILE *mailfd;

   int res;
   char cmd[128];

   memset(cmd, 0, sizeof(cmd));

   /* use the system's 'mail' command */
   sprintf(cmd, "`which mail` %s -s \"SRS UPDATE REQUEST\"",
           SRSMAILADDR);

   mailfd = popen(cmd, "w");
   if (mailfd == NULL)
   {
      error("error request new client (popen): %s\n\n", strerror(errno));
      return;
   }

   memset(cmd, 0, sizeof(cmd));

   /* actual request to be parsed by SRS staff */
   sprintf(cmd, "NEW VERSION - ID %s\n\n[Note to admin: "
           "if you get this message back with a mailing error, ]\n"
           "[Please contact RSI and request the new version. ]\n\n", ID);

   res = fputs(cmd, mailfd);
   if (res == ERROR)
   {
      error("error requesting new version (fputs): %s\n\n",
            strerror(errno));

      quit(ERROR);
   }

   (void)fputs(".\n", mailfd); /* terminate message */

   res = pclose(mailfd);
   if (res == ERROR)
   {
      error("error closing mailing pipe (pclose): %s\n\n",
            strerror(errno));

      quit(ERROR);
   }
   
   return;
}


/* ------------------------------------------------- */


/* get server list */
void getServList()
{
   char readbuf[MAXREADSIZE];

   send_data("SERVLIST\n");
   curServ = ERROR;

   (void)signal(SIGALRM, listTimeout);
   (void)alarm(MAXTIMEOUT);

   /* read and parse each server in the list */
   while(1)
   {
      memset(readbuf, 0, sizeof(readbuf));
      recv_data(readbuf, sizeof(readbuf));

      debug("(in getServList) data is: %s\n", readbuf);

      if (strncmp(readbuf, "SERV", 4) == 0) putServ(++useServ, readbuf); 
      else if (strncmp(readbuf, "DONE", 4) == 0) break; /* DONE == EOF */

      if (debugging == 1) (void)putchar('\n');
   }

   if (debugging != 1) (void)putchar('\n');
   (void)alarm(0);
}


/* ----------------------------------------------- */


/* parse and put server in ServList */
void putServ(int servNum, char *buf)
{
   int count = 0;    /* prevent any overflows */
      
   char *ipptr;      /* pointer to buffer with server's IP   */
   char *dataptr;    /* pointer to data from server          */
       
   char servname[128]; /* just to be sure it's compatible in the future */
       
   debug("parsing server servNames[%d] (server #%d)\n", 
         servNum, servNum+1);

   memset(servname, 0, sizeof(servname));
   ipptr = servname;
       
   dataptr = strstr(buf, "SERV");
   dataptr += 5; /* skip "SERV " */
      
   count = 0;

   while ((*dataptr) && (*dataptr != ' ') && 
          (isprint((int)*dataptr) != 0) && (count < (int)sizeof(servname)))
   {
      *ipptr++ = *dataptr++;
      count++;
   }

   /* includes a port to connect to */
   if ((*dataptr == ' ') || (*dataptr == '\t'))
   {
      char port[16];
      char *portstr = port;

      memset(port, 0, sizeof(port));

      while ((*dataptr == ' ') || (*dataptr == '\t')) dataptr += 1;
      if (isdigit((int)*dataptr) != 0) servList[servNum].port = PORT;

      count = 0;
      while ((*dataptr) && (*dataptr != ' ') &&
             (isdigit((int)*dataptr) != 0) && (count < (int)sizeof(port)))
      {
         *portstr++ = *dataptr++;
         count++;
      }

      servList[servNum].port = atoi(port);
   }

   if (servList[servNum].port <= 0) 
      servList[servNum].port = PORT;

   servList[servNum].name = (char *)malloc(strlen(servname)+1);
   memset(servList[servNum].name, 0, strlen(servname)+1);
   strcpy(servList[servNum].name, servname);

   (void)printf("%ctreaming server #%d is: %s (port %d)\n", 
                (debugging != 1 ? 'S' : 's'), servNum+1, 
                servList[servNum].name, servList[servNum].port);   
}


/* ----------------------------------------------- */
      
    
/* get "OKAY" from client */
void getOkay()
{
   char readbuf[MAXREADSIZE];
   
   while(1)
   {
      errno = 0;

      memset(readbuf, 0, sizeof(readbuf));
      recv_data(readbuf, sizeof(readbuf));

      if ((errors == 1) || (gotAlrm == 1))
      {
         if (done == 1) return;

         errors = 1;
         error("(in getOkay) timeout or error..\n\n");

         if (done == 1) return;

         if (gotInfoServ == 1) longjmp(reconnect, 1);
         else longjmp(infoconn, 1);
      }

      if (strncmp(readbuf, "OKAY", 4) == 0)
      {
        if (silent != 1) debug("got OKAY\n\n");
        break;
      } 

      else if (done != 1)
      {
         error("OKAY not received.. retrying\n\n");
         continue;
      }

      else return;
   }
}


/* ------------------------------------------------- */


/* parse and setup args */
void getArgs(int argc, char **argv)
{
   int opt;

   dbFile = errorFile = spoolFile = NULL;

   if (debugging != 1)
      if (argc > 1)
      {
         /* -h == help, -d == debugging enabled, -e == error file, */
         /* -s == spool file, -l == log file */

         /* FIX - add long option name support */

         while ((opt = getopt(argc, argv, "hde:l:s:p:")) != ERROR)
         {
            int  testfd1;
            FILE *testfd; /* test if the user-defined files work */

            switch(opt)
            {
               case 'h':
                  usage(argv[0]);
                  exit(SUCCESS);

               case 'd':
                  debugging = 1;
                  debug("debugging enabled\n");

                  break;

               case 'e':
                  errorFile = optarg;
                  debug("using %s to log errors\n", errorFile);

                  while(1)
                  {
                     testfd1 = open(errorFile, O_CREAT | O_WRONLY | O_APPEND,
                                    0600);

                     if (testfd1 == ERROR)
                     {
                        if (errno == EINTR) continue;

                        (void)fprintf(stderr, "Unable to open %s: %s\n",
                                      errorFile, strerror(errno));
  
                        exit(ERROR);
                     }

                     else break;
                  }

                  (void)close(testfd1);
                  break;

               case 'l':
                  dbFile = optarg;
                  debug("using %s to log db info\n", dbFile);

                  while(1)
                  {
                     testfd1 = open(dbFile, O_CREAT | O_WRONLY | O_APPEND,
                                    0600);

                     if (testfd1 == ERROR)
                     {
                        if (errno == EINTR) continue;

                        (void)fprintf(stderr, "Unable to open %s: %s\n",
                                      dbFile, strerror(errno));

                        exit(ERROR);
                     }

                     else break;
                  }

                  (void)close(testfd1);
                  break;

               case 'p':
                  locPort = atoi(optarg);

                  if ((locPort > IPPORT_RESERVED-1) || 
                      (locPort < 512))
                  { 
                     fprintf(stderr, 
                             "Error: port must be between 512-%d\n",
                             IPPORT_RESERVED-1);

                     exit(ERROR);
                  }

                  break;

               case 's':
                  spoolFile = optarg;
                  debug("using %s to spool system logs\n", spoolFile);

                  while (1)
                  {
                     testfd = fopen(spoolFile, "a+");

                     if (testfd == NULL)
                     {
                        if (errno == EINTR) continue;

                        (void)fprintf(stderr, "Unable to open %s: %s\n",
                                      spoolFile, strerror(errno));

                        exit(ERROR);
                     }

                     else break;
                  }

                  (void)chmod(spoolFile, S_IREAD | S_IWRITE);
                  (void)fclose(testfd);

                  break;

               case '?':
                  (void)fputc('\n', stderr), usage(argv[0]);
                  (void)fprintf(stderr, "Press 'Enter' to continue..\n");
                  (void)getchar();
                  
                  break;

               default:
                  (void)fprintf(stderr, "getopt() returned %d.. exiting\n",
                                opt);

                  exit(ERROR);
            }
         }
      }
}


/* ------------------------------------------------ */


/* progress through server list */
void nextServer()
{
   if ((curServ == ERROR) && (connected == 1))
   {
      if (servList[curServ+1].name == NULL)
      {
         error("have no valid streaming server name in list\n\n");
         quit(ERROR);
      }

      if (prim == 1)
      {
         if ((strcmp(PRIMSERV, servList[curServ+1].name) == 0) &&
             (PORT == servList[curServ+1].port)) 
         {
            curServ += 2;
            initing = 1, errors = 1;

            return;
         }
      }

      else
      {
         if ((strcmp(SECSERV, servList[curServ+1].name) == 0) &&
             (PORT == servList[curServ+1].port)) 
         {
            curServ += 2;
            initing = 1, errors = 1;

            return;
         }
      }
   }
}


/* ------------------------------------------------ */


/* do stuff with finding next server in list */
void doServer()
{
   if (curServ < 0)
   { 
      curServ = 0;
      return;
   }

   /* ------------------------------------- */

   while(1)
   {
      if (useServ == 0)
      {
         curServ++;
         break;
      }

      else if ((curServ + 1) > useServ)
      {
         if ((servList[curServ].name == NULL) || (servList[0].name == NULL))
         {
            error("NULL server name.. comparing server [%d] to [0]\n\n",
                  curServ);

            quit(ERROR);
         }

         else if ((strcmp(servList[curServ].name, servList[0].name) != 0) ||
                  (servList[curServ].port != servList[0].port))
         {
            curServ++;
            break;
         }

         else curServ = 0;
      }

      else
      {
         if ((servList[curServ].name == NULL) || (servList[0].name == NULL))
         {
            error("NULL server name.. comparing server [%d] to [%d]\n\n",
                  curServ, curServ + 1);

            quit(ERROR);
         }

         else if ((strcmp(servList[curServ].name,
                  servList[curServ+1].name) != 0) ||
                  (servList[curServ].port != servList[curServ+1].port))
         {
            curServ++;
            break;
         }

         else curServ++;
      }
   }

   /* ------------------------------------- */

   if (curServ > useServ) 
   {
      if (spooling != 1) 
      {
         error("reached end of server list.. now starting spooling\n\n");

         debug("(in parent)\n%cow forking a child to start spooling "
               "to %s/spool/%s locally\n\n", (debugging != 1 ? 'N' : 'n'),
               SRSdir, LOGFILE);

         connected = 0, silent = 1;
         forkSpool();
      }

      else
      {
         (void)sleep(MAXPAUSE * 3); /* take a break.. */
         curServ = 0; /* just keep going forever */
      }
   }
}


/* --------------------------------------------- */


/* go into background and daemonize */
void daemonize()
{
   int res;

   if (start == 1)
   {
      (void)printf("Now forking..\n");

      res = fork();
      if (res == ERROR)
      {
         error("error forking into the background: %s\n\n",
               strerror(errno)); 

         quit(ERROR);
      } 

      else if (res == 0)
      {
         int res1;

         setsid();

         res1 = fork();
         if (res1 == ERROR)
         {
            error("error with fork(): %s\n\n", strerror(errno));
            quit(ERROR);
         }

         else if (res1 == 0)
         {
            nullfd = open("/dev/null", O_RDONLY);
            if (nullfd == ERROR)
            {
               error("error opening /dev/null: %s\n\n", strerror(errno));
               quit(ERROR);
            }

            (void)close(STDIN), dup(nullfd);
            (void)close(STDOUT), dup(errlogfd);
            (void)close(STDERR), dup(errlogfd);
         }

         else exit(SUCCESS);
      }

      else exit(SUCCESS);
   }

   umask(077);
}


/* --------------------------------- */


/* export 16 bit msg length.. (msg can be up to 65,535 bytes long) */
char *exportInt(int value)
{
   static char out[2];

   out[0] = value, out[1] = value >> 8;
   return out;
}

/* ----------------------- */


/* import 16 bit msg length.. (msg can be up to 65,535 bytes long) */
int importInt(char *value)
{
   u_char *in = (u_char *)value;
   return ((int)in[0]) | ((int)in[1] << 8);
}


/* ----------------------------------------- */


/* copy a log msg structure to a new one (to handle users) */
void copyLogStruct(int srcstruct, int dststruct)
{
   logs[dststruct].facility = logs[srcstruct].facility;

   logs[dststruct].priority.single = logs[srcstruct].priority.single;
   logs[dststruct].priority.exclude = logs[srcstruct].priority.exclude;

   logs[dststruct].priority.ignpri = logs[srcstruct].priority.ignpri;
   logs[dststruct].priority.priority = logs[srcstruct].priority.priority;

   logs[dststruct].nsync = logs[srcstruct].nsync;
}


/* ----------------------------------------- */


/* get uid of SRS user */
void getSRSuser()
{
   int count;
   FILE *userfd;

   char *res;
   char *userptr, *dataptr;

   char buf[MAXREADSIZE/4];
   char srsuser[MAXUSERNAME+1];

   memset(srsuser, 0, sizeof(srsuser));

   userfd = fopen(USERFILE, "r");
   if (userfd == NULL)
   {
      error("error opening %s: %s\n"
            "please run install.sh and read SRS.doc\n\n", USERFILE,
            strerror(errno));

      quit(ERROR);
   }

   while(1)
   {
      memset(buf, 0, sizeof(buf));
      res = fgets(buf, sizeof(buf)-1, userfd);

      if (res == NULL)
      {
         if (errno == EINTR) continue;
         else if ((!feof(userfd)) && (errno > 0))
         {
            error("error reading from %s: %s\n\n", USERFILE, strerror(errno));
            quit(ERROR);
         }

         else
         {
            (void)fclose(userfd);
            break;
         }
      }

      debug("(in getSRSuser) parsing line: %s%c", buf, 
            (strchr(buf, '\n') == NULL ? '\n' : '\0'));

      if (isprint((int)buf[0]) == 0) continue; 
      else
      {
         if (buf[0] == '#') continue;
         else
         {
            if (strchr(buf, '#') != NULL) (*(strchr(buf, '#'))) = '\0';

            count = 0, dataptr = buf, userptr = srsuser;
            while((*dataptr) && (isprint((int)*dataptr) != 0) &&
                  (count < sizeof(srsuser)))
            {
               *userptr++ = *dataptr++;
               count++;
            }

            pwd = getpwnam(srsuser);

            if ((pwd != NULL) && (pwd->pw_uid > 0)) break;
            else
            {
               if (pwd != NULL) 
                  error("the SRS user should not be root.. aborting\n\n");

               else error("error with getpwnam(%s): %s\n\n", srsuser, 
                          strerror(errno));
 
               quit(ERROR);
            }
         }
      }
   }

   (void)fclose(userfd);

   if (debugging == 1)
   {
      (void)putchar('\n');
      (void)write(dblogfd, "\n", 1);
   }
}


/* ------------------------------------ */


/* bind to a reserved port (from highest to lowest) */
void rbindport()
{
   int res;
   int success = 0;

   int eperm = 0;

   int lowport = 512;
   int highport = IPPORT_RESERVED-1;

   while(1)
   {
      if (curport == ERROR) curport = highport;

      while(1)
      {
         curport--;
         if (curport < lowport) break;

         laddr.sin_port = htons(curport);
         if (spooling != 1) debug("attempting to bind to port %d\n", curport);

         while(1)
         {
            errno = 0;
            res = bind(sockfd, (struct sockaddr *)&laddr, 
                       sizeof(struct sockaddr));

            if (res == ERROR)
            {
               if (errno == EINTR) continue;

               if (errno != EADDRINUSE)
               {
                  error("error binding to port: %s\n", strerror(errno));
                  (void)write(errlogfd, "\n", 1);
               }

               else debug("error binding to port: %s\n", strerror(errno));
               /* ---------------- */
               if (debugging == 1)
               {
                  (void)putchar('\n');
                  (void)write(dblogfd, "\n", 1);
               }

               if ((errno == EACCES) || (errno == EPERM))
               {
                  if (eperm != 1) eperm = 1;
                  else quit(ERROR);

#                if !defined(SUN) && !defined(BSD)
#                 ifdef _POSIX_SAVED_IDS
                  res = setuid(0);
#                 else
                  res = seteuid(0);
#                 endif

                  if (res == ERROR)
                  {
                     error("error setting [e]uid: %s\n\n",
                           strerror(errno));

                     quit(ERROR);
                  }
#                endif
               }

               break;
            }

            else
            {
               success = 1;
               break;
            }
         }

         if (success == 1) break;
      }

#    if !defined(SUN) && !defined(BSD)
      if (pwd != NULL)
      {
#        ifdef _POSIX_SAVED_IDS
         res = setuid(pwd->pw_uid);
#        else
         res = seteuid(pwd->pw_uid);
#        endif

         if (res == ERROR)
         {
            error("error setting [e]uid: %s\n\n", strerror(errno)); 
            quit(ERROR);
         }         
      }
#    endif

      if (success == 1) break;
   }

   if (spooling != 1) debug("successfully bind'd to port %d\n", curport);
}
