/* $Id: mailhub.pc,v 1.21 2008-08-22 17:49:11 zacheiss Exp $
 *
 * This generates the /usr/lib/aliases file for the mailhub.
 *
 * (c) Copyright 1988-1998 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 <sys/stat.h>

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

#include "util.h"

EXEC SQL INCLUDE sqlca;

RCSID("$Header: /afs/athena.mit.edu/astaff/project/moiradev/repository/moira/gen/mailhub.pc,v 1.21 2008-08-22 17:49:11 zacheiss Exp $");

char *whoami = "mailhub.gen";
char *db = "moira/moira";
char *divide = "##############################################################";

#define MAX_LINE_WIDTH	72
#define MAX_ALIAS_WIDTH 592

#define FALSE 0
#define TRUE (!FALSE)

FILE *out = stdout;

struct hash *users, *machines, *strings, *lists;
struct user {
  char *login;
  char *pobox;
};
struct member {
  struct member *next;
  char *name;
  int list_id;
};
struct list {
  char *name;
  char maillist;
  char *description;
  char acl_t;
  int acl_id;
  char mailman;
  char *mailman_server;
  struct member *m;
};

void get_info(void);
void save_mlist(int id, void *list, void *force);
int check_string(char *s);
void output_login(int dummy, void *names, void *out);
void output_mlist(int id, struct list *l);
void put_fill(FILE *aliases, char *string);
void do_people(void);

int incount = 0;

int main(int argc, char **argv)
{
  time_t tm = time(NULL);
  char filename[MAXPATHLEN], *targetfile;

  EXEC SQL CONNECT :db;

  if (argc == 2)
    {
      targetfile = argv[1];
      sprintf(filename, "%s~", targetfile);
      if (!(out = fopen(filename, "w")))
	{
	  fprintf(stderr, "unable to open %s for output\n", filename);
	  exit(MR_OCONFIG);
	}
    }
  else if (argc != 1)
    {
      fprintf(stderr, "usage: %s [outfile]\n", argv[0]);
      exit(MR_ARGS);
    }

  fprintf(out, "%s\n# Aliases File Extract of %s", divide, ctime(&tm));
  fprintf(out, "# This file is automatically generated, "
	  "do not edit it directly.\n%s\n\n", divide);

  get_info();

  EXEC SQL COMMIT;

  incount = 0;
  fprintf(out, "\n%s\n# Mailing lists\n%s\n\n", divide, divide);
  hash_step(lists, save_mlist, FALSE);
  fprintf(stderr, "Output %d lists\n", incount);

  incount = 0;
  fprintf(out, "\n%s\n# People\n%s\n\n", divide, divide);
  hash_step(users, output_login, out);
  fprintf(stderr, "Output %d users\n", incount);

  fprintf(out, "\n%s\n# End of aliases file\n", divide);

  if (fclose(out))
    {
      perror("close failed");
      exit(MR_CCONFIG);
    }

  if (argc == 2)
    fix_file(targetfile);
  exit(MR_SUCCESS);
}

void get_info(void)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int id, pid, iid, bid, eid, cnt, maillistp, acl, mid, mailman;
  char mname[MACHINE_NAME_SIZE], str[STRINGS_STRING_SIZE];
  char login[USERS_LOGIN_SIZE], potype[USERS_POTYPE_SIZE];
  char lname[LIST_NAME_SIZE], desc[LIST_DESCRIPTION_SIZE];
  char type[LIST_ACL_TYPE_SIZE], mailman_server[MACHINE_NAME_SIZE];
  EXEC SQL END DECLARE SECTION;
  char *s;
  struct user *u;
  struct list *l, *memberlist;
  struct member *m;

  /* The following is declarative, not executed,
   * and so is dependent on where it is in the file,
   * not in the order of execution of statements.
   */
  EXEC SQL WHENEVER SQLERROR GOTO sqlerr;

  cnt = 0;
  machines = create_hash(100);

  EXEC SQL DECLARE m_cursor CURSOR FOR
    SELECT mach_id, name
    FROM machine
    WHERE status = 1
    AND ( mach_id IN ( SELECT UNIQUE pop_id FROM users ) OR
	  mach_id IN ( SELECT UNIQUE mach_id FROM filesys
		       WHERE type = 'IMAP' ) )    
    ORDER BY mach_id;
  EXEC SQL OPEN m_cursor;
  while (1)
    {
      EXEC SQL FETCH m_cursor INTO :id, :mname;
      if (sqlca.sqlcode)
	break;
      if ((s = strchr(mname, '.')))
	*s = '\0';
      else
	strtrim(mname);
#ifdef ATHENA
      strcat(mname, ".LOCAL");
#endif
      if (hash_store(machines, id, strdup(mname)) < 0)
	{
	  fprintf(stderr, "Out of memory!\n");
	  exit(MR_NO_MEM);
	}
      cnt++;
    }
  EXEC SQL CLOSE m_cursor;

  EXEC SQL DECLARE e_cursor CURSOR FOR
    SELECT mach_id, name
    FROM machine
    WHERE status = 1
    AND mach_id in (SELECT UNIQUE exchange_id FROM users)
    ORDER BY mach_id;
  EXEC SQL OPEN e_cursor;
  while (1)
    {
      EXEC SQL FETCH e_cursor INTO :id, :mname;
      if (sqlca.sqlcode)
	break;
      strtrim(mname);
      if (hash_store(machines, id, strdup(mname)) < 0)
	{
	  fprintf(stderr, "Out of memory!\n");
	  exit(MR_NO_MEM);
	}
      cnt++;
    }
  EXEC SQL CLOSE e_cursor;
	
  fprintf(stderr, "Loaded %d machines\n", cnt);

  cnt = 0;
  strings = create_hash(11001);

  EXEC SQL DECLARE s_cursor CURSOR FOR
    SELECT string_id, string
    FROM strings
    ORDER BY string_id;
  EXEC SQL OPEN s_cursor;
  while (1)
    {
      EXEC SQL FETCH s_cursor INTO :id, :str;
      if (sqlca.sqlcode)
	break;
      if (hash_store(strings, id, strdup(strtrim(str))) < 0)
	{
	  fprintf(stderr, "Out of memory!\n");
	  exit(MR_NO_MEM);
	}
      cnt++;
    }
  EXEC SQL CLOSE s_cursor;

  fprintf(stderr, "Loaded %d strings\n", cnt);

  cnt = 0;
  users = create_hash(13001);

  EXEC SQL DECLARE u_cursor CURSOR FOR
    SELECT users_id, login, potype, pop_id, imap_id, box_id, exchange_id
    FROM users
    WHERE status != 3
    ORDER BY users_id;
  EXEC SQL OPEN u_cursor;
  while (1)
    {
      char *saddr = NULL, *paddr = NULL;

      EXEC SQL FETCH u_cursor INTO :id, :login, :potype, :pid, :iid, :bid,
	:eid;
      if (sqlca.sqlcode)
	break;
      u = malloc(sizeof(struct user));
      u->login = strdup(strtrim(login));

      if (!strcmp(strtrim(potype), "NONE"))
	u->pobox = NULL;
      else
	{
	  /* If SMTP or SPLIT, get SMTP address. */
	  if (potype[0] == 'S')
	    {
	      saddr = hash_lookup(strings, bid);

	      /* If SMTP, clear pid and iid. */
	      if (potype[1] == 'M')
		pid = iid = eid = 0;
	    }

	  /* If IMAP, or SPLIT with IMAP, set pid to mach_id. */
	  if (potype[0] == 'I' || (potype[0] == 'S' && iid))
	    {
	      EXEC SQL SELECT mach_id INTO :pid FROM filesys
		WHERE filsys_id = :iid;
	    }

	  /* If EXCHANGE or SPLIT with EXCHANGE, set pid to eid. */
	  if (potype[0] == 'E' || (potype[0] == 'S' && eid))
	    pid = eid;

	  if (pid && (s = hash_lookup(machines, pid)))
	    {
	      paddr = malloc(strlen(u->login) + strlen(s) + 2);
	      sprintf(paddr, "%s@%s", u->login, s);
	    }

	  if (paddr && saddr)
	    {
	      u->pobox = malloc(strlen(paddr) + strlen(saddr) + 3);
	      sprintf(u->pobox, "%s, %s", paddr, saddr);
	      free(paddr);
	    }
	  else if (paddr)
	    u->pobox = paddr;
	  else
	    u->pobox = saddr;
	}

      check_string(u->login);
      if (hash_store(users, id, u) < 0)
	{
	  fprintf(stderr, "Out of memory!\n");
	  exit(MR_NO_MEM);
	}
      cnt++;
    }
  EXEC SQL CLOSE u_cursor;
  fprintf(stderr, "Loaded %d users\n", cnt);

  cnt = 0;
  lists = create_hash(15000);

  EXEC SQL DECLARE l_cursor CURSOR FOR
    SELECT l.list_id, l.name, l.maillist, l.description, l.acl_type, l.acl_id,
    l.mailman, m.name
    FROM list l, machine m
    WHERE active != 0 AND l.mailman_id = m.mach_id
    ORDER BY list_id;
  EXEC SQL OPEN l_cursor;
  while (1)
    {
      EXEC SQL FETCH l_cursor INTO :id, :lname, :maillistp, :desc, :type, :acl,
	:mailman, :mailman_server;
      if (sqlca.sqlcode)
	break;
      l = malloc(sizeof(struct list));
      l->name = strdup(strtrim(lname));
      l->maillist = maillistp;
      l->description = strdup(strtrim(desc));
      l->acl_t = type[0];
      l->acl_id = acl;
      l->mailman = mailman;
      l->mailman_server = strdup(strtrim(mailman_server));
      l->m = NULL;
      if (hash_store(lists, id, l) < 0)
	{
	  fprintf(stderr, "Out of memory!\n");
	  exit(MR_NO_MEM);
	}
      cnt++;
    }
  EXEC SQL CLOSE l_cursor;
  fprintf(stderr, "Loaded %d lists\n", cnt);

  cnt = 0;

  EXEC SQL DECLARE m_cursor2 CURSOR FOR
    SELECT list_id, member_type, member_id
    FROM imembers
    WHERE direct = 1
    ORDER BY list_id;
  EXEC SQL OPEN m_cursor2;
  while (1)
    {
      EXEC SQL FETCH m_cursor2 INTO :id, :type, :mid;
      if (sqlca.sqlcode)
	break;
      cnt++;
      if ((l = hash_lookup(lists, id)))
	{
	  m = malloc(sizeof(struct member));
	  if (type[0] == 'U' && (u = hash_lookup(users, mid)))
	    {
	      m->list_id = 0;
	      m->name = u->login;
	      m->next = l->m;
	      l->m = m;
	    }
	  else if (type[0] == 'L' && (memberlist = hash_lookup(lists, mid)))
	    {
	      m->list_id = mid;
	      m->name = memberlist->name;
	      m->next = l->m;
	      l->m = m;
	    }
	  else if (type[0] == 'S' && (s = hash_lookup(strings, mid)))
	    {
	      m->list_id = 0;
	      m->next = l->m;
	      l->m = m;
	      m->name = s;
	    }
	}
    }
  EXEC SQL CLOSE m_cursor2;
  fprintf(stderr, "Loaded %d members\n", cnt);

  EXEC SQL COMMIT;
  return;
