/* $Id: acl.pc 4034 2011-03-15 16:34:23Z zacheiss $
 *
 * This generates acl files for various servers.
 *
 * Copyright (C) 1999 by the Massachusetts Institute of Technology.
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#include <mit-copyright.h>
#include <moira.h>
#include <moira_site.h>
#include "util.h"

#include <sys/stat.h>
#include <sys/types.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_KRB4
#include <krb.h>
#endif

EXEC SQL INCLUDE sqlca;

RCSID("$HeadURL: svn+ssh://svn.mit.edu/moira/trunk/moira/gen/acl.pc $ $Id: acl.pc 4034 2011-03-15 16:34:23Z zacheiss $");

char *whoami = "acl.gen";
char *db = "moira/moira";

void dump_access_file(FILE *out, int lid);
char *merge_access_bits(char *t1, char *t2);
void dump_discuss_acl(FILE *out, int lid);
void dump_passwd_file(FILE *out, int lid);
void dump_group_file(FILE *out, int id);
char *merge_discuss_acls(char *one, char *two);
void sqlerr(void);

int main(int argc, char **argv)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int mid, lid, discuss_uid = -1;
  char target[ACL_TARGET_SIZE], kind[ACL_KIND_SIZE];
  char host[MACHINE_NAME_SIZE];
  EXEC SQL END DECLARE SECTION;
  char filename[MAXPATHLEN];
  TARFILE *tf;
  FILE *out;
  struct save_queue *sq;
  time_t now = time(NULL);

  EXEC SQL WHENEVER SQLERROR DO sqlerr();
  EXEC SQL CONNECT :db;
  init_acls();

  EXEC SQL DECLARE csr_mach CURSOR FOR
    SELECT UNIQUE mach_id FROM acl;
  EXEC SQL OPEN csr_mach;
  while (1)
    {
      EXEC SQL FETCH csr_mach INTO :mid;
      if (sqlca.sqlcode)
	break;

      EXEC SQL SELECT name INTO :host FROM machine
	WHERE mach_id = :mid;
      if (sqlca.sqlcode)
	continue;
      strtrim(host);

      sprintf(filename, "%s/acl/%s", DCM_DIR, host);
      tf = tarfile_open(filename);

      EXEC SQL DECLARE csr_acl CURSOR FOR
	SELECT target, kind, list_id
	FROM acl
	WHERE mach_id = :mid;
      EXEC SQL OPEN csr_acl;
      while (1)
	{
	  EXEC SQL FETCH csr_acl INTO :target, :kind, :lid;
	  if (sqlca.sqlcode)
	    break;

	  strtrim(target);
	  strtrim(kind);

	  if (!strcasecmp(kind, "discuss"))
	    {
	      /* Discuss acls need to be owned by discuss. */
	      if (discuss_uid == -1)
		{
		  EXEC SQL SELECT unix_uid INTO :discuss_uid
		    FROM users WHERE login = 'discuss';
		}
	      out = tarfile_start(tf, target, 0644, discuss_uid, 1,
				  "discuss", "daemon", now);
	      dump_discuss_acl(out, lid);
	    }
	  else
	    {
	      /* Otherwise own by root? Perhaps the acl table should
	       * say, really...
	       */
	      out = tarfile_start(tf, target, 0644, 0, 0, "root", "root", now);

	      if (!strcasecmp(kind, "kerberos4"))
		dump_krb_acl(out, "LIST", lid, 4);
	      else if (!strcasecmp(kind, "kerberos5"))
		dump_krb_acl(out, "LIST", lid, 5);
	      else if (!strcasecmp(kind, "access"))
		dump_access_file(out, lid);
	      else if (!strcasecmp(kind, "passwd"))
		dump_passwd_file(out, lid);
	      else if (!strcasecmp(kind, "group"))
		dump_group_file(out, lid);
	      else if (!strcasecmp(kind, "userlist"))
		dump_user_list(out, "LIST", lid);
	    }

	  tarfile_end(tf);
	}

      tarfile_close(tf);
    }

  EXEC SQL COMMIT RELEASE;

  exit(MR_SUCCESS);
}

