/*
	Written to parse the output of gradm -O 
  	2003-2004 --solarx
	GPL
	Note about the mixed coding styles used in this file.
	I more or less pulled this code out of my ass or from 
	other projects I'm working on..
*/


#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>

/*********************************/
#define VENDOR "unknown"
#define ACL_HEADER "#"
#define _GENTOO_
/*********************************/

#ifdef _GENTOO_

#undef VENDOR
#define VENDOR "gentoo"
#define QPKG "/usr/bin/qpkg"
#undef ACL_HEADER

#define ACL_HEADER "\
# Copyright 1999-2004 Gentoo Linux.\n\
# Distributed under the terms of the GNU General Public License v2\n\
# $Header: /cvsroot/grtoolkit/parsegrcfg.c,v 1.1.1.1 2003/05/19 19:24:32 solar Exp $"
#endif				/* _GENTOO_ */

#ifndef GR_DIR
#define GR_DIR "/etc/grsec/"
#endif

#ifndef ACL_FILE
#define ACL_FILE GR_DIR "learning.roles"
#endif

/* Used to queue a lot of things */
struct queue_t {
   char *item;
   struct queue_t *next;
};
typedef struct queue_t qlist;

struct gracl_t {
   char *subject;
   char *flags;
   char *header;
   qlist *objects, *binds, *connects, *res, *caps;
#ifdef _GENTOO_
   char *Package;
#endif
   struct gracl_t *next;
};
typedef struct gracl_t gracl;

/**************/
/* Prototypes */
/**************/

char *rmspace(char *);
qlist *listadd(char *, qlist *, char);
void listclear(qlist *);

/**********/
/* MACROS */
/**********/

#ifdef DEBUG
#define DBG(a)  a
#else				/* !DEBUG */
#define DBG(a)			/* do nothing! */
#endif				/* DEBUG */

#ifdef DEBUGPATH
#undef DEBUGPATH
#define DEBUGPATH printf("In %s() on line %d\n", __FUNCTION__, __LINE__)
#else
#define DEBUGPATH ;
#endif				/* DEBUGPATH */

#define ARRAYSIZE(array) (sizeof(array)/sizeof(array[0]))

#define MALLOC malloc
#define CALLOC calloc

#define FREE(x) { \
  if (x != NULL) { \
    free(x); \
  } else { \
    printf("Attempting to free a NULL pointer at 0x%x\n", x); \
  } \
}

#define FREE_IF(x) { \
  if (x != NULL) { \
    free(x); \
  } \
}


#ifdef _GENTOO_
char *get_gentoo_package(const char *subject)
{
   FILE *fp;
   char *p;
   char buf[BUFSIZ];

   memset(buf, 0, sizeof(buf));

   snprintf(buf, sizeof(buf) - 1, "%s -nc -f %s", QPKG, subject);
   if ((fp = popen(buf, "r")) == NULL)
      return NULL;

   memset(buf, 0, sizeof(buf));
   fgets(buf, sizeof(buf), fp);
   fclose(fp);

   if (!buf[0])
      return NULL;

   if ((p = strchr(buf, '\n')) != NULL)
      *p = 0;

   if (buf[0])
      return strdup(buf);

   return NULL;
}
#endif

char *rmspace(char *s)
{
   char *p;
   /* wipe start & end of string */
   for (p = s + strlen(s) - 1; ((isspace(*p)) && (p >= s)); p--);
   if (p != s + strlen(s) - 1)
      *(p + 1) = 0;
   for (p = s; ((isspace(*p)) && (*p)); p++);
   if (p != s)
      strcpy(s, p);
   return (char *) s;
}

/*
 Add something to a list queue or create it
 if it does not already exist.
*/

qlist *listadd(char *ss, qlist * q, char separator)
{
   qlist *ll, *z;
   char *s, *ptr, *p;

   s = strdup(ss);
   ptr = (char *) MALLOC(strlen(ss));

   do {
      p = strchr(s, separator);
      if (p != NULL) {
	 *p = 0;
	 p++;
	 strcpy(ptr, p);
      } else
	 *ptr = 0;
      ll = (qlist *) MALLOC(sizeof(qlist));
      ll->next = NULL;
      ll->item = (char *) MALLOC(strlen(s) + 1);
      strcpy(ll->item, s);
      if (q == NULL)
	 q = ll;
      else {
	 z = q;
	 while (z->next != NULL)
	    z = z->next;
	 z->next = ll;
      }
      *s = 0;
      strcpy(s, ptr);
   }
   while (s[0]);

   free(s);
   free(ptr);
   return q;
}

