/*
 GGSD (Generic Graphical Server Daemon) (C) Patrick Lambert <drow@darkelf.net>
*/

#include "ggsd.h"

/* check args and start the thing */
int main(int argc, char *argv[])
{
 int i;
 for(i = 1; argv[i] != NULL; i++)
 {
  /* user id under which to run */
  if(!strcasecmp(argv[i], "-uid"))
  {
   i++;
   if(argv[i] == NULL || *argv[i]=='-')
    ggsd_report("You didn't specify a UID !", GGSD_EXIT_ERROR, GGSD_PARAM);
   if(getpwnam(argv[i]) == NULL) uid = atoi(argv[i]);
   else uid = getpwnam(argv[i])->pw_uid;
  }
  /* version */
  else if(!strcasecmp(argv[i], "-version"))
  {
   usage();
   exit(0);
  }
  /* log file to use, or "syslog" */
  else if(!strcasecmp(argv[i], "-log"))
  {
   i++;
   if(argv[i] == NULL || *argv[i]=='-')
    ggsd_report("You didn't specify a log file !", GGSD_EXIT_ERROR, GGSD_PARAM);
   if(!strcasecmp(argv[i], "syslog"))
   {
    GGSD_LOG_SYSLOG = 1;
    strcpy(logfile, "syslog");
   }
   else
   {
    if(strlen(argv[i]) > 98) ggsd_report("Log file name too long", GGSD_EXIT_ERROR, GGSD_PARAM);
    strcpy(logfile, argv[i]);
   }
  }
  /* port num */
  else if(!strcasecmp(argv[i], "-port"))
  {
   i++;
   if(argv[i] == NULL || *argv[i]=='-')
    ggsd_report("You didn't specify a port number !", GGSD_EXIT_ERROR, GGSD_PARAM);
   if(atoi(argv[i]) < 1 || atoi(argv[i]) > 65500)
    ggsd_report("Invalid port", GGSD_EXIT_ERROR, GGSD_PARAM);
   portnum = atoi(argv[i]);
  }
  /* allow list, hostnames separated by spaces */
  else if(!strcasecmp(argv[i], "-allow"))
  {
   i++;
   if(argv[i] == NULL || *argv[i]=='-')
    ggsd_report("You didn't specify an access list !", GGSD_EXIT_ERROR, GGSD_PARAM);
   if(strlen(argv[i]) > 1000) ggsd_report("Access list too long (>1000 chars)", GGSD_EXIT_ERROR, GGSD_PARAM);
   strcpy(allow_list, argv[i]);
  }
  /* script to use */
  else if(!strcasecmp(argv[i], "-script"))
  {
   i++;
   if(argv[i] == NULL || *argv[i]=='-')
    ggsd_report("You didn't specify a script !", GGSD_EXIT_ERROR, GGSD_PARAM);
   if(strlen(argv[i]) > 100) ggsd_report("Script file name too long", GGSD_EXIT_ERROR, GGSD_PARAM);
   strcpy(script, argv[i]);
  }
  /* don't fork */
  else if(!strcasecmp(argv[i], "-fg"))
  {
   nofork = 1;
  }
  /* use allow list first */
  else if(!strcasecmp(argv[i], "-allow-first"))
  {
   allow_first = 1;
  }
  /* don't display config */
  else if(!strcasecmp(argv[i], "-nogui"))
  {
   nogui = 1;
  }
  /* deny list, hostnames separated by spaces */
  else if(!strcasecmp(argv[i], "-deny"))
  {
   i++;
   if(argv[i] == NULL || *argv[i]=='-')
    ggsd_report("You didn't specify an access list !", GGSD_EXIT_ERROR, GGSD_PARAM);
   if(strlen(argv[i]) > 1000) ggsd_report("Access list too long (>1000 chars)", GGSD_EXIT_ERROR, GGSD_PARAM);
   strcpy(deny_list, argv[i]);
  }
  /* unknown args */
  else
  {
   sprintf(temp, "Unknown argument: %s", argv[i]);
   ggsd_report(temp, GGSD_EXIT_ERROR, GGSD_PARAM);
  }
 }
#ifdef USE_GUI
 if(!nogui)
 {
  gtk_init(&argc, &argv);
  make_gui();
 }
#endif
 print_status();
 if(!nofork)
 {
  if((pid=fork()))
  {
   exit(0);
  }
 }
 start_server();
 return 0;
}