void dump_access_file(FILE *out, int lid)
{
  struct save_queue *sq = get_acl("LIST", lid, merge_access_bits);
  struct imember *m;
  char *name, *lasts = NULL;
  int i = 0;

  while (sq_remove_data(sq, &m))
    {
      if (m->type == 'U' ||
	  (m->type == 'S' && m->name[0] == '*'))
	{
	  if (*(m->tag))
	    fprintf(out, "%-10s %s\n", m->name, m->tag);
	  else
	    fprintf(out, "%-10s rl\n", m->name);
	}
      else if (m->type == 'K')
	{
	  name = strtok_r(m->name, "@", &lasts);
	  EXEC SQL SELECT count(login) INTO :i FROM users 
	    WHERE login = :name and status != 3;
	  if (i == 1)
	    {
	      if (*(m->tag))
		fprintf(out, "%-10s %s\n", m->name, m->tag);
	      else
		fprintf(out, "%-10s rl\n", m->name);
	    }
	}
      freeimember(m);
    }
  sq_destroy(sq);
}

char *merge_access_bits(char *t1, char *t2)
{
  char *m = NULL, *ans;
  int r = 1, l = 1;

  /* Clear bits that aren't granted by both tags. */
  if ((*t1 && t1[0] != 'r' && t1[1] != 'r') ||
      (*t2 && t2[0] != 'r' && t2[1] != 'r'))
    r = 0;
  if ((*t1 && t1[0] != 'l' && t1[1] != 'l') ||
      (*t2 && t2[0] != 'l' && t2[1] != 'l'))
    l = 0;

  if (!r || !l)
    {
      /* Skip to message part of tag 1 */
      m = t1;
      while (*m && !isspace(*m))
	m++;
      while (isspace(*m))
	m++;

      /* If nothing there, skip to message part of tag 2 */
      if (!*m)
	{
	  m = t2;
	  while (*m && !isspace(*m))
	    m++;
	  while (isspace(*m))
	    m++;
	}

      ans = malloc(4 + strlen(m));
      sprintf(ans, "%c  %s", r ? 'r' : l ? 'l' : '-', m);
    }
  else
    {
      ans = malloc(3);
      strcpy(ans, "rl");
    }
  return ans;
}

void dump_discuss_acl(FILE *out, int lid)
{
  struct save_queue *sq = get_acl("LIST", lid, merge_discuss_acls);
  struct imember *m;
  char name[STRINGS_STRING_SIZE], *bits;
  char starbits[8] = { 0 };
  int num;

  num = 0;
  while (sq_get_data(sq, &m))
    {
      if (m->type != 'S' || !strcmp(m->name, "*"))
	num++;
    }

  fprintf(out, "%d\n", num);
  while (sq_remove_data(sq, &m))
    {
      bits = merge_discuss_acls(m->tag, "");
      if (m->type != 'S')
	{
	  canon_krb(m, 4, name, sizeof(name));
	  fprintf(out, "%s:%s\n", bits, name);
	}
      else if (!strcmp(m->name, "*"))
	strcpy(starbits, bits);
      free(bits);
      freeimember(m);
    }
  sq_destroy(sq);

  /* Discuss ACLs are ordered, so "*" must come last. */
  if (*starbits)
    fprintf(out, "%s:*\n", starbits);
}

char *merge_discuss_acls(char *one, char *two)
{
  char bits[8];

  sprintf(bits, "%c%c%c%c%c%c%c",
	  strchr(one, 'a') || strchr(two, 'a') ? 'a' : ' ',
	  strchr(one, 'c') || strchr(two, 'c') ? 'c' : ' ',
	  strchr(one, 'd') || strchr(two, 'd') ? 'd' : ' ',
	  strchr(one, 'o') || strchr(two, 'o') ? 'o' : ' ',
	  strchr(one, 'r') || strchr(two, 'r') ? 'r' : ' ',
	  strchr(one, 's') || strchr(two, 's') ? 's' : ' ',
	  strchr(one, 'w') || strchr(two, 'w') ? 'w' : ' ');
  return strdup(bits);
}