sqlerr:
  db_error(sqlca.sqlcode);
  exit(MR_DBMS_ERR);
}


void save_mlist(int id, void *list, void *force)
{
  struct member *m;
  struct list *l = list, *l1;

  if (l->maillist > 1 || (l->maillist == 0 && !force) ||
      !check_string(l->name))
    return;

  /* If user group appears on list, replace with user. */
  if (l->m && l->m->next == NULL && !strcasecmp(l->name, l->m->name))
    {
      l->maillist = 3;
      return;
    }
  l->maillist = 2;
  output_mlist(id, l);

  if (l->acl_t == 'L' && (l1 = hash_lookup(lists, l->acl_id)))
    save_mlist(0, l1, (void *)TRUE);

  for (m = l->m; m; m = m->next)
    {
      if (m->list_id && (l1 = hash_lookup(lists, m->list_id)))
	save_mlist(0, l1, (void *)TRUE);
    }
}

void output_login(int dummy, void *user, void *out)
{
  struct user *u = user;

  incount++;
  if (u->pobox && check_string(u->login) && u->login[0] != '#')
    fprintf(out, "%s: %s\n", u->login, u->pobox);
}

static const char *mailman_suffixes[] = { "-admin", "-owner", "-request",
 					  "-bounces", "-confirm", "-join",
 					  "-leave", "-subscribe",
 					  "-unsubscribe", NULL };