/* clear out a list */
void listclear(qlist * list)
{
   qlist *ll, *q;
   ll = list;
   while (ll != NULL) {
      q = ll->next;
      free(ll->item);
      free(ll);
      ll = q;
   }
}

void printlist(qlist * list, char *prefix, FILE * stream)
{
   qlist *ll, *q;
   ll = list;
   while (ll != NULL) {
      q = ll->next;
      fprintf(stream, "%s%s\n", (prefix != NULL ? prefix : " "), ll->item);
      ll = q;
   }
}

void print_gracl(gracl * list, FILE * stream)
{
   gracl *ll, *q;
   ll = list;
   while (ll != NULL) {
      q = ll->next;

      if (ll->header != NULL)
	 fprintf(stream, "%s\n", ll->header);

#ifdef GRSEC1
      fprintf(stream, "%s %s {\n", ll->subject, ll->flags);
#else
      fprintf(stream, "subject %s %s {\n", ll->subject, ll->flags);
#endif
      printlist(ll->objects, "\t", stream);

      if (ll->caps != NULL)
	 printlist(ll->caps, "\t", stream);

      if (ll->res != NULL)
	 printlist(ll->res, "\t", stream);

      if (ll->binds != NULL) {
	 fprintf(stream, "\tbind {\n");
	 printlist(ll->binds, "\t\t", stream);
	 fputs("\t}\n", stream);
      }

      if (ll->connects != NULL) {
	 fprintf(stream, "\tconnect {\n");
	 printlist(ll->connects, "\t\t", stream);
	 fputs("\t}\n", stream);
      }
      fputs("}\n", stream);
      ll = q;
   }
}

int lsearch(qlist * list, char *string)
{
   qlist *ll;

   for (ll = list; ll != NULL; ll = ll->next) {
      if ((strcmp(ll->item, string)) == 0)
	 return 1;
   }
   return 0;
}

void freeacl(gracl * list)
{
   gracl *ll, *q;
   ll = list;
   while (ll != NULL) {
      q = ll->next;
      FREE_IF(ll->header);
      FREE_IF(ll->subject);
      FREE_IF(ll->flags);
#ifdef _GENTOO_
      FREE_IF(ll->Package);
#endif
      listclear(ll->objects);
      listclear(ll->binds);
      listclear(ll->connects);
      listclear(ll->caps);
      listclear(ll->res);
      free(ll);
      ll = q;
   }
}