/* print variables */
void print_status()
{
 printf("ggsd: Starting server [UID=%d PORT=%d LOG=%s]\n", uid, portnum, logfile);
 printf("ggsd: Allowing hosts: %s\n", allow_list);
 printf("ggsd: Denying hosts: %s\n", deny_list);
}

/* main net function */
void start_server()
{
 char remotehost[1024];
 struct hostent *hp;
 struct sockaddr_in from;
 sockfd2 = socket(AF_INET, SOCK_STREAM, 0);
 if(sockfd2<1) ggsd_report("Can't open a socket", GGSD_EXIT_ERROR, GGSD_RUN);
 listen_addr.sin_family = AF_INET;
 listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 listen_addr.sin_port = htons(portnum);
 listen_len = sizeof(listen_addr);
 if(geteuid()==0) setsockopt(sockfd2, SOL_SOCKET, SO_REUSEADDR, (char *) &opt, sizeof(int));
 if(bind(sockfd2, (struct sockaddr *)&listen_addr, listen_len))
  ggsd_report("Can't bind address (an other ggsd running on that port?)", GGSD_EXIT_ERROR, GGSD_BIND);
 setuid(uid); /* we need to bind as root */
 listen(sockfd2, 5);
 for(;;)
 {
  if(sockfd!=(int)NULL) close(sockfd);
  sockfd = accept(sockfd2, (struct sockaddr *)&address, &len);
  if((pid=fork())) break;
 }
 from_len=sizeof(from_addr);
 memset(&from_addr, 0, sizeof(from_addr));
 if(getpeername(sockfd, (struct sockaddr *)&from_addr,&from_len) < 0)
 {
  ggsd_report("Connection from unknown host. Refusing", GGSD_EXIT_ERROR, GGSD_RUN);
  output("Sorry. Can't find your IP\n");
  close(sockfd);
  exit(0);
 }
 else
 {
  from.sin_family = AF_INET;
  from.sin_addr.s_addr = inet_addr(inet_ntoa(from_addr.sin_addr));
  hp=gethostbyaddr((char *)&from.sin_addr, sizeof(struct in_addr),from.sin_family);
  if(hp==NULL) strncpy(remotehost,"unknown", 1000);
  else strncpy(remotehost,(char *)hp->h_name, 1000);
  if(strlen(inet_ntoa(from_addr.sin_addr))>50) exit(1);
  sprintf(temp, "Connection from %s[%s] [pid=%d]\n", remotehost, inet_ntoa(from_addr.sin_addr), pid);
  ggsd_report(temp, 0, GGSD_RUN);
 }
 if(check_address(remotehost) == -1)
 {
  close(sockfd);
  sprintf(temp, "Refusing connection [pid=%d]", pid);
  ggsd_report(temp, GGSD_EXIT_OK, GGSD_RUN);
 }
 get_report();
 sprintf(temp, "Closing connection [pid=%d]", pid);
 ggsd_report(temp, 0, GGSD_RUN);
 close(sockfd);
 exit(0);
}

/* check the host with the lists */
int check_address(char *ip)
{
 int i;
 if(allow_first)
 for(i = 0; lindex(allow_list, i)!=NULL; i++)
 {
  if(!match(lindex(allow_list, i), ip)) return 1;
 }
 for(i = 0; lindex(deny_list, i)!=NULL; i++)
 {
  if(!match(lindex(deny_list, i), ip)) return -1;
 }
 if(!allow_first)
 for(i = 0; lindex(allow_list, i)!=NULL; i++)
 {
  if(!match(lindex(allow_list, i), ip)) return 1;
 }
 return 1;
}

