/* $Id: genacl.pc,v 1.6 2009-05-04 20:49:10 zacheiss Exp $
 *
 * Utility functions for outputting ACLs
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef HAVE_KRB4
#include <krb.h>
#else
#include <mr_krb.h>
#endif
#include <krb5.h>

EXEC SQL INCLUDE sqlca;

RCSID("$Header: /afs/athena.mit.edu/astaff/project/moiradev/repository/moira/gen/genacl.pc,v 1.6 2009-05-04 20:49:10 zacheiss Exp $");

static char *defaultrealm = NULL;

static struct hash *users, *strings;

static void save_imember(struct save_queue *sq, char *type, int id, char *tag);
static struct imember *imember(char type, char *name, char *tag);
static struct save_queue *merge_imembers(struct save_queue *sq,
					 char *(merge_func)(char *, char *));

void init_acls(void)
{
  krb5_context context = NULL;
  int code;

  users = create_hash(2000);
  strings = create_hash(2000);

  code = krb5_init_context(&context);
  if (code)
    goto out;

  code = krb5_get_default_realm(context, &defaultrealm);
  if (code)
    goto out;

 out:
  if (context)
    krb5_free_context(context);
}

void dump_krb_acl(FILE *out, char *type, int id, int vers)
{
  struct save_queue *sq;
  struct imember *m;
  char kbuf[MAX_K_NAME_SZ];

  sq = get_acl(type, id, NULL);
  while (sq_remove_data(sq, &m))
    {
      if (m->name == NULL)
	{
	  fprintf(stderr, "Found string_id with no associated string.  Exiting.\n");
	  exit(MR_DBMS_ERR);
	}
      if (m->type != 'S')
	{
	  canon_krb(m, vers, kbuf, sizeof(kbuf));
	  fprintf(out, "%s\n", kbuf);
	}
      freeimember(m);
    }
  sq_destroy(sq);
}

void canon_krb(struct imember *m, int vers, char *buf, int len)
{
  char *at;
  char kbuf[MAX_K_NAME_SZ];

  switch (m->type)
    {
    case 'U':
      snprintf(buf, len, "%s@%s", m->name, defaultrealm);
      break;

    case 'K':
      /* We assume we have a krb4-style namespace.  If we want a krb5 acl, we need to
       * krb5_425_conv_principal() on it. For krb4, do nothing special.
       */ 
      at = strchr(m->name, '@');
      if (!at)
	at = strchr(m->name, '\0');
      snprintf(kbuf, len, "%s", m->name);

      if (!*at)
	{
	  int plen = strlen(kbuf);
	  snprintf(kbuf + plen, len - plen, "@%s", defaultrealm);
	}

      if (vers == 5)
	{
	  char name[ANAME_SZ] = "\0", inst[INST_SZ] = "\0", realm[REALM_SZ] = "\0";
	  char *kuser = NULL;
	  krb5_context context = NULL;
	  krb5_principal client = NULL;
	  int status = 0;

	  if (mr_kname_parse(name, inst, realm, kbuf) != 0)
	    goto out;

	  status = krb5_init_context(&context);
	  if (status)
	    goto out;

	  status = krb5_425_conv_principal(context, name, inst, realm, &client);
	  if (status)
	    goto out;

	  status = krb5_unparse_name(context, client, &kuser);
	  if (status)
	    goto out;

	  strncpy(buf, kuser, MAX_K_NAME_SZ);
	  buf[MAX_K_NAME_SZ - 1] = '\0';

	out:
	  if (kuser)
	    krb5_free_unparsed_name(context, kuser);
	  if (client)
	    krb5_free_principal(context, client);
	  if (context)
	    krb5_free_context(context);
	}
      else
	{
	  /* v4 output, and we should already have added a realm. */
	  snprintf(buf, len, "%s", kbuf);
	}
      break;
    }
}