int add_include(char *filename)
{
   FILE *input;

   char aclheader[65536];
   int getbody, get_bind, get_connect, x, count;
   char buf[BUFSIZ];
   char subject[BUFSIZ];
   char *p, *flags, *tmp;
   char local_time[128];
   time_t now;
   qlist *list;
   gracl *gracls, *acl;
   struct stat st;
   struct dirent *de;
   DIR *dir;

   gracls = NULL;
   count = 0;

   if (filename[0] == '.')
      return count;

   if ((strcmp(filename, "/")) == 0)
      return count;

   printf("%s(%s);\n", __FUNCTION__, filename);
   if ((stat(filename, &st)) != (-1)) {
      if (S_ISDIR(st.st_mode)) {
	 DBG((printf("%s is a dir\n", filename)));
	 if (!(dir = opendir(filename))) {
	    perror("opendir");
	    return count;
	 }
	 while ((de = readdir(dir))) {
	    errno = 0;
	    stat(de->d_name, &st);
	    if ((de->d_name[0] != '.')
		&& (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode))) {
	       tmp =
		   (char *) malloc(strlen(filename) +
				   strlen((char *) de->d_name) + 2);
	       sprintf(tmp, "%s/%s", filename, de->d_name);
	       count += add_include(tmp);
	       free(tmp);
	    }
	 }
	 return count;
      }

   }
   if ((input = fopen(filename, "r")) == NULL) {
      fprintf(stderr, "fopen(%s) = %s\n", filename, strerror(errno));
      return count;
   }

   gracls = (gracl *) CALLOC(sizeof(gracl), 1);

   now = time(0);
   strftime(local_time, sizeof(local_time), "%D", localtime(&now));
   while ((fgets(buf, sizeof(buf), input)) != NULL) {
      if (buf[0] == 'i') {
	 if ((strncmp(buf, "include", 7)) == 0) {
	    if ((p = strchr(buf, '<')) != NULL) {
	       tmp = strdup(p + 1);
	       if ((p = strrchr(tmp, '>')) != NULL) {
		  *p = 0;
		  rmspace(tmp);
		  count += add_include(tmp);
	       }
	       free(tmp);
	    }
	 }
      }
      if (buf[0] == 's') {
	/* grsecurity 2 style */
        if ((strncmp(buf, "subject", 7)) == 0) {
		strcpy(buf, &buf[7]);
		rmspace(buf);
	}
      }
      if (buf[0] == '/') {
	 acl = (gracl *) calloc(sizeof(gracl), 1);
	 memset(acl, 0, sizeof(gracl));

	 acl->header = NULL;
	 acl->subject = NULL;
	 acl->flags = NULL;
	 acl->connects = NULL;
	 acl->binds = NULL;
	 acl->objects = NULL;
	 acl->res = NULL;
	 acl->caps = NULL;
#ifdef _GENTOO_
	 acl->Package = NULL;
#endif

	 memset(subject, 0, sizeof(subject));
	 strncpy(subject, buf, sizeof(subject) - 1);
	 if ((p = strchr(subject, ' ')) != NULL)
	    *p = 0;
	 acl->subject = strdup(subject);
	 acl->flags = strdup(p + 1);
#ifdef _GENTOO_
	 if ((strcmp(acl->subject, "/")) != 0) {
	    acl->Package = (char *) get_gentoo_package(subject);
	    if (acl->Package == NULL)
	       fprintf(stderr,
		       "Gentoo: Cant find matching catagory & pkg for subject:%s\n",
		       subject);
	 }
#endif
	 if ((p = strchr(acl->flags, ' ')) != NULL)
	    *p = 0;
	 if ((p = strchr(acl->flags, '{')) != NULL)
	    *p = 'i';

#ifdef _GENTOO_
	 snprintf(aclheader, sizeof(aclheader),
		  "%s\n\n# Package: %s\n# Date: %s\n", ACL_HEADER,
		  ((acl->Package !=
		    NULL) ? acl->Package : "unknown"), local_time);
#else
	 snprintf(aclheader, sizeof(aclheader), "%s\n# Date: %s\n",
		  ACL_HEADER, local_time);
#endif

	 list = NULL;
	 list = listadd(aclheader, list, '\n');

	 acl->header = strdup(aclheader);

	 getbody = 1;
	 get_bind = 0;
	 get_connect = 0;
	 while (getbody) {
	    if ((fgets(buf, sizeof(buf), input)) == NULL)
	       getbody = 0;
	    if (*buf == '}')
	       getbody = 0;
	    if (buf[0] != '\n') {
	       // list = listadd(buf, list, '\n');
	       rmspace(buf);
	       DBG((printf("switch(%c)\n", buf[0])));
	       switch (buf[0]) {
		  case '+':
		  case '-':
		     acl->caps = listadd(buf, acl->caps, '\n');
		     break;
		  case 'R':
		     acl->res = listadd(buf, acl->res, '\n');
		     break;
		  case '/':
		     if (((strncmp(buf, "/lib/ld-", 8)) == 0)
			 || ((strncmp(buf, "/lib/lib", 8)) == 0)
			 || ((strncmp(buf, "/lib/modules", 12)) == 0)
			 || ((strncmp(buf, "/proc/", 6)) == 0)
			 || ((strncmp(buf, "/dev/tty", 8)) == 0)
			 || ((strncmp(buf, "/dev/vc/", 8)) == 0)
			 || ((strncmp(buf, "/dev/pty", 8)) == 0)
			 || ((strncmp(buf, "/usr/lib/lib", 8 + 4)) == 0)
			 ) {
			int xx = 0;
			for (xx = 0; xx < strlen(buf); xx++)
			   if (isdigit(buf[xx]))
			      buf[xx] = '?';
		     }
		     acl->objects = listadd(buf, acl->objects, '\n');
		     break;
		  case 'b':
		     get_bind = 1;
		     break;
		  case 'c':
		     get_connect = 1;
		     break;
		  case '}':
		     get_bind = 0;
		     get_connect = 0;
		     break;
		  case 'd':
		  default:
		     if (get_bind)
			acl->binds = listadd(buf, acl->binds, '\n');
		     if (get_connect) {
			int mixed, ipa, ipb, ipc, ipd, ipport;
			char *tmpbuf;
			mixed =
			    sscanf(buf, "%d.%d.%d.%d:%d",
				   &ipa, &ipb, &ipc, &ipd, &ipport);
			if (mixed == 5) {
			   if (ipport == 53) {
			      DBG((printf
				   ("Got Port %d for %d.%d.%d.%d\n",
				    ipport, ipa, ipb, ipc, ipd)));
			      tmpbuf = strdup(buf);
			      p = strchr(buf, ' ');
			      sprintf(tmpbuf, "0.0.0.0/0:53 %s", p + 1);
			      strcpy(buf, tmpbuf);
			      free(tmpbuf);
			   }
			}
			mixed = 0;
			if (acl->connects != NULL)
			   mixed = lsearch(acl->connects, buf);
			if (mixed == 0)
			   acl->connects =
			       listadd(buf, acl->connects, '\n');
		     }
		     break;
	       }
	    }
	    if (!getbody) {
	       FILE *output = stdout;
	       if ((strcmp(basename(acl->subject), "/")) != 0) {
		  char output_file[255];

		  strncpy(output_file, basename(acl->subject),
			  sizeof(output_file));

#ifdef _GENTOO_
		  tmp = strdup(((acl->Package != NULL) ? acl->Package : "unknown"));
		  mkdir(dirname(tmp), 0755); 
		  DBG((printf("mkdir(%s) && ", dirname(tmp))));
		  free(tmp);
			
		  mkdir(((acl->Package != NULL) ? acl->Package : "unknown"), 0755);

		  snprintf(output_file, sizeof(output_file), "%s/%s%s",
			   (acl->Package !=
			    NULL) ? acl->Package : "unknown",
			   basename(acl->subject), ".acl~");

		  printf("open(%s)\n", output_file);
#endif
		  if ((output =
		       fopen(output_file,
			     "w")) != NULL) {
		     print_gracl(acl, output);
		     fclose(output);
		  } else {
		     fprintf(stderr, "err: open(%s) %s\n",
			     basename(acl->subject), strerror(errno));
		  }
	       }
	       freeacl(acl);
	       count++;
	    }
	 }
      }
   }
   fclose(input);
   printf("Parsed %d acls from %s\n ", count, filename);
   return count;
}