void output_mlist(int id, struct list *l)
{
  struct list *l1;
  struct member *m;
  struct user *u;
  int line_width, alias_width, word_width, beginning;
  static int cont = 1;
  char str[8];
  int i;

  put_fill(out, l->description);

  if (l->mailman && strcmp(l->mailman_server, "[NONE]"))
    {
      for (i = 0; mailman_suffixes[i]; i++)
	fprintf(out, "%s%s: %s%s@%s\n", l->name, mailman_suffixes[i], l->name,
		mailman_suffixes[i], l->mailman_server);
      fprintf(out, "owner-%s: %s-owner@%s\n%s: ", l->name, l->name,
	      l->mailman_server, l->name);
    }
  else if (l->acl_t ==  'L' && (l1 = hash_lookup(lists, l->acl_id)))
    fprintf(out, "owner-%s: %s\n%s: ", l->name, l1->name, l->name);
  else if (l->acl_t ==  'U' && (u = hash_lookup(users, l->acl_id)))
    fprintf(out, "owner-%s: %s\n%s: ", l->name, u->login, l->name);
  else
    fprintf(out, "%s: ", l->name);

  alias_width = line_width = strlen(l->name) + 2;
  beginning = 1;
  for (m = l->m; m; m = m->next)
    {
      word_width = strlen(m->name);

      if (!beginning && alias_width + word_width + 2 > MAX_ALIAS_WIDTH)
	{
	  /* Make a continuation. */
	  sprintf(str, "%c%c%c%c%c%c", rand() % 26 + 97, rand() % 26 + 97,
		  rand() % 26 + 97, rand() % 26 + 97,
		  rand() % 26 + 97, rand() % 26 + 97);
	  fprintf(out, ",\n\tcont%d-%s\ncont%d-%s: ", cont, str, cont, str);
	  cont++;
	  alias_width = line_width = 17 + word_width;
	  fputs(m->name, out);
	}
      else if (beginning)
	{
	  /* Beginning of alias, so don't wrap. */
	  line_width += word_width;
	  alias_width = line_width;
	  fputs(m->name, out);
	  beginning = 0;
	}
      else if (line_width + word_width + 2 > MAX_LINE_WIDTH)
	{
	  /* Wrap. */
	  fprintf(out, ",\n\t%s", m->name);
	  alias_width += line_width + word_width + 2;
	  line_width = word_width + 8;
	}
      else
	{
	  /* Continue line. */
	  line_width += word_width + 2;
	  fprintf(out, ", %s", m->name);
	}
    }
  if (!l->m)
    fprintf(out, "/dev/null");
  fprintf(out, "\n\n");
  incount++;
}