/* connection is done, we do whatever we need to do here */
void get_report()
{
 FILE *fd, *fd2;
 fd = fopen(script, "r");
 if(fd==NULL) ggsd_report("Script not found", GGSD_EXIT_ERROR, GGSD_RUN);
 while(fgets(line, 500, fd)!=NULL)
 {
  if(lindex(line,0)!=NULL)
  {
   line[strlen(line)-1]=' ';
   if(!strcasecmp(lindex(line,0),"run:")) system(lrange(line,1));
   if(!strcasecmp(lindex(line,0),"cat:"))
   {
    fd2 = fopen(lindex(line,1), "r");
    if(fd2!=NULL)
    {
     bzero(line, 512);
     while(fgets(line, 500, fd2)!=NULL)
     {
      output(line);
     }
     fclose(fd2);
    }
    else
    {
     sprintf(temp, "Could not open file for cat command: %s", line);
     ggsd_report(temp, 0, GGSD_RUN);
    }
   } /* cat */
   if(!strcasecmp(lindex(line,0),"output:")) output(lrange(line,1));
   if(!strcasecmp(lindex(line,0),"deny:"))
   {
    fd2 = fopen(lindex(line,1), "r");
    if(fd2==NULL)
    {
     sprintf(temp, "Could not open file for require command: %s", line);
     ggsd_report(temp, 0, GGSD_RUN);
    }
    bzero(temp, 512);
    fgets(temp, 500, fd2);
    temp[strlen(temp)-1]=' ';
    fclose(fd2);
    sprintf(line, "*%s*", lindex(line,2));
    if(!match(line, temp)) { close(sockfd); return; }
   }
   if(!strcasecmp(lindex(line,0),"require:"))
   {
    fd2 = fopen(lindex(line,1), "r");
    if(fd2==NULL)
    {
     sprintf(temp, "Could not open file for require command: %s", line);
     ggsd_report(temp, 0, GGSD_RUN);
    }
    bzero(temp, 512);                                               
    fgets(temp, 500, fd2);
    temp[strlen(temp)-1]=' ';
    fclose(fd2);
    sprintf(line, "*%s*", lindex(line,2));
    if(match(line,temp)) { close(sockfd); return; }
   }
   if(!strcasecmp(lindex(line,0),"input:"))
   {
    fd2 = fopen(lindex(line,1), "w");
    if(fd2!=NULL)
    {
     bzero(line, 512);
     read(sockfd, line, 500);
     line[strlen(line)-2]=' ';
     fputs(line, fd2);
     fclose(fd2);
     fd2=NULL;
    }
    else
    {
     sprintf(temp, "Could not open file for input command: %s", line);
     ggsd_report(temp, 0, GGSD_RUN);
    }
   } /* input */
   if(!strcasecmp(lindex(line,0),"tail:"))
   {
    fd2 = fopen(lindex(line,1), "r");
    if(fd2!=NULL)
    {
     bzero(line, 512);
     fseek(fd2, 0, SEEK_END);
     fgets(line, 500, fd);
     while(1)
     {
      if(fgets(line, 500, fd2) != (char*)EOF && strcmp(line, temp)) output(line);
      strcpy(temp, line);
      sleep(1);
     }
     fclose(fd2);
    }
    else
    {
     sprintf(temp, "Could not open file for tail command: %s", line);
     ggsd_report(temp, 0, GGSD_RUN);
    }
   } /* tail */
  }
 } /* while */
 fclose(fd);
 close(sockfd);
 ggsd_report("Connection closed", GGSD_EXIT_OK, GGSD_RUN);
}

/* output on the socket */
void output(char *msg)
{
 write(sockfd, msg, strlen(msg));
}

/* an event occured */
void ggsd_report(char *msg, int type, int when)
{
 if(GGSD_LOG_SYSLOG && (when != GGSD_PARAM))
 {
  openlog("ggsd",LOG_PID,LOG_USER);
  syslog(LOG_DAEMON | LOG_NOTICE, msg);
  closelog();
 }
 else
 {
  if(when == GGSD_PARAM || when == GGSD_BIND) fprintf(stderr, "ggsd: %s\n", msg);
  print_log(msg);
 }
 if(when == GGSD_PARAM) usage();
 if(type == GGSD_EXIT_ERROR) exit(1);
 if(type == GGSD_EXIT_OK) exit(0);
 return;
}

/* print to a log file */
void print_log(char *msg)
{
 FILE *fd;
 fd = fopen(logfile, "a");
 if(fd == NULL) return;
 fputs(msg, fd);
 fputs("\n", fd);
 fclose(fd);
 return;
}

/* print usage info */
void usage()
{
 printf("GGSD v%s (C) 1999 Patrick Lambert <drow@darkelf.net> http://devplanet.fastethernet.net\n", VERSION);
 printf("ggsd -script <filename> -uid <uid> -port <port> -log <filename|syslog> -allow <list of hostnames> -deny <list of hostnames> [-version] [-nogui] [-fg] [-allow-first]\n");
 return;
}

/* matching function */
int match(char *ip1, char *ip2)
{
 while(*ip1 == '*' || *ip1==*ip2 || *ip1 == '?')
  if(*ip1 == '*')
  if(*++ip1)
  {
   while(*ip2)
    if(!match(ip1,ip2++)) return 0;
   return 1;
  }
  else return 0;
  else if (!*ip1) return 0;
  else if (!*ip2) return 1;
  else
  {
   ++ip1;
   ++ip2;
  }
 return 1;
}