void dump_passwd_file(FILE *out, int lid)
{
  struct save_queue *sq = get_acl("LIST", lid, NULL);
  struct imember *m;
  EXEC SQL BEGIN DECLARE SECTION;
  char shell[USERS_SHELL_SIZE], fullname[USERS_FULLNAME_SIZE];
  char oa[USERS_OFFICE_ADDR_SIZE];
  char op[USERS_OFFICE_PHONE_SIZE], hp[USERS_HOME_PHONE_SIZE];
  int uid, i = 0;
  char *name, *n, *lasts = NULL;
  EXEC SQL END DECLARE SECTION;

  while (sq_remove_data(sq, &m))
    {
      switch (m->type)
	{
	case 'U':
	  name = m->name;

	  EXEC SQL SELECT unix_uid, shell, fullname,
	    office_addr, office_phone, home_phone
	    INTO :uid, :shell, :fullname, :oa, :op, :hp
	    FROM users
	    WHERE login = :name AND status != 3;
	  if (sqlca.sqlcode)
	    continue;

	  strtrim(shell);
	  strtrim(fullname);
	  strtrim(op);
	  strtrim(oa);
	  strtrim(hp);

	  fprintf(out, "%s:*:%d:101:%s,%s,%s,%s:/mit/%s:%s\n",
		  name, uid, fullname, oa, op, hp, name, shell);
	  break;

	case 'K':
	  name = strtok_r(m->name, "@", &lasts);
	    
	  EXEC SQL SELECT count(login) INTO :i FROM users WHERE 
	    login = :name and status != 3;
	  if (i == 1)
	    {
	      EXEC SQL SELECT unix_uid, shell, fullname,
		office_addr, office_phone, home_phone 
		INTO :uid, :shell, :fullname, :oa, :op, :hp 
		FROM users 
		WHERE login = :name AND status != 3;	      
	      if (sqlca.sqlcode)
		continue;
	      
	      strtrim(shell);
	      strtrim(fullname);
	      strtrim(op);
	      strtrim(oa);
	      strtrim(hp);
	      
	      fprintf(out, "%s:*:%d:101:%s,%s,%s,%s:/mit/%s:%s\n",
		      name, uid, fullname, oa, op, hp, name, shell);
	    }
	  break;
	}
      freeimember(m);
    }
  sq_destroy(sq);
}

/* This one is a bit weird since we actually look at top-level
 * lists rather then flattening them.
 */
void dump_group_file(FILE *out, int id)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int lid = id, mid, gid, grouplist, i = 0;
  char mtype[IMEMBERS_MEMBER_TYPE_SIZE], name[LIST_NAME_SIZE];
  EXEC SQL END DECLARE SECTION;
  struct save_queue *sq;
  struct imember *m;
  char *maybecomma, *s, *n, *lasts = NULL;

  EXEC SQL DECLARE csr_grp CURSOR FOR
    SELECT member_type, member_id FROM imembers
    WHERE list_id = :lid AND direct = 1;
  EXEC SQL OPEN csr_grp;
  while (1)
    {
      EXEC SQL FETCH csr_grp INTO :mtype, :mid;
      if (sqlca.sqlcode)
	break;

      switch (*mtype)
	{
	case 'L':
	  EXEC SQL SELECT name, gid, grouplist
	    INTO :name, :gid, :grouplist
	    FROM list
	    WHERE list_id = :mid;
	  if (sqlca.sqlcode || !grouplist)
	    break;

	  strtrim(name);

	  fprintf(out, "%s:*:%d:", name, gid);
	  sq = get_acl("LIST", mid, NULL);
	  maybecomma = "";
	  while (sq_remove_data(sq, &m))
	    {
	      if (m->type == 'U')
		{
		  fprintf(out, "%s%s", maybecomma, m->name);
		  maybecomma = ",";
		}
	      else if (m->type == 'K')
		{
		  n = strtok_r(m->name, "@", &lasts);
		  EXEC SQL SELECT count(login) INTO :i FROM users
		    WHERE login = :n and status != 3;
		  if (i == 1)
		    {
		      fprintf(out, "%s%s", maybecomma, n);
		      maybecomma = ",";
		    }
		}
	      freeimember(m);
	    }
	  fprintf(out, "\n");
	  sq_destroy(sq);
	  break;
	}
    }
  EXEC SQL CLOSE csr_grp;
}

void sqlerr(void)
{
  db_error(sqlca.sqlcode);
  exit(MR_DBMS_ERR);
}
