/*
 *
 *	Copyright (C) 1988, 1989 by the Massachusetts Institute of Technology
 *    	Developed by the MIT Student Information Processing Board (SIPB).
 *    	For copying information, see the file mit-copyright.h in this release.
 *
 */
/*
 * coreutil.c  -- These contain lower-layer, utility type routines to
 *		  be used by core.  These include things to handle the
 *		  in-memory superblock, and to open & close meetings.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */

#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#include <netdb.h>
#include <discuss/discuss.h>
#include "mtg.h"
#ifdef ZEPHYR
#include <zephyr/zephyr.h>
#endif ZEPHYR

/* global variables */
char rpc_caller [256];
char current_mtg [256] = "";	/* meeting that's opened */
int u_trn_f,u_control_f,u_acl_f; /* UNIX file descriptors */
bool nuclear = FALSE;		/* Using atomic reads/writes */
afile a_control_f = NULL;	/* radioactive file descriptor */
tfile abort_file = NULL;	/* close this on abort */
bool  read_lock = FALSE;	/* have lock on u_control_f */
bool  mtg_swapped = FALSE;	/* current meeting is swapped */
dsc_acl   *mtg_acl = NULL;	/* current mtg acl */
int 	last_acl_mod;		/* last mod to ACL */
mtg_super super;
char *super_chairman;
char *super_long_name;
int has_privs = 0;		/* Has privileges (linked) */
int no_nuke = 0;		/* Don't be atomic (linked) */
#ifdef ZEPHYR
int use_zephyr = 1;		/* Actually do use Zephyr. */
#else
int use_zephyr = 0;
#endif

/* EXTERNAL */
extern int errno;
static void panic (char *);

/*
 *
 * new_string (s)  --   Routine to create a copy of the given string, using
 *		      	malloc.
 *
 */
char *new_string (s)
const char *s;
{
     int len;
     char *newstr;

     len = strlen (s) + 1;
     newstr = malloc ((unsigned)len);
     (void) strcpy (newstr, s);
     return (newstr);
}

/*
 *
 * open_mtg (mtg_name) -- Routine to open a meeting, if necessary.
 *
 */
int open_mtg (mtg_name)
char *mtg_name;
{
     char str[256];
     int result;
     int mtg_name_len;
     trn_base tb;
     int uid = (int)geteuid();
     struct stat sb;

     mtg_name_len = strlen (mtg_name);
     if (mtg_name[0] != '/' || mtg_name_len == 0 || mtg_name_len > 168
	 || mtg_name [mtg_name_len-1] == '/') {
	  return (BAD_PATH);
     }

     /* Check for moved meeting */
     (void) strcpy (str, mtg_name);
     (void) strcat (str, "/forward");
     if (!stat(str, &sb)) {
	  return(MTG_MOVED);
     }

     (void) strcpy (str, mtg_name);
     (void) strcat (str, "/acl");

     if (!strcmp (mtg_name, current_mtg)) {
	  /*
	   * is acl stale? 
	   */
	  if (stat(str, &sb) >= 0)
	       if (sb.st_mtime <= last_acl_mod)
		    return (0);				/* that was easy */
     }

     if (current_mtg [0] != '\0') {		/* close previous meeting */
	  if (nuclear)
	       panic ("Nuclear flag error");	/* should never happen */
	  (void) close (u_trn_f);
	  (void) close (u_control_f);
	  current_mtg [0] = '\0';
	  acl_destroy(mtg_acl);
     }

     u_trn_f = u_control_f = u_acl_f = 0;

     if ((u_acl_f = open(str, O_RDONLY, 0700)) < 0) {
	  if (errno == ENOENT)
	       result = NO_SUCH_MTG;
	  else if (errno == EACCES)
	       result = NO_ACCESS;
	  else
	       result = BAD_PATH;
	  goto punt;
     }
     if (!fis_owner (u_acl_f, uid)) {
	  result = NO_ACCESS;
	  goto punt;
     }

     mtg_acl = acl_read (u_acl_f);
     (void) fstat(u_acl_f, &sb);
     last_acl_mod = sb.st_mtime;
     (void) close(u_acl_f);
     u_acl_f = 0;

     (void) strcpy (str, mtg_name);
     (void) strcat (str, "/control");

     if ((u_control_f = open(str, O_RDWR, 0700)) < 0) {
	  if (errno == ENOENT)
	       result = NO_SUCH_MTG;
	  else if (errno == EACCES)
	       result = NO_ACCESS;
	  else
	       result = BAD_PATH;
	  goto punt;
     }

     if (!fis_owner (u_control_f, uid)) {
	  result = NO_ACCESS;
	  goto punt;
     }

     (void) strcpy (str, mtg_name);
     (void) strcat (str, "/transactions");
     
     if ((u_trn_f = open(str, O_RDWR, 0700)) < 0) {
	  if (errno == ENOENT)
	       result = NO_SUCH_MTG;
	  else if (errno == EACCES)
	       result = NO_ACCESS;
	  else
	       result = BAD_PATH;
	  goto punt;
     }

     if (!fis_owner (u_trn_f, uid)) {
	  result = NO_ACCESS;
	  goto punt;
     }

     read (u_trn_f, (char *) &tb, sizeof (trn_base));
     if (tb.unique == TRN_BASE_UNIQUE_SWAP) {
	  swap_trn_base(&tb);
	  mtg_swapped = TRUE;
     } else {
	  mtg_swapped = FALSE;
     }

     if (tb.unique != TRN_BASE_UNIQUE) {
	  result = INCONSISTENT;
	  goto punt;
     }

     (void) strcpy (current_mtg, mtg_name);

     return (0);

 punt:
     if (u_trn_f)
	     (void) close(u_trn_f);
     if (u_control_f)
	     (void) close(u_control_f);
     if (u_acl_f)
	     (void) close(u_acl_f);
     acl_destroy(mtg_acl);
     mtg_acl = NULL;
     return (result);
}


int read_super()
{
     if (nuclear) {
	  aread (a_control_f, (char *) &super, sizeof(super), 0);
     } else {
	     lseek (u_control_f, (long)0, 0);
	     read (u_control_f, (char *) &super, sizeof (super));
     }

     if (!mtg_swapped) {
	  if (super.unique != MTG_SUPER_UNIQUE)
	       return (INCONSISTENT);
     } else {
	  if (super.unique != MTG_SUPER_UNIQUE_SWAP)
	       return (INCONSISTENT);
	  swap_super(&super);
     }

     if (super.version != MTG_SUPER_1)
	  return (NEW_VERSION);

     super_long_name = malloc ((unsigned)super.long_name_len);
     super_chairman = malloc ((unsigned)super.chairman_len);

     if (nuclear) {
	  aread (a_control_f, super_long_name, super.long_name_len, super.long_name_addr);
	  aread (a_control_f, super_chairman, super.chairman_len, super.chairman_addr);
     } else {
	  lseek (u_control_f, (long)super.long_name_addr, 0);
	  read (u_control_f, super_long_name, super.long_name_len);
	  lseek (u_control_f, (long)super.chairman_addr, 0);
	  read (u_control_f, super_chairman, super.chairman_len);
     }

     return(0);
}

write_super ()
{
     int sc_len, sl_len;
     int slong_name_len, slong_name_addr;
     int schairman_len, schairman_addr;

     sc_len = strlen (super_chairman)+1;
     sl_len = strlen (super_long_name)+1;

     if (sc_len != super.chairman_len || sl_len != super.long_name_len) {		/* reallocate things */
	  super.long_name_addr = sizeof (super);
	  super.long_name_len = sl_len;
	  super.chairman_addr = super.long_name_addr + super.long_name_len;
	  super.chairman_len = sc_len;
     }

     /* Copy into locals to avoid swapping */
     slong_name_len = super.long_name_len;
     slong_name_addr = super.long_name_addr;
     schairman_len = super.chairman_len;
     schairman_addr = super.chairman_addr;

     if (mtg_swapped)
	  swap_super(&super);

     if (nuclear) {
	  awrite (a_control_f, (char *) &super, sizeof (super), 0);
	  awrite (a_control_f, super_long_name, slong_name_len, slong_name_addr);
	  awrite (a_control_f, super_chairman, schairman_len, schairman_addr);
     } else {
	  lseek (u_control_f, (long)0, 0);
	  write (u_control_f, (char *)  &super, sizeof (super));
	  lseek (u_control_f, (long)slong_name_addr, 0);
	  write (u_control_f, super_long_name, slong_name_len);
	  lseek (u_control_f, (long)schairman_addr, 0);
	  write (u_control_f, super_chairman, schairman_len);
     }

     free (super_chairman);
     super_chairman = NULL;
     free (super_long_name);
     super_long_name = NULL;
     super.unique = 0;					/* prevent accidents */

     return;
}

/*
 *
 * forget_super () -- Routine to dump superblock, like when we just read
 *		      it.  This frees any alloc'd storage.
 *
 */
forget_super()
{
     if (super_long_name != NULL)
	  free(super_long_name);
     super_long_name = NULL;

     if (super_chairman != NULL)
	  free(super_chairman);
     super_chairman = NULL;
     super.unique = 0;
}

/*
 *
 * start_read () --	This reserves the meeting for non-destructive reading.
 *			Simply does an flock.
 *
 */
start_read()
{
#ifndef HAVE_FLOCK
     struct flock fl;
#endif
     
     if (!no_nuke) {
#ifndef HAVE_FLOCK
	  fl.l_type = F_RDLCK;
	  fl.l_whence = SEEK_SET;
	  fl.l_start = 0;
	  fl.l_len = 0;
	  if (fcntl(u_control_f, F_SETLKW, &fl) < 0)
	       panic ("Cannot share lock");
#else
	  if (flock (u_control_f, LOCK_SH) < 0)
	       panic ("Cannot share lock");
#endif
	  read_lock = TRUE;
     }
}

/*
 *
 * finish_read () --	This frees up our reservation on the meeting.  This
 *			does the opposite of start_read.
 *
 */
finish_read()
{
#ifndef HAVE_FLOCK
     struct flock fl;
#endif

     if (!no_nuke) {
#ifndef HAVE_FLOCK
	  fl.l_type = F_UNLCK;
	  fl.l_whence = SEEK_SET;
	  fl.l_start = 0;
	  fl.l_len = 0;
	  fcntl(u_control_f, F_SETLKW, &fl);
#else
	  flock(u_control_f, LOCK_UN);
#endif
	  read_lock = 0;
     }
}

/*
 *
 * chain_addr -- Returns address of given chain block.
 *
 */
faddr chain_addr(trn)
trn_nums trn;
{
     if (trn < super.lowest || trn > super.highest)
	  return (0);

     return (super.chain_start + (trn-1) * sizeof (chain_blk));
}

int read_chain (trn, cb)
trn_nums trn;
chain_blk *cb;
{
     faddr cbaddr;

     cbaddr = chain_addr (trn);
     if (cbaddr == 0)
	  return (NO_SUCH_TRN);

     if (nuclear)
	  aread (a_control_f, (char *) cb, sizeof (chain_blk), cbaddr);
     else {
	  lseek (u_control_f, (long)cbaddr, 0);
	  read (u_control_f, (char *) cb, sizeof (chain_blk));
     }

     if (mtg_swapped)
	  swap_chain(cb);

     if (cb -> unique != CHAIN_BLK_UNIQUE || cb -> current != trn)
	  return (INCONSISTENT);

     if (cb -> version != CHAIN_BLK_1)
	  return (NEW_VERSION);

     return (0);
}

int write_chain (cb)
chain_blk *cb;
{
     faddr cbaddr;

     cbaddr = chain_addr (cb -> current);
     if (cbaddr == 0)
	  return (NO_SUCH_TRN);

     if (mtg_swapped)
	  swap_chain(cb);

     if (nuclear) {
	  if (awrite (a_control_f, (char *) cb, sizeof (chain_blk), cbaddr) != sizeof (chain_blk)) {
	       if (mtg_swapped)
		    swap_chain(cb);
	       return (NO_WRITE);
	  }
     }
     else {
	  lseek (u_control_f, (long)cbaddr, 0);
	  if (write (u_control_f, (char *) cb, sizeof (chain_blk)) != sizeof (chain_blk)) {
	       if (mtg_swapped)
		    swap_chain(cb);
	       return (NO_WRITE);
	  }
     }

     if (mtg_swapped)
	  swap_chain(cb);
     return (0);
}

/*
 *
 * read_trn -- routine to read a transaction from the transaction file.
 *
 */
int read_trn (trn_addr, th, th_subject, th_author, th_signature)
faddr trn_addr;
trn_hdr *th;
char **th_subject, **th_author, **th_signature;
{
     char *author;

     lseek (u_trn_f, (long)trn_addr, 0);
     read (u_trn_f, (char *) th, sizeof (trn_hdr));

     if (mtg_swapped) {
	  swap_trn(th);
     }
     if (th -> unique != TRN_HDR_UNIQUE)
	  return (INCONSISTENT);

     if (th -> version != TRN_HDR_1)
	  return(NEW_VERSION);

     if (th_subject != NULL) {
	  *th_subject = malloc ((unsigned)(th -> subject_len));
	  lseek (u_trn_f, (long)(th -> subject_addr), 0);
	  read (u_trn_f, *th_subject, th -> subject_len);
     }
	  
     if (th_author != NULL || th_signature != NULL) {
	  author = malloc ((unsigned)(th -> author_len));
	  lseek (u_trn_f, (long)(th -> author_addr), 0);
	  read (u_trn_f, author, th -> author_len);
     }

     if (th_author != NULL)
	  *th_author = author;

     if (th_signature != NULL) {
	  if (strlen(author)+1 == th -> author_len) {
	       *th_signature = new_string(author);	/* No signature, return author */
	  } else {
	       *th_signature = new_string(&author[strlen(author)+1]);
	  }
	  if (th_author == NULL)
	       free(author);
     }
	       
     return(0);
}
	  

/*
 *
 * core_abort () -- Routine for use by core routines to clean things up.
 *
 */
core_abort ()
{
     int dummy;

     if (nuclear) {
	  aabort(a_control_f);
	  nuclear = FALSE;
     }

     if (abort_file != NULL) {
	  tclose (abort_file,&dummy);
	  abort_file = NULL;
     }

     if (read_lock)
	  finish_read();

     forget_super();

     return;
}

/*
 *
 * fsize () -- Routine to find out the size of a file.
 *
 */
fsize (d)
int d;
{
     struct stat buf;

     if (fstat (d, &buf) < 0)
	  return (0);

     return (buf.st_size);
}
/*
 *
 * fis_owner () -- Routine to find out if uid is owner of file.
 *
 */
fis_owner (d, uid)
int d,uid;
{
     struct stat buf;

     if (fstat (d, &buf) < 0)
	  return (0);

     return (uid == buf.st_uid);
}

/*
 *
 * has_trn_access -- Routine to return true if current user can access
 *		     transaction.
 *
 */
bool has_trn_access(author,mode)
char *author;
char mode;
{
     char *test_modes;

     if (has_privs)
	  return(TRUE);				/* linked in */

     switch (mode) {
     case 'd':
	  if (!strcmp(author,rpc_caller)) {
	       test_modes = "o";
	  } else {
	       test_modes = "d";
	  }
	  return (dsc_acl_check(mtg_acl,rpc_caller,test_modes));

     case 'r':
	  return (dsc_acl_check(mtg_acl,rpc_caller,"r") ||
		  ((!strcmp (author, rpc_caller)) && dsc_acl_check(mtg_acl,rpc_caller,"o")));
     default:
	  panic ("Invalid mode");
	  /*NOTREACHED*/
     }
}	  
/*
 *
 * has_mtg_access -- Routine to return true if current user can access
 *		     mtg, with given mode.
 *
 */
bool has_mtg_access(mode)
char mode;
{
     char *test_modes;
     char mode_str[3];

     if (has_privs)				/* linked in stuff */
	  return (TRUE);		

     switch (mode) {
     case 'w':
     case 'a':
     case 'r':
     case 's':
     case 'o':
	  mode_str[0] = mode;
	  mode_str[1] = '\0';
	  test_modes = mode_str;
	  break;

     case 'c':
	  test_modes = "c";
	  break;

     default:
	  panic("Invalid mode");
     }

     return(dsc_acl_check(mtg_acl,rpc_caller,test_modes));
}	  
/*
 *
 * panic -- just a printf
 *
 */
static void
panic(str)
char *str;
{
     printf("panic: %s\n",str);
     perror("discuss");
     exit(1);
}

#ifdef ZEPHYR
/*
 *
 * mtg_znotify -- send off a Zephyr notification as appropriate
 *
 */

static const char * this_host = (const char *) NULL;

void mtg_znotify(mtg_name, subject, author, signature)
	char *mtg_name, *subject, *author, *signature;
{
	register dsc_acl_entry *ae;
	register int n;
	ZNotice_t notice;
	char *msglst[5],bfr[30],fullpath[256];
	int code, list_size;

	if (!use_zephyr)
	    return;

	if (!this_host) {
	    /* perform initializations */
	    char *h;
	    char host[100];
	    struct hostent *hent;
	    if (gethostname(host,100) != 0)
		return;
	    hent = (struct hostent *) gethostbyname(host);
	    if (hent == 0)
		return;
	    h = (char *) malloc (strlen (hent->h_name) + 1);
	    if (!h)
		return;
	    strcpy (h, hent->h_name);
	    this_host = h;
	    ZInitialize();
	}

	/* Set up the notice structure */
	memset(&notice, 0, sizeof(notice));

	sprintf(fullpath,"%s:%s", this_host, mtg_name);
	ZOpenPort(NULL);
	notice.z_kind = UNSAFE;
	notice.z_port = 0;
	notice.z_class = "DISCUSS";
	notice.z_class_inst = fullpath;
	notice.z_opcode = "NEW_TRN";
	notice.z_sender = 0;
	if (signature == NULL)
	     notice.z_default_format = "New transaction [$1] entered in $2\nFrom: $3\nSubject: $4";
	else
	     notice.z_default_format = "New transaction [$1] entered in $2\nFrom: $3 ($5)\nSubject: $4";
	msglst[0] = bfr;
	sprintf(msglst[0],"%04d",super.highest);
	msglst[1] = super_long_name;
	msglst[2] = author;
	msglst[3] = subject;
	list_size = 4;
	if (signature != NULL) {
	     msglst[4] = signature;
	     list_size = 5;
	}

	/* Does "*" have read access? If so, just send out a global
	 * notice.
	 */

	/* XXX
	 * Check at some point for people who don't have access, etc.
	 */

	for (ae = mtg_acl->acl_entries, n=mtg_acl->acl_length;
	     n;
	     ae++, n--) {
		if ((strcmp("*", ae->principal) == 0) &&
		    acl_is_subset("r", ae->modes))
			break;
	}
	if (n) {
		notice.z_recipient = "";
		/* We really don't care if it gets through... */
		code = ZSendList(&notice,msglst,list_size,ZNOAUTH);
		return;
	}
	for (ae = mtg_acl->acl_entries, n=mtg_acl->acl_length;
	     n;
	     ae++, n--) {
		if (acl_is_subset("r", ae->modes)) {
			notice.z_recipient = ae->principal;
			ZSendList(&notice,msglst,list_size,ZNOAUTH);
		}
	}
}
#endif /* ZEPHYR */

#ifndef __GNUC__
#define inline
#endif

static inline short Sshort (short P_s)
{
    union {
	short s;
	char c[2];
    } x1, x2;
    x1.s = P_s;
    x2.c[0] = x1.c[1];
    x2.c[1] = x1.c[0];
    return x2.s;
}

static inline long Slong (long P_l)
{
    union {
	long l;
	char c[4];
    } x1, x2;
    x1.l = P_l;
    x2.c[0] = x1.c[3];
    x2.c[1] = x1.c[2];
    x2.c[2] = x1.c[1];
    x2.c[3] = x1.c[0];
    return x2.l;
}

swap_super(superp)
mtg_super *superp;
{
     superp->version = Slong(superp->version);
     superp->unique = Slong(superp->unique);
     superp->first = Slong(superp->first);
     superp->last = Slong(superp->last);
     superp->lowest = Slong(superp->lowest);
     superp->highest = Slong(superp->highest);
     superp->highest_chain = Slong(superp->highest_chain);
     superp->date_created = Slong(superp->date_created);
     superp->date_modified = Slong(superp->date_modified);
     superp->long_name_addr = Slong(superp->long_name_addr);
     superp->chairman_addr = Slong(superp->chairman_addr);
     superp->long_name_len = Sshort(superp->long_name_len);
     superp->chairman_len = Sshort(superp->chairman_len);
     superp->flags = Sshort(superp->flags);
     superp->chain_start = Slong(superp->chain_start);
     superp->high_water = Slong(superp->high_water);
     superp->trn_fsize = Slong(superp->trn_fsize);
     superp->highest_trn_addr = Slong(superp->highest_trn_addr);
}

swap_chain(cbp)
chain_blk *cbp;
{
     cbp->version = Slong(cbp->version);
     cbp->unique = Slong(cbp->unique);
     cbp->current = Slong(cbp->current);
     cbp->prev = Slong(cbp->prev);
     cbp->next = Slong(cbp->next);
     cbp->pref = Slong(cbp->pref);
     cbp->nref = Slong(cbp->nref);
     cbp->trn_chain = Slong(cbp->trn_chain);
     cbp->trn_addr = Slong(cbp->trn_addr);
     cbp->flags = Sshort(cbp->flags);
     cbp->filler = Sshort(cbp->filler);
     cbp->chain_fref = Slong(cbp->chain_fref);
     cbp->chain_lref = Slong(cbp->chain_lref);
}

swap_trn_base(tbp)
trn_base *tbp;
{
     tbp->version = Slong(tbp->version);
     tbp->unique = Slong(tbp->unique);
     tbp->date_created = Slong(tbp->date_created);
     tbp->long_name_addr = Slong(tbp->long_name_addr);
     tbp->chairman_addr = Slong(tbp->chairman_addr);
     tbp->long_name_len = Sshort(tbp->long_name_len);
     tbp->chairman_len = Sshort(tbp->chairman_len);
     tbp->public_flag = Sshort(tbp->public_flag);
}

swap_trn(thp)
trn_hdr *thp;
{
     thp->version = Slong(thp->version);
     thp->unique = Slong(thp->unique);
     thp->current = Slong(thp->current);
     thp->orig_pref = Slong(thp->orig_pref);
     thp->date_entered = Slong(thp->date_entered);
     thp->num_lines = Slong(thp->num_lines);
     thp->num_chars = Slong(thp->num_chars);
     thp->prev_trn = Slong(thp->prev_trn);
     thp->subject_addr = Slong(thp->subject_addr);
     thp->author_addr = Slong(thp->author_addr);
     thp->text_addr = Slong(thp->text_addr);
     thp->subject_len = Sshort(thp->subject_len);
     thp->author_len = Sshort(thp->author_len);
}