/* Write a word-wrapped list description to the aliases file as a
 * comment. */
void put_fill(FILE *aliases, char *string)
{
  char *c;
  int line_width;
  int word_width;

  if (!string || !*string)
    return;
  fputs("#  ", aliases);
  line_width = 3;

  while (1)
    {
      while (*string == ' ')
	string++;
      c = strchr(string, ' ');
      if (!c)
	word_width = strlen(string);
      else
	{
	  word_width = c - string;
	  *c = '\0';
	}

      if (line_width + word_width > MAX_LINE_WIDTH)
	{
	  fputs("\n#  ", aliases);
	  line_width = 3;
	  fputs(string, aliases);
	}
      else
	fputs(string, aliases);

      if (!c)
	break;
      /* add a space after the word */
      fputc(' ', aliases);
      word_width++;
      line_width += word_width;
      string += word_width;
      /* add another if after a period */
      if (*--c == '.')
	{
	  fputc(' ', aliases);
	  line_width++;
	}
    }

  fputc('\n', aliases);
}


/* Illegal chars: this no longer corresponds to the array
 * in setup_alis.  '+' is a valid character in a string on 
 * a list, but is not a valid character in a listname.
 */

static int illegalchars[] = {
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^@ - ^O */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* ^P - ^_ */
  1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1, /* SPACE - / */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, /* 0 - ? */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* @ - O */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* P - _ */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* ` - o */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, /* p - ^? */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};

int check_string(char *s)
{
  for (; *s; s++)
    {
      if (isupper(*s))
	*s = tolower(*s);

      if (illegalchars[(unsigned) *s])
	return 0;
    }
  return 1;
}