/* lindex */
char *lindex(char *input_string, int word_number)
{
 char *tokens[1024];
 static char tmpstring[1024];
 int i;
 strncpy(tmpstring,input_string,1024);
 (char *)tokens[i=0] = (char *)strtok(tmpstring, " ");
 while (((char *)tokens[++i] = (char *)strtok(NULL, " ")));
 tokens[i] = NULL;
 return(tokens[word_number]);
}

/* lrange */
char *lrange(char *input_string, int starting_at)
{
 char *tokens[1024];
 static char tmpstring[1024]="";
 int i;
 char out_string[1024]="";
 strcpy(out_string,"");
 if(input_string==NULL) {
  strcpy(out_string," ");
  strcat(out_string,NULL);   
  strcpy(global_var,out_string);
  return (char *)global_var; }
 strncpy(tmpstring,input_string,1024);
 tokens[i=0] = strtok(tmpstring, " ");
 while((tokens[++i] = strtok(NULL, " ")));
 tokens[i] = NULL;
 i++;
 if(i<starting_at)
 {
  return (char *)"";
 }
 while(tokens[starting_at] != NULL)
 {
  strncat(out_string,tokens[starting_at],1024);
  strcat(out_string, " ");
  starting_at++;
 }
 strncpy(global_var,out_string,511);
 return (char *)global_var;
}

#ifdef USE_GUI
void gui_exit(GtkWidget *w, GtkWidget *e)
{
 fprintf(stderr, "ggsd: Aborted.\n");
 exit(1);
}

void gui_cb1(GtkWidget *w, GtkWidget *e)
{
 nofork = GTK_TOGGLE_BUTTON(w)->active;
}

void gui_cb2(GtkWidget *w, GtkWidget *e)
{
 allow_first = GTK_TOGGLE_BUTTON(w)->active;
}  

void gui_ok(GtkWidget *w, GtkWidget *e)
{
 strncpy(allow_list, gtk_entry_get_text(GTK_ENTRY(eb1)), 1000);
 strncpy(deny_list, gtk_entry_get_text(GTK_ENTRY(eb2)), 1000);
 strncpy(logfile, gtk_entry_get_text(GTK_ENTRY(eb3)), 100);
 strncpy(script, gtk_entry_get_text(GTK_ENTRY(eb6)), 100);
 if(!strcasecmp(gtk_entry_get_text(GTK_ENTRY(eb3)), "syslog")) GGSD_LOG_SYSLOG = 1;
 if(getpwnam(gtk_entry_get_text(GTK_ENTRY(eb4))) == NULL) uid = atoi(gtk_entry_get_text(GTK_ENTRY(eb4)));
 else uid = getpwnam(gtk_entry_get_text(GTK_ENTRY(eb4)))->pw_uid;
 portnum = atoi(gtk_entry_get_text(GTK_ENTRY(eb5)));
 if(portnum < 1 || portnum > 65500)
 {
  ggsd_report("Invalid port", GGSD_EXIT_ERROR, GGSD_PARAM);
  exit(1);
 }
 gtk_widget_destroy(window);
 gtk_main_quit();
 if(allow_first)
 printf("ggsd: *** Note: To start a server with these settings on system bootup you should use this line in one of your startup files, ie. /etc/rc.d/rc.local:\n/sbin/ggsd -nogui -allow-first -allow \"%s\" -deny \"%s\" -port %d -uid %d -log %s -script %s\n", allow_list, deny_list, portnum, uid, logfile, script);
 else
 printf("ggsd: *** Note: To start a server with these settings on system bootup you should use this line in one of your startup files, ie. /etc/rc.d/rc.local:\n/sbin/ggsd -nogui -allow \"%s\" -deny \"%s\" -port %d -uid %d -log %s -script %s\n", allow_list, deny_list, portnum, uid, logfile, script);
}