void dump_user_list(FILE *out, char *type, int id)
{
  struct save_queue *sq;
  struct imember *m;

  sq = get_acl(type, id, NULL);
  while (sq_remove_data(sq, &m))
    {
      if (m->type == 'U' || (m->type == 'S' && !strchr(m->name, '@')))
	fprintf(out, "%s\n", m->name);
      freeimember(m);
    }
  sq_destroy(sq);
}

struct save_queue *get_acl(char *type, int id,
			   char *(merge_func)(char *, char *))
{
  struct save_queue *sq;

  sq = sq_create();
  save_imember(sq, type, id, NULL);
  return merge_imembers(sq, merge_func);
}

static void save_imember(struct save_queue *sq, char *type, int id, char *tag)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int lid = id, mid, mid2, tagid, status;
  char mtype[IMEMBERS_MEMBER_TYPE_SIZE];
  EXEC SQL END DECLARE SECTION;
  char *mtag;

  switch (*type)
    {
    case 'U':
      EXEC SQL SELECT status INTO :status FROM users WHERE users_id = :id;
      if (status != 3)
	sq_save_data(sq, imember('U', user_lookup(id), tag));
      break;

    case 'K':
    case 'S':
      sq_save_data(sq, imember(*type, string_lookup(id), tag));
      break;

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

	  if (tag)
	    mtag = tag;
	  else
	    mtag = string_lookup(tagid);
	  if (mtype[0] == 'L')
	    {
	      EXEC SQL DECLARE csr_list CURSOR FOR
		SELECT member_type, member_id FROM imembers
		WHERE list_id = :mid AND member_type != 'LIST';
	      EXEC SQL OPEN csr_list;
	      while (1)
		{
		  EXEC SQL FETCH csr_list INTO :mtype, :mid;
		  if (sqlca.sqlcode)
		    break;

		  save_imember(sq, mtype, mid, mtag);
		}
	      EXEC SQL CLOSE csr_list;
	    }
	  else
	    save_imember(sq, mtype, mid, mtag);
	}
    }
}

static struct save_queue *merge_imembers(struct save_queue *sq,
					 char *(merge_func)(char *, char *))
{
  int n;
  struct imember *m1, *m2;
  struct save_queue *out;
  char *t1;

  out = sq_create();
  while (sq_remove_data(sq, &m1))
    {
      while (sq_get_data(sq, &m2))
	{
	  if (m1->type == m2->type && !strcmp(m1->name, m2->name))
	    {
	      sq_remove_last_data(sq);
	      if (merge_func)
		{
		  t1 = m1->tag;
		  m1->tag = merge_func(m1->tag, m2->tag);
		  free(t1);
		}
	      freeimember(m2);
	    }
	}
      sq_save_data(out, m1);
    }
  sq_destroy(sq);
  return out;
}  

static struct imember *imember(char type, char *name, char *tag)
{
  struct imember *m;
  m = malloc(sizeof(struct imember));
  m->type = type;
  m->name = name;
  m->tag = strdup(tag ? tag : "");
  return m;
}

void freeimember(struct imember *m)
{
  free(m->tag);
  free(m);
}

char *user_lookup(int users_id)
{
  char *u;

  u = hash_lookup(users, users_id);
  if (u)
    return u;
  else
    {
      EXEC SQL BEGIN DECLARE SECTION;
      char login[USERS_LOGIN_SIZE];
      EXEC SQL END DECLARE SECTION;

      EXEC SQL SELECT login INTO :login FROM users
	WHERE users_id = :users_id;
      if (sqlca.sqlcode)
	return NULL;

      u = strdup(strtrim(login));
      hash_store(users, users_id, u);
      return u;
    }
}

char *string_lookup(int string_id)
{
  char *s;

  s = hash_lookup(strings, string_id);
  if (s)
    return s;
  else
    {
      EXEC SQL BEGIN DECLARE SECTION;
      char string[STRINGS_STRING_SIZE];
      EXEC SQL END DECLARE SECTION;

      EXEC SQL SELECT string INTO :string FROM strings
	WHERE string_id = :string_id;
      if (sqlca.sqlcode)
	return NULL;

      s = strdup(strtrim(string));
      hash_store(strings, string_id, s);
      return s;
    }
}