int main(int argc, char **argv)
{
   char *aclfile = ACL_FILE;
   char *p, *gr_dir = GR_DIR;
   char hostname[126];
   int count = 0;

   time_t start, end;

   if (argc > 1)
      aclfile = argv[1];

   if ((chdir(gr_dir)) != 0) {
      fprintf(stderr, "chdir(%s); %s\n", gr_dir, strerror(errno));
      return 1;
   }
#ifdef _GENTOO_
   if (access(QPKG, X_OK) != 0) {
      printf("I cant find qpkg on this system.. emerge it..");
      exit(1);
   }
#endif
   // mkdir /etc/grsec/ + $HOSTNAME + _secure_acls

   memset(hostname, 0, sizeof(hostname));
   gethostname(hostname, sizeof(hostname) - 1);
   if ((p = strchr(hostname, '.')) != NULL) {
      *p = 0;
      fprintf(stderr,
	      "Found what appears a domainname within your hostname..\nYou should fix this.\n");
      fprintf(stderr, "hostname %s; domainname %s\n", hostname, p + 1);
   }
   strncat(hostname, "_secure_acls", sizeof(hostname) - 1);
   mkdir(hostname, 00755);
   chdir(hostname);

   start = time(0);
   count += add_include(aclfile);
   end = time(0);
   printf("\nParsed %d valid acls in %lu%s\n", count, end - start,
	  " seconds");
   return 0;
}