void make_gui()
{
 window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 gtk_signal_connect(GTK_OBJECT(window), "delete_event",
  GTK_SIGNAL_FUNC(gui_exit), &window);
 gtk_container_border_width(GTK_CONTAINER(window), 5);
 gtk_window_set_title(GTK_WINDOW(window), "GGSD Control Panel");
 gtk_widget_set_uposition (window, 200, 200);
 gtk_widget_set_usize (window, 300, 350);

 vbox = gtk_vbox_new (FALSE, 0);
 gtk_container_add (GTK_CONTAINER (window), vbox);
 gtk_widget_show (vbox);

 cb1 = gtk_check_button_new_with_label("Don't go to background");
 gtk_signal_connect (GTK_OBJECT(cb1), "toggled",
  GTK_SIGNAL_FUNC(gui_cb1), cb1);
 gtk_box_pack_start (GTK_BOX (vbox), cb1, TRUE, TRUE, 0);
 if(nofork) gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cb1), TRUE);
 gtk_widget_show (cb1);

 cb2 = gtk_check_button_new_with_label("Use allow list before deny");
 gtk_signal_connect (GTK_OBJECT(cb2), "toggled",
  GTK_SIGNAL_FUNC(gui_cb2), cb2);
 gtk_box_pack_start (GTK_BOX (vbox), cb2, TRUE, TRUE, 0);
 if(allow_first) gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(cb2), TRUE);
 gtk_widget_show (cb2);

 label = gtk_label_new ("Hostnames allowed in:");
 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 gtk_widget_show (label);

 eb1 = gtk_entry_new ();
 gtk_editable_select_region (GTK_EDITABLE (eb1), 0, -1);
 gtk_box_pack_start (GTK_BOX (vbox), eb1, TRUE, TRUE, 0);
 if(strcmp(allow_list,""))
 gtk_entry_set_text (GTK_ENTRY (eb1), allow_list);
 else gtk_entry_set_text (GTK_ENTRY (eb1), "*");
 gtk_widget_show (eb1);

 label = gtk_label_new ("Hostnames denied:");
 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 gtk_widget_show (label);

 eb2 = gtk_entry_new ();
 gtk_editable_select_region (GTK_EDITABLE (eb2), 0, -1);
 gtk_box_pack_start (GTK_BOX (vbox), eb2, TRUE, TRUE, 0);  
 if(strcmp(deny_list,""))
 gtk_entry_set_text (GTK_ENTRY (eb2), deny_list);   
 else gtk_entry_set_text (GTK_ENTRY (eb2), "unknown localhost 192.168.* 10.* 127.*");
 gtk_widget_show (eb2);

 label = gtk_label_new ("Log file name:");
 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 gtk_widget_show (label);

 eb3 = gtk_entry_new ();
 gtk_editable_select_region (GTK_EDITABLE (eb3), 0, -1);
 gtk_box_pack_start (GTK_BOX (vbox), eb3, TRUE, TRUE, 0);
 gtk_entry_set_text (GTK_ENTRY (eb3), logfile);
 gtk_widget_show (eb3);

 label = gtk_label_new ("User ID:");
 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 gtk_widget_show (label);

 eb4 = gtk_entry_new ();
 gtk_editable_select_region (GTK_EDITABLE (eb4), 0, -1);
 gtk_box_pack_start (GTK_BOX (vbox), eb4, TRUE, TRUE, 0);
 if(uid != -1)
 {
  sprintf(temp, "%d", uid);
  gtk_entry_set_text (GTK_ENTRY (eb4), temp);
 }
 else gtk_entry_set_text (GTK_ENTRY (eb4), "nobody");
 gtk_widget_show (eb4);

 label = gtk_label_new ("Port number:");
 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);                   
 gtk_widget_show (label);

 eb5 = gtk_entry_new ();
 gtk_editable_select_region (GTK_EDITABLE (eb5), 0, -1);
 gtk_box_pack_start (GTK_BOX (vbox), eb5, TRUE, TRUE, 0);
 sprintf(temp, "%d", portnum);
 gtk_entry_set_text (GTK_ENTRY (eb5), temp);
 gtk_widget_show (eb5);

 label = gtk_label_new ("GGSD script to use:");
 gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0);
 gtk_widget_show (label);

 eb6 = gtk_entry_new ();
 gtk_editable_select_region (GTK_EDITABLE (eb6), 0, -1);
 gtk_box_pack_start (GTK_BOX (vbox), eb6, TRUE, TRUE, 0);
 gtk_entry_set_text (GTK_ENTRY (eb6), script);
 gtk_widget_show (eb6);

 hbox = gtk_hbutton_box_new();
 gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
 gtk_button_box_set_spacing(GTK_BUTTON_BOX(hbox), 5);
 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
 gtk_widget_show (hbox);

 button = gtk_button_new_with_label ("Start server");
 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
  GTK_SIGNAL_FUNC(gui_ok), GTK_OBJECT (window));
 gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
 gtk_widget_show (button);

 button = gtk_button_new_with_label ("Cancel");
 gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
  GTK_SIGNAL_FUNC(gui_exit), GTK_OBJECT (window));
 gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
 gtk_widget_show (button);

 gtk_widget_show (window);
 gtk_main();
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1