/*
 * $Id: auth.c,v 1.10 1999/09/06 19:33:29 labovit Exp $
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <ctype.h>
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
/*#include <mrt.h> */
#include <irrauth.h>

extern int verbose;

#ifndef HAVE_CRYPT_H
extern char *crypt (const char *, const char*);
#endif

/*JW: need to add support for RPSL comments within an object */

/* local yokel's */

static void maint_check (trace_t *tr, FILE *, int *, int *, trans_info_t *);
static int determine_op_type (trace_t *tr, int *sockfd, FILE **fd_old, trans_info_t *ti, 
			      char *fname, int *num_trans, long *obj_pos);
static enum AUTH_CODE perform_auth_check (trace_t *tr, int *, int, FILE *, long, FILE *, 
					  long, trans_info_t *, int *);
static enum AUTH_CODE do_auth_check (trace_t *tr, char *, trans_info_t *);
static FILE *get_db_object (trace_t *tr, int *num_trans, char *fname, int *sockfd, 
			    int obj_size, char *obj_type, char *obj_key, char *source,
			    long *obj_pos);
static enum AUTH_CODE auth_process (trace_t *tr, int op, int *sockfd, FILE *fd, long fpos, 
				    trans_info_t *ti, int *num_trans, char **mb_list, 
				    char **mb_exist_list);
static enum AUTH_CODE mnt_by_exist (trace_t *tr, int *sockfd, trans_info_t *ti, 
				    char **mb_list, int *num_trans);
static enum AUTH_CODE auth_ok (trace_t *tr, int op, int *sockfd, trans_info_t *ti, 
			       char *mntner, int *mntner_exists, int *num_trans);
static int check_crypt_passwd (char *cleartxt, char *encrypted);
static void update_trans_info (trace_t *tr, enum AUTH_CODE ret_code, trans_info_t *ti,
			       int op, FILE *fd_new, long fpos, FILE *fd_old, 
			       long old_obj_pos);
static enum AUTH_CODE mntner_mblist_check (trace_t *tr, int op, int *sockfd, 
					   trans_info_t *ti, int *num_trans, 
					   char **mb_list, char **mb_exist_list);

/* String array's for debug output */
char *str_op[] = {"DEL", "ADD", "REPLACE"};
char *str_res[] = {"AUTH_FAIL_C", "AUTH_PASS_C", "OTHER_FAIL_C", 
		   "MNT_NO_EXIST_C", "DEL_NO_EXIST_C", "NOOP_C"};

/* Global object lookup structure
 * Briefly, our transaction file may have multiple
 * updates and so it may have the latest object
 * instance rather than IRRd.  This linked list will keep
 * track of object keys and file pointer to object starts in
 * the transaction file (ie, the output file this routine generates).
 */
lookup_info_t gstart;

/* Used by get_db_object().  Easier to decalare
 * global to this file as get_db_object() is nested
 * fairly deeply and frequently.
 */
static char *IRRd_HOST;
static int  IRRd_PORT;
static char *SUPER_PASSWD;

int auth_check (trace_t *tr, char *infname, char *outfname, 
		char *irrd_host, int irrd_port, char *super_passwd) {
  int num_trans = 0, sockfd;
  long fin_offset, fout_offset;
  char buf[MAXLINE];
  FILE *fin, *fout;
  trans_info_t ti;  

  IRRd_HOST    = irrd_host;
  IRRd_PORT    = irrd_port;
  SUPER_PASSWD = super_passwd;

  /* open the input and output files */
  if ((fout = myfopen (tr, outfname, "w+", "auth_check () output file")) == NULL)
    return -1;

  umask(0077);
  if ((fin = myfopen (tr, infname, "r", "auth_check () input file"))== NULL)
    return -1;

  /* initialize the transaction list lookup struct */
  gstart.first = gstart.last = NULL;

  while (fgets (buf, MAXLINE - 1, fin) != NULL) {
    if (strncmp (HDR_START, buf, strlen (HDR_START) - 1))
      continue;

    fin_offset = ftell (fin);
    if (parse_header (tr, fin, &fin_offset, &ti))
      trace (NORM, tr, "ERROR: auth_check () parse_header error!\n");
      /* JW need to take some logical course of action later:
       * return 0;  illegal hdr field found or EOF 
       * (ie, no object after hdr): want to construct legal hdr 
       * with "OTHERFAIL" initialized 
       */

    /* perform the actual maintainer checking */
    if (!ti.syntax_errors)
      maint_check (tr, fin, &sockfd, &num_trans, &ti);

    /* write the header info for this trans object */
    print_hdr_struct (fout, &ti);

    /* copy the current object after the header info */
    fout_offset = ftell (fout);
    write_trans_obj (tr, fin, fin_offset, fout, 
		     MAX_IRRD_OBJ_SIZE, update_has_errors (&ti));

    /* print a blank line seperator between objects */
    if (EOF == fputs ("\n", fout))
      trace (NORM, tr, "ERROR: writing object to file (%s)\n", strerror (errno));

    /* update the transaction lookup list (see gstart) */
    trans_list_update (tr, &gstart, &ti, fout, fout_offset);

    free_ti_mem (&ti);
  }

  if (num_trans) {
    end_irrd_session (tr, sockfd);
    close_connection (sockfd);
    close (sockfd);
  }

  /* return trans list memory */
  free_trans_list (tr, &gstart);

  fflush (fout);
  fclose (fout);
  fclose (fin);
  return (1);
}

/* Contol the process of determining if the user has authorization to update 
 * the object.  The function determines the operation (ie ADD, DEL, REPLACE),
 * checks for update authorization and set's the notify/forward addr's for
 * notify.
 *
 * Return:
 *  (via the 'ti' struct)
 *  -notify/forward addr's
 *  -file with old object (for DEL and REPLACE op's)
 *  -authorization outcome
 */
void maint_check (trace_t *tr, FILE *fd_new, int *sockfd, int *num_trans, trans_info_t *ti) {
  int op;
  enum AUTH_CODE ret_code;
  FILE *fd_old = NULL;
  long fpos, old_obj_pos;
  char old_fname[256];

  /* save obj file pos to restore on exit */
  fpos = ftell (fd_new); 

  /* determine the operation type, ie, DEL, ADD, or REPLACE */
  if ((op = determine_op_type (tr, sockfd, &fd_old, ti, old_fname,
			       num_trans, &old_obj_pos)) < 0)
    return;

  if (*sockfd < 0) {
    if (fd_old != NULL) {
      fclose (fd_old);
      remove (old_fname);
    }
    ti->otherfail = strdup ("IRRd connection refused.  Abort transaction!");
    return;
  }

  /* Perform the authorization check.  fd_new points to the DEL, ADD, 
   * or REPLACE object in the input file.  fd_old has a copy 
   * of the object to be DEL'd or REPLACE'd (for ADD fd_old is NULL).
   */
  ret_code = perform_auth_check (tr, sockfd, op, fd_old, old_obj_pos, fd_new, fpos, 
				 ti, num_trans);

  /* fold the transaction outcome/ret_code info into the object header struct */
  update_trans_info (tr, ret_code, ti, op, fd_new, fpos, fd_old, old_obj_pos);

  /* Notify will get: a file name  (ie, old object came from 
   * IRRd/DB) or a file pos (ie, a number; object came from our 
   * output/transaction file) for REPLACE and DEL operations.
   */
  if (fd_old != NULL) {
    if (old_obj_pos == 0L) {
      ti->old_obj_fname = strdup (old_fname);
      fclose (fd_old);
    }
    else {
      sprintf (old_fname, "%ld", old_obj_pos);
      ti->old_obj_fname = strdup (old_fname);
    }
  }

  /* restore fpos to the begining of object */
  fseek (fd_new, fpos, SEEK_SET);
}


/*
 * Determine the operation type, ie, ADD, DEL, or REPLACE.
 * If a delete field is provided by the user the OP: field
 * value will be set to DEL.  Otherwise the OP: field will 
 * be empty.
 */
int determine_op_type (trace_t *tr, int *sockfd, FILE **fd_old, trans_info_t *ti, 
		       char *fname, int *num_trans, long *obj_pos) {
  int op = -1;

  *fd_old = get_db_object (tr, num_trans, fname, sockfd, FULL_OBJECT,
			   ti->obj_type, ti->obj_key, ti->source, obj_pos);
  /* ADD or REPLACE */
  if (ti->op == NULL) {
    if (*fd_old == NULL) {
      op = iADD;
      set_op_type (ti->op, ADD_OP);
    }
    else {
      op = iREPLACE;
      set_op_type (ti->op, REPLACE_OP);
    }
  }
  else if (!strcmp (ti->op, DEL_OP))
    op = iDEL;
  else if (ti->op != NULL)
    fprintf (stderr, "ERROR: determine_op_type () unrecognized op (%s)...\n", ti->op);

  /*fprintf (dfile, "return determine_op_type () (%d)\n", op);*/
  return op;
}


/* Control the process of determining authorization.  The function is
 * passed an 'op' (ie, DEL, ADD, REPLACE) and 1 or 2 non-empty object
 * files; an authorization code is returned.  On return the mnt_nfy 
 * list (AUTH_PASS_C) or the upd_to list (AUTH_FAIL_C) will be complete 
 * (ie, from all referenced maintainers).  It is the calling routines
 * onus to remove mnt_nfy/upd_to addr's based on the return code.
 *
 * Return:
 *   All legal 'enum AUTH_CODE' return codes are possible (see auth.h)  
 *   The 'ti' struct 'ti->forward' and 'ti->notify' may be updated.
 */
enum AUTH_CODE perform_auth_check (trace_t *tr, int *sockfd, int op, FILE *fd_old, 
				   long fpos_old, FILE *fd_new, long fpos_new, 
				   trans_info_t *ti, int *num_trans) {
  enum AUTH_CODE ret_code = AUTH_FAIL_C;
  char *mb_list = NULL, *mb_list2 = NULL, *mb_exist_list = NULL;
  
  /*fprintf (dfile, "\n---Enter perform_auth_check () op-(%s)...\n", str_op[op]);*/
  
  if (op != iADD) {
    if (fd_old == NULL)
      return DEL_NO_EXIST_C;
    
    if (op == iREPLACE && noop_check (tr, fd_old, fpos_old, fd_new, fpos_new))
      return NOOP_C;
    
    /* Determine authorization */
    if ((ret_code = auth_process (tr, iDEL, sockfd, fd_old, fpos_old, ti, num_trans, 
				  &mb_list, &mb_exist_list)) != AUTH_PASS_C)
      goto FREE_MEM;
  }
  
  if (op != iDEL) {
    if (op == iADD && /* Determine authorization */
	(ret_code = auth_process (tr, iADD, sockfd, fd_new, fpos_new, ti, num_trans, 
				  &mb_list2, &mb_exist_list)) != AUTH_PASS_C)
      goto FREE_MEM;

    /* Next section makes sure all new maintainer references exist */

    /* Remove possible self-mntner reference from new maint submission's */
    if (op == iADD       &&
	mb_list2 != NULL && 
	!strcmp (ti->obj_type, "mntner")) {
      /*fprintf (dfile, "remove possible maintainer reference from maint (%s) list (%s)\n", 
	ti->obj_key, mb_list2);*/
      mb_list2 = filter_duplicates (tr, mb_list2, ti->obj_key);
    }
    
    /* For speed up, remove maintainers found in authorization determination */
    if (op == iREPLACE && ti->mnt_by[0] != '\0') {
      mb_list2 = strdup (ti->mnt_by);
      /* filter out all mntner ref's in mb_list2 that are in mb_exist_list */
      mb_list2 = filter_duplicates (tr, mb_list2, mb_exist_list);
    }
    
    /* Maintainer references for new objects must exist */
    if ((ret_code = mnt_by_exist (tr, sockfd, ti, &mb_list2, num_trans)) != AUTH_PASS_C)
      goto FREE_MEM;
    
    mb_list = filter_duplicates (tr, mb_list, mb_list2);
  }
  
  /* For remaining members of mb_list (ie, DEL and REPLACE), get 
   * the maintainer and pick off "mnt-nfy" fields, add the new email 
   * addrs to ti->notify
   */
  if (mb_list != NULL) {
    mnt_by_exist (tr, sockfd, ti, &mb_list, num_trans);
    ti->maint_no_exist = free_mem (ti->maint_no_exist);
  }
  
FREE_MEM:
  free_mem (mb_list);
  free_mem (mb_list2);
  free_mem (mb_exist_list);

  /*fprintf (dfile, "perform_auth_check () returns-(%d)\n---\n\n", ret_code);*/
  return ret_code;
}

/* This routine collects all maintainer references collected from an ADD or REPLACE
 * object and check's if they exist.  For each referenced maintainer the
 * mnt-nfy's are collected in ti->notify.
 *
 * Return:
 *   AUTH_PASS_C if all mb_list members exist and ti->notify is appended
 *   with mnt_nfy's from the referenced maintainer's.
 *
 *   MNT_NO_EXIST_C if one or more of the mb_list members do not exist
 *   and are returned in ti->maint_no_exist.
 */
enum AUTH_CODE mnt_by_exist (trace_t *tr, int *sockfd, trans_info_t *ti, char **mb_list, 
			     int *num_trans) {
  char *p, *q, c;
  char fname[256], *ret_list = NULL, *mnt_nfy_list;
  long obj_pos;
  FILE *fd;

  /*
  if (*mb_list == NULL)
    fprintf (dfile, "enter mb_by_exist () mb_list is NULL\n");
  else
    fprintf (dfile, "enter mnt_by_exist (%s)\n", *mb_list);
  */

  p = q = *mb_list;
  while (find_token (&p, &q) > 0) {
    c = *q;
    *q = '\0';
    /*fprintf (dfile, "mnt_by_exist () see if (%s) exists\n", p);*/
    if ((fd = get_db_object (tr, num_trans, fname, sockfd, SHORT_OBJECT,
			     "mntner", p, ti->source, &obj_pos)) == NULL) {
	ret_list = myconcat (ret_list, p);
	/*fprintf (dfile, "mnt_by_exist () (%s) does not exist; aggregate list (%s)\n", p, ret_list);*/
    }
    else {
      if (ret_list == NULL) {
        mnt_nfy_list = cull_attribute (tr, fd, obj_pos, (u_int) MNT_NFY_ATTR);
        ti->notify = myconcat (ti->notify, mnt_nfy_list);

	/*if (mnt_nfy_list != NULL)
	  fprintf (dfile,"mnt_by_exist () (%s) exist's; adding notify (%s)\n", p, mnt_nfy_list);
	  if (ti->notify != NULL)
	  fprintf (dfile,"mnt_by_exist () (%s) aggregate notify list\n", ti->notify);*/

        free_mem (mnt_nfy_list);
      }
      /* don't delete objects in our output file */
      if (obj_pos == 0L) {
	fclose (fd);
	remove (fname);
      }
    }
    *q = c;
  }

  if (ret_list != NULL) {
    ti->maint_no_exist = myconcat (ti->maint_no_exist, ret_list);
    /*fprintf (dfile, " mnt_by_exist ret_code (MNT_NO_EXIST_C)\n");*/
    return  MNT_NO_EXIST_C;
  }
  /*fprintf (dfile, " mnt_by_exist ret_code (AUTH_PASS_C)\n");*/
  *mb_list = free_mem (*mb_list);
  return AUTH_PASS_C;
}

/* Control the process of determining if one of the maintainer's referenced
 * in this object passes authorization.
 *
 * Return:
 *   one of the 'enum AUTH_CODE's (see irrauth.h)
 */
enum AUTH_CODE auth_process (trace_t *tr, int op, int *sockfd, FILE *fd, long fpos, 
			     trans_info_t *ti, int *num_trans, char **mb_list,
                             char **mb_exist_list) {

  /*fprintf (dfile, "\n---enter auth_process () op-(%s)....\n", str_op[op]);*/
  
  if (op == iADD) {
    if (ti->mnt_by != NULL)
      *mb_list = strdup (ti->mnt_by);
    else
      *mb_list = NULL;
  }
  else
    *mb_list = cull_attribute (tr, fd, fpos, (u_int) MNT_BY_ATTR);
  
  if (ti->override) {
    if (check_crypt_passwd (ti->override, SUPER_PASSWD))
      return AUTH_PASS_C;
    else {
      trace (NORM, tr, "** Auth failure -- override password did not match\n");
      return BAD_OVERRIDE_C;
    }
  }

  /* bounce out new maintainer requests unless you 
   * know the super user override password
   */
  if (op == iADD && !strcmp (ti->obj_type, "mntner"))
    return NEW_MNT_ERROR_C;
  
  if (*mb_list == NULL) {
    if (op == iADD) /* new submissions must have a valid maint */
      return AUTH_FAIL_C;
    else            /* if there are junk objects in the DB we must go along :( */
      return AUTH_PASS_C;
  }
  
  /* see if one of the referenced mntner's pass authorization */
  return mntner_mblist_check (tr, op, sockfd, ti, num_trans, mb_list, mb_exist_list);
}

/* Given a maintainer reference (*mntner), this function passes the 
 * auth: lines from the maintainer to do_auth_check () for authorization. 
 * mnt_nfy and upd_to fields are also collected in ti->notify and ti->forward.
 * 
 * Return:
 *  AUTH_PASS_C if authorisation passes.  The mny_nfy references from
 *  all the maintainers it took to establish authorisation (ie, some
 *  mnt_nfy ref's may still need to be collected) are appended to 'ti->notify'.
 *
 *  AUTH_FAIL_C if authorisation could not be obtained.  In this case
 *  ti->forward will have all upd_to: ref's.
 *
 *  MNT_NO_EXIST_C if the referenced maintainer does not exist.
 */
enum AUTH_CODE auth_ok (trace_t *tr, int op, int *sockfd, trans_info_t *ti, 
			char *mntner, int *mntner_exists, int *num_trans) {
  int in_attr = 0, skip = 0;
  long obj_pos, fpos;
  FILE *fd;
  enum AUTH_CODE ret_code = AUTH_FAIL_C;
  char buf[MAXLINE], fname[256], *q;

  if ((fd = get_db_object (tr, num_trans, fname, sockfd, SHORT_OBJECT,
			   "mntner", mntner, ti->source, &obj_pos)) == NULL) {
    trace (NORM, tr, "Maintainer (%s) does not exist. Authorization fails!\n", mntner);
    *mntner_exists = 0;
    if (op == iADD)
      return MNT_NO_EXIST_C;
    else
      return AUTH_FAIL_C;
  }
  else {
    /*fprintf (dfile, "auth_ok () say's (%s) exists, so gather notify's/auth: lines...\n", mntner);*/
    *mntner_exists = 1;
    /* save the fpos to restore on exit */
    fpos = ftell (fd);
    fseek (fd, obj_pos, SEEK_SET);

    while (fgets (buf, MAXLINE - 1, fd) != NULL && buf[0] != '\n') {
      if ((in_attr = find_attr (tr, buf, in_attr, 
				(u_int) (AUTH_ATTR|MNT_NFY_ATTR|UPD_TO_ATTR), &q))) {
	/*fprintf (dfile, "auth_ok () find_attr: (%s)\n", q);*/
	if (in_attr == AUTH_ATTR) {
	  if (!skip && (ret_code = do_auth_check (tr, q, ti)) == AUTH_PASS_C)
	    skip = 1;
	}
	else if (in_attr == MNT_NFY_ATTR)
	  ti->notify = myconcat (ti->notify, q);
	else
	  ti->forward = myconcat (ti->forward, q);
      }
    }

    /* JW later it would be better to keep file around till the
     * end for possible reuse */
    /* ie, don't close and remove our output file */
    if (obj_pos == 0L) {
      fclose (fd);
      remove (fname);
    }
    else
      fseek (fd, fpos, SEEK_SET);
  }

  /* fprintf (dfile, "auth_ok () ret_code-(%d)\n", ret_code);*/
  return ret_code;
}

/* Place a copy of the referenced DB object into file 'fname' should the 
 * DB object exist.  The object can come from our transaction output file
 * (latest copy) or from IRRd.  'obj_size' can be FULL_OBJECT or SHORT_OBJECT.
 * SHORT_OJBECT will return only auth, mnt_nfy, mb_by, and notify fields.
 *
 * Return:
 *  Stream file pointer to the object if it was retrieved successfully
 *  and the name of the file in 'fname'.
 *  NULL otherwise.
 */
FILE *get_db_object (trace_t *tr, int *num_trans, char *fname, int *sockfd, 
		     int obj_size, char *obj_type, char *obj_key, 
		     char *source, long *obj_pos) {
  obj_lookup_t *obj;
  FILE *fp;

  /*fprintf (dfile, "\n---\nenter get_db_object () key-(%s)\n", obj_key);*/

  /* see if the object is in our trans obj list first */
  if ((obj = find_trans_object (tr, &gstart, source, obj_type, obj_key)) != NULL) {
    /* object has been deleted */
    if (obj->state == 0)
      return NULL;
    else {
      /*fprintf (dfile, "get_db_object () found trans obj (%s) fpos (%ld)\n", obj_key, obj->fpos);*/
      *obj_pos = obj->fpos;
      return obj->fd;
    }
  }

  /* The object was not in our transaction list, get it from IRRd. */
  *obj_pos = 0L;
  strcpy (fname, tmpfntmpl);
  return IRRd_fetch_obj (tr, fname, num_trans, sockfd, obj_size,
			 obj_type, obj_key, source, MAX_IRRD_OBJ_SIZE,
			 IRRd_HOST, IRRd_PORT);
}


enum AUTH_CODE do_auth_check (trace_t *tr, char *auth_line, trans_info_t *ti) {
  enum AUTH_CODE ret_code = AUTH_FAIL_C;
  char *p, *q, c;
  regex_t re;

  /*fprintf (dfile, "do_auth_check () found an auth token-(%s)\n", auth_line);*/
  
  if (!strncmp ("NONE", auth_line, 4)) {
    /*fprintf (dfile, "do_auth_check () auth NONE!\n");*/
    return AUTH_PASS_C;
  }

  p = q = strchr (auth_line, ' ');
  if (p == NULL || find_token (&p, &q) < 0)
    return AUTH_FAIL_C;
  
  if (!strncmp ("MAIL-FROM", auth_line, 9) && ti->sender_addrs != NULL) {
    /*fprintf (dfile, "do_auth_check () from-(%s) auth from-(%s)\n", ti->sender_addrs, p);*/
    if (!regcomp (&re, p, REG_EXTENDED|REG_NOSUB|REG_ICASE) != 0) {
      if (!regexec(&re, ti->sender_addrs, (size_t) 0, NULL, 0)) {
	/*fprintf (dfile, "%%%%%%%%%%%%%%%% regex (%s) passed!\n", p); */
	ret_code = AUTH_PASS_C;
      }
      else
	/*fprintf (dfile, "%%%%%%%%%%%%%%%% regex (%s) failed!\n", p); */

      regfree (&re);
    }
  }
  else if (!strncmp ("PGP-FROM", auth_line, 8) && ti->pgp_from != NULL) {
    /*fprintf (dfile, "do_auth_check () pgp-from-(%s) auth pgp-from-(%s)\n", ti->pgp_from, p);*/

    if (!regcomp (&re, p, REG_EXTENDED|REG_NOSUB|REG_ICASE) != 0) {
      p = q = ti->pgp_from;
      while (find_token (&p, &q) > 0) {
	c = *q;
	*q = '\0';
	/* JW take out if (!strcasecmp (ti->pgp_from, p)) */
	if (!regexec (&re, p, (size_t) 0, NULL, 0))
	  ret_code = AUTH_PASS_C;
	/*fprintf (dfile, "do_auth_check (): PGP-FROM (%s) result (%d)\n", p, ret_code);*/
	*q = c;
	if (ret_code == AUTH_PASS_C)
	  break;
      }

      regfree (&re);
    }
  }
  else if (!strncmp ("PGP-KEY", auth_line, 7) && ti->pgp_key != NULL) {
    /*fprintf (dfile, "do_auth_check () pgp-from-(%s) auth pgp-from-(%s)\n", ti->pgp_from, p);*/
    if (!strcasecmp (ti->pgp_key, p))
      ret_code = AUTH_PASS_C;
  }
  else if (!strncmp ("CRYPT-PW", auth_line, 8) && ti->crypt_pw != NULL) {
    /*fprintf (dfile, "do_auth_check () cleartxt passwd-(%s) crypt passwd-(%s)\n",ti->crypt_pw, p);*/
    if (check_crypt_passwd (ti->crypt_pw, p))
      ret_code = AUTH_PASS_C;
  }
  
  return ret_code;
}

/* Determine if the cleartxt password matches the encrypted password.
 * 
 * Return:
 *   1 if cleartxt password is a match
 *   0 otherwise
 */
int check_crypt_passwd (char *cleartxt, char *encrypted) {

  if (cleartxt == NULL ||
      encrypted == NULL)
    return 0;

  return !strcmp ((char *) crypt (cleartxt, encrypted), encrypted);
}

/* Fold the transaction outcome into the 'ti' struct.  The 'ti' struct
 * info will be translated into object header text in the output file for 
 * notify.  This function also performs the final notify/forward address
 * processing (which is dependent on the transaction outcome/'ret_code').
 *
 * Return:
 *  void
 */
void update_trans_info (trace_t *tr, enum AUTH_CODE ret_code, trans_info_t *ti,
			int op, FILE *fd_new, long fpos, FILE *fd_old, long old_obj_pos) {
  char *obj_notifies;

  if (ret_code == AUTH_PASS_C) {
    /*fprintf (dfile, ">>>>>>>enter ret_code AUTH_PASS_C process...\n");*/
    if (op != iDEL) { /* JW can take out of this works &&
			 (obj_notifies = cull_attribute (dfile, fd_new, fpos, 
			 (u_int) (NOTIFY_ATTR|MNT_NFY_ATTR))) != NULL) {
			 ti->notify = myconcat (ti->notify, obj_notifies);
			 free_mem (obj_notifies);
			 */
      ti->notify = myconcat (ti->notify, ti->check_notify);
      ti->notify = myconcat (ti->notify, ti->check_mnt_nfy);
    }
    /* fprintf (dfile, "checking old/del object for notify's\n");*/
    if (fd_old != NULL &&
	(obj_notifies = cull_attribute (tr, fd_old, old_obj_pos, 
					(u_int) NOTIFY_ATTR)) != NULL) {
      ti->notify = myconcat (ti->notify, obj_notifies);
      free_mem (obj_notifies);
    }

    /*if (ti->notify != NULL)
      fprintf (dfile, "ret_code AUTH_PASS_C notify list (%s)\n", ti->notify);*/
  }
  else if (ret_code == DEL_NO_EXIST_C)
    ti->del_no_exist = 1;
  else if (ret_code ==  NOOP_C) {
    set_op_type (ti->op, NOOP_OP);
  }
  else if (ret_code == AUTH_FAIL_C) {
    ti->authfail = 1;
    /* Don't notify upd-to's on ADD operations */
    if (op == iADD)
      ti->forward = free_mem (ti->forward);
  }
  else if (ret_code == NEW_MNT_ERROR_C)
    ti->new_mnt_error = 1;
  else if (ret_code == BAD_OVERRIDE_C)
    ti->bad_override = 1;
  
  if (CLEAR_NOTIFY & ret_code)
    ti->notify = free_mem (ti->notify);

  if (CLEAR_FORWARD & ret_code)
    ti->forward = free_mem (ti->forward);

  /* Bookeeping: remove trans fields not needed by notify */
  if (ret_code != BAD_OVERRIDE_C)
    ti->override    = free_mem (ti->override);
  ti->check_notify  = free_mem (ti->check_notify);
  ti->check_mnt_nfy = free_mem (ti->check_mnt_nfy);
}

/* Given a mnt_by: list from an object, control the process of
 * determining if any of the referenced maintainers pass authorization.
 * 
 * Return:
 *   One of the legal 'enum AUTH_CODE's
 *   Function will also remove the mntner references from 'mb_list'
 *   that were checked in the process of determining authorization.
 *   Remaining members of 'mb_list' will need to be checked for mnt_nfy
 *   references (when authorization passes).  If authorization fails, 
 *   all referenced 'upd_to:'s will have been gathered in 'ti->forward'.
 */
enum AUTH_CODE mntner_mblist_check (trace_t *tr, int op, int *sockfd, trans_info_t *ti, 
				    int *num_trans, char **mb_list, char **mb_exist_list) {
  char *p, *q, c;
  int mntner_exists;
  enum AUTH_CODE ret_code = AUTH_FAIL_C;
  
  /* see if any of the referenced maintainers pass auth */
  p = q = *mb_list;
  while (find_token (&p, &q) > 0) {
    c = *q;
    *q = '\0';
    /*fprintf (dfile, "auth_process () check maint-(%s)\n", p);*/
    ret_code = auth_ok (tr, op, sockfd, ti, p, &mntner_exists, num_trans); 
    /*fprintf (dfile, "auth_process () auth_ok ret_code-(%d)\n", ret_code);*/
    
    /* Save mntner's that exist to avoid expensive duplicate lookups */
    if (mntner_exists)
      *mb_exist_list = myconcat (*mb_exist_list, p);
    
    if (ret_code == MNT_NO_EXIST_C) {
      if (op == iADD) {
	ti->maint_no_exist = myconcat (ti->maint_no_exist, p);
	break;
      }
      ret_code = AUTH_FAIL_C;
    }
    
    *q = c;
    
    /* trim down the mb_list for later gathering of mnt_nfy's */
    if (ret_code == AUTH_PASS_C) {
      if (find_token (&p, &q) > 0)
	q = strdup (p);
      else
	q = NULL;
      free_mem (*mb_list);
      *mb_list = q;
      break;
    }
  }

  return ret_code;
}
