/* $Id: qaccess.pc 4016 2010-08-26 02:49:36Z zacheiss $
 *
 * Check access to queries
 *
 * Copyright (C) 1987-1998 by the Massachusetts Institute of Technology
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#include <mit-copyright.h>
#include "mr_server.h"
#include "qrtn.h"
#include "query.h"

#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#ifndef _PROC_
#include <resolv.h>
#endif

EXEC SQL INCLUDE sqlca;

RCSID("$HeadURL: svn+ssh://svn.mit.edu/moira/trunk/moira/server/qaccess.pc $ $Id: qaccess.pc 4016 2010-08-26 02:49:36Z zacheiss $");

extern char *whoami;
extern int dbms_errno, mr_errcode;

EXEC SQL WHENEVER SQLERROR DO dbmserr();


/* Specialized Access Routines */

/* access_user - verify that client name equals specified login name
 *
 *  - since field validation routines are called first, a users_id is
 *    now in argv[0] instead of the login name.
 */

int access_user(struct query *q, char *argv[], client *cl)
{
  if (cl->users_id != *(int *)argv[0])
    return MR_PERM;
  else
    return MR_SUCCESS;
}

int access_update_user(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int users_id, unix_uid, status, comments, secure, sponsor_id;
  char login[USERS_LOGIN_SIZE], shell[USERS_SHELL_SIZE];
  char winconsoleshell[USERS_WINCONSOLESHELL_SIZE], last[USERS_LAST_SIZE];
  char first[USERS_FIRST_SIZE], middle[USERS_MIDDLE_SIZE];
  char clearid[USERS_CLEARID_SIZE], type[USERS_TYPE_SIZE];
  char signature[USERS_SIGNATURE_SIZE], sponsor_type[USERS_SPONSOR_TYPE_SIZE];
  char expiration[USERS_EXPIRATION_SIZE], alternate_email[USERS_ALTERNATE_EMAIL_SIZE];
  char alternate_phone[USERS_ALTERNATE_PHONE_SIZE];
  EXEC SQL END DECLARE SECTION;

  /* The two fields we let users update themselves didn't appear until
   * version 11.
   */
  if (q->version < 11)
    return MR_PERM;

  if (cl->users_id != *(int *)argv[0])
    return MR_PERM;

  users_id = *(int *)argv[0];

  EXEC SQL SELECT u.login, u.unix_uid, u.shell, u.winconsoleshell, u.last,
    u.first, u.middle, u.status, u.clearid, u.type, u.comments, u.signature,
    u.secure, u.sponsor_type, u.sponsor_id, u.expiration, u.alternate_email,
    u.alternate_phone INTO :login, :unix_uid, :shell, :winconsoleshell, :last, :first,
    :middle, :status, :clearid, :type, :comments, :signature, :secure, :sponsor_type,
    :sponsor_id, :expiration, :alternate_email, :alternate_phone
    FROM USERS u WHERE u.users_id = :users_id;

  /* None of these things can have changed. */
  if (strcmp(argv[1], strtrim(login)) ||
      (unix_uid != atoi(argv[2])) ||
      strcmp(argv[3], strtrim(shell)) ||
      strcmp(argv[4], strtrim(winconsoleshell)) ||
      strcmp(argv[5], strtrim(last)) ||
      strcmp(argv[6], strtrim(first)) ||
      strcmp(argv[7], strtrim(middle)) ||
      (status != atoi(argv[8])) ||
      strcmp(argv[9], strtrim(clearid)) ||
      strcmp(argv[10], strtrim(type)) ||
      (comments != *(int *)argv[11]) ||
      strcmp(argv[12], strtrim(signature)) ||
      (secure != atoi(argv[13])))
    return MR_PERM;

  /* These fields added in version 12 of the query. */
  if (q->version >= 12)
    {
      if (strcmp(argv[16], strtrim(sponsor_type)) ||
	  (sponsor_id != *(int *)argv[17]) ||
	  strcmp(argv[18], strtrim(expiration)))
	return MR_PERM;
    }

  /* These fields added in version 14 of the query. */
  if (q->version >= 14)
    {
      if (strcmp(argv[19], strtrim(alternate_email)) ||
	  strcmp(argv[20], strtrim(alternate_phone)))
	return MR_PERM;
    }

  return MR_SUCCESS;
}

/* access_login - verify that client name equals specified login name
 *
 *   argv[0...n] contain search info.  q->
 */

int access_login(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int id;
  EXEC SQL END DECLARE SECTION;

  if (q->argc != 1)
    return MR_ARGS;

  if (!strcmp(q->shortname, "gual"))
    {
      EXEC SQL SELECT users_id INTO :id FROM users
	WHERE login = :argv[0] AND users_id != 0;
    }
  else if (!strcmp(q->shortname, "gubl"))
    {
      EXEC SQL SELECT users_id INTO :id FROM users u
	WHERE u.login = :argv[0] AND u.users_id != 0;
    }
  else if (!strcmp(q->shortname, "guau"))
    {
      EXEC SQL SELECT users_id INTO :id FROM users
	WHERE unix_uid = :argv[0] AND users_id != 0;
    }
  else if (!strcmp(q->shortname, "gubu"))
    {
      EXEC SQL SELECT users_id INTO :id FROM users u
	WHERE u.unix_uid = :argv[0] AND u.users_id != 0;
    }

  if (sqlca.sqlcode == SQL_NO_MATCH)
    return MR_NO_MATCH; /* ought to be MR_USER, but this is what
			   gual returns, so we have to be consistent */
  else if (sqlca.sqlerrd[2] != 1 || id != cl->users_id)
    return MR_PERM;
  else
    return MR_SUCCESS;
}


/* access_spob - check access for set_pobox */

int access_spob(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int id;
  EXEC SQL END DECLARE SECTION;
  int status;

  if (!strcmp(argv[1], "IMAP"))
    {
      EXEC SQL SELECT owner INTO :id FROM filesys f 
	WHERE f.label = :argv[2] AND f.type = 'IMAP' AND
	f.lockertype = 'USER';
      if (cl->users_id != id)
	return MR_PERM;
    }

  /* Non-query owners can't forward mail to a POSTOFFICE or MAILHUB server,
   * nor to a nonresolving domain.
   */
  if (!strcmp(argv[1], "SMTP") || !strcmp(argv[1], "SPLIT"))
    {
      status = check_mail_string(argv[2]);
      if (status)
	return status;
    }
  
  if (cl->users_id != *(int *)argv[0])
    return MR_PERM;
  else
    return MR_SUCCESS;
}


/* access_list - check access for most list operations
 *
 * Inputs: argv[0] - list_id
 *	    q - query name
 *	    argv[2] - member ID (only for queries "amtl" and  "dmfl")
 *	    argv[7] - group ID (only for query "ulis")
 *          cl - client name
 *
 * - check that client is a member of the access control list
 * - OR, if the query is add_member_to_list or delete_member_from_list
 *	and the list is public, allow access if client = member
 */

int access_list(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int list_id, acl_id, flags, gid, users_id, member_id, member_acl_id;
  int memacl_id, mailman, mailman_id;
  char acl_type[LIST_ACL_TYPE_SIZE], name[LIST_NAME_SIZE], *newname;
  char member_acl_type[LIST_ACL_TYPE_SIZE], memacl_type[LIST_ACL_TYPE_SIZE];
  EXEC SQL END DECLARE SECTION;
  int status, cnt;
  char *buf;

  list_id = *(int *)argv[0];
  member_id = *(int *)argv[2];
  EXEC SQL SELECT acl_id, acl_type, memacl_id, memacl_type,
    gid, publicflg, name, mailman, mailman_id
    INTO :acl_id, :acl_type, :memacl_id, :memacl_type, 
    :gid, :flags, :name, :mailman, :mailman_id
    FROM list
    WHERE list_id = :list_id;

  if (sqlca.sqlerrd[2] != 1)
    return MR_INTERNAL;

  /* if update_list, don't allow them to change the GID or rename to a
	 username other than their own */
  if (!strcmp("ulis", q->shortname))
    {
      if (!strcmp(argv[7], UNIQUE_GID))
	{
	  if (gid != -1)
	    return MR_PERM;
	}
      else
	{
	  if (gid != atoi(argv[7]))
	    return MR_PERM;
	}

      newname = argv[1];

      /* Check that it doesn't conflict with the Grouper namespace. */
      if (strlen(newname) > 4 && isdigit(newname[2]) && 
	  isdigit(newname[3]) && newname[4] == '-')
	{
	  if (!strncasecmp(newname, "fa", 2) ||
	      !strncasecmp(newname, "sp", 2) ||
	      !strncasecmp(newname, "su", 2) ||
	      !strncasecmp(newname, "ja", 2))
	    return MR_RESERVED;
	}
      
      /* Don't let anyone take owner-foo list names.  They interact 
       * weirdly with the aliases automatically generated by 
       * mailhub.gen.
       */
      if (!strncasecmp(newname, "owner-", 6))
	return MR_RESERVED;
      
      EXEC SQL SELECT users_id INTO :users_id FROM users
	WHERE login = :newname;
      if ((sqlca.sqlcode != SQL_NO_MATCH) && strcmp(strtrim(name), newname) &&
	  (users_id != cl->users_id))
	return MR_PERM;

      /* For modern enough clients, don't allow ordinary users to toggle
       * the mailman bit or change the server.
       */
      if (q->version >= 10)
	{
	  if (mailman != atoi(argv[9]))
	    return MR_PERM;

	  if (mailman_id != *(int *)argv[10])
	    return MR_PERM;
	}
    }

  /* Don't allow non-query owners to add STRINGs to lists if they end
   * in a domain that's MIT.EDU or one of the hosts that provide the
   * MAILHUB or POSTOFFICE services.
   */
  if (!strcmp(q->shortname, "amtl") || !strcmp(q->shortname, "atml"))
    {
      if (!strcmp("STRING", argv[1]))
	{
	  buf = malloc(0);
	  status = id_to_name(*(int *)argv[2], STRINGS_TABLE, &buf);
	  if (status)
	    return status;

	  status = check_mail_string(buf);
	  free(buf);
	  if (status)
	    return status;
	}
    }

  /* check for client in access control list and return success right 
   * away if it's there. */
  if (find_member(acl_type, acl_id, cl))
    return MR_SUCCESS;

  /* If not amtl, atml, or dmfl, we lose. */
  if (strcmp(q->shortname, "amtl") && strcmp(q->shortname, "atml") &&
      strcmp(q->shortname, "dmfl") && strcmp(q->shortname, "tmol"))
    return MR_PERM;

  if (find_member(memacl_type, memacl_id, cl))
    return MR_SUCCESS;

  if (flags || q->type == MR_Q_DELETE)
    {
      if (!strcmp("USER", argv[1]) && *(int *)argv[2] == cl->users_id)
        return MR_SUCCESS;
      if (!strcmp("KERBEROS", argv[1]) && *(int *)argv[2] == -cl->client_id)
        return MR_SUCCESS;
      if (!strcmp("LIST", argv[1]) && !strcmp("dmfl", q->shortname))
	{
	  EXEC SQL SELECT acl_id, acl_type INTO :member_acl_id, 
	    :member_acl_type 
	    FROM list
	    WHERE list_id = :member_id; 
	  
	  if (find_member(member_acl_type, member_acl_id, cl))
	    return MR_SUCCESS;
	}
    }

  /* Otherwise fail. */
  return MR_PERM;
}


/* access_visible_list - allow access to list only if it is not hidden,
 *	or if the client is on the ACL
 *
 * Inputs: argv[0] - list_id
 *         cl - client identifier
 */

int access_visible_list(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int list_id, acl_id, memacl_id, flags ;
  char acl_type[LIST_ACL_TYPE_SIZE], memacl_type[LIST_ACL_TYPE_SIZE];
  EXEC SQL END DECLARE SECTION;
  int status;

  list_id = *(int *)argv[0];
  EXEC SQL SELECT hidden, acl_id, acl_type, memacl_id, memacl_type
    INTO :flags, :acl_id, :acl_type, :memacl_id, :memacl_type
    FROM list
    WHERE list_id = :list_id;
  if (sqlca.sqlerrd[2] != 1)
    return MR_INTERNAL;
  if (!flags)
    return MR_SUCCESS;

  /* check for client in access control list */
  status = find_member(acl_type, acl_id, cl);
  if (!status)
    {
      status = find_member(memacl_type, memacl_id, cl);
      if (!status)
	return MR_PERM;
    }
  return MR_SUCCESS;
}


/* access_vis_list_by_name - allow access to list only if it is not hidden,
 *	or if the client is on the ACL
 *
 * Inputs: argv[0] - list name
 *         cl - client identifier
 */

int access_vis_list_by_name(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int acl_id, memacl_id, flags, rowcount, list_id;
  char acl_type[LIST_ACL_TYPE_SIZE], memacl_type[LIST_ACL_TYPE_SIZE];
  char *listname;
  EXEC SQL END DECLARE SECTION;
  int status;

  listname = argv[0];
  EXEC SQL SELECT hidden, acl_id, acl_type, memacl_id, memacl_type, list_id 
    INTO :flags, :acl_id, :acl_type, :memacl_id, :memacl_type, :list_id
    FROM list 
    WHERE name = :listname;

  rowcount = sqlca.sqlerrd[2];
  if (rowcount > 1)
    return MR_WILDCARD;
  if (rowcount == 0)
    return MR_NO_MATCH;
  if (!flags)
    return MR_SUCCESS;

  /* If the user is a member of the acl, memacl, or the list itself,
   * accept them.
   */
  status = find_member(acl_type, acl_id, cl);
  if (!status)
    status = find_member(memacl_type, memacl_id, cl);
  if (!status)
    status = find_member("LIST", list_id, cl);
  if (!status)
    return MR_PERM;

  return MR_SUCCESS;
}


/* access_member - allow user to access member of type "USER" and name matches
 * username, or to access member of type "KERBEROS" and the principal matches
 * the user, or to access member of type "LIST" and list is one that user is
 * on the acl of, or the list is visible.  Allow anyone to look up list
 * memberships of MACHINEs.
 */

int access_member(struct query *q, char *argv[], client *cl)
{
  if (!strcmp(argv[0], "LIST") || !strcmp(argv[0], "RLIST"))
    return access_visible_list(q, &argv[1], cl);

  if (!strcmp(argv[0], "USER") || !strcmp(argv[0], "RUSER"))
    {
      if (cl->users_id == *(int *)argv[1])
	return MR_SUCCESS;
    }

  if (!strcmp(argv[0], "KERBEROS") || !strcmp(argv[0], "RKERBEROS"))
    {
      if (cl->client_id == -*(int *)argv[1])
	return MR_SUCCESS;
    }

  if (!strcmp(argv[0], "MACHINE") || !strcmp(argv[0], "RMACHINE"))
    return MR_SUCCESS;	 

  return MR_PERM;
}


/* access_qgli - special access routine for Qualified_get_lists.  Allows
 * access iff argv[0] == "TRUE" and argv[2] == "FALSE".
 */

int access_qgli(struct query *q, char *argv[], client *cl)
{
  if (!strcmp(argv[0], "TRUE") && !strcmp(argv[2], "FALSE"))
    return MR_SUCCESS;
  return MR_PERM;
}


/* access_service - allow access if user is on ACL of service.  Don't
 * allow access if a wildcard is used.
 */

int access_service(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int acl_id;
  char *name, acl_type[LIST_ACL_TYPE_SIZE];
  EXEC SQL END DECLARE SECTION;
  int status;
  char *c;

  name = argv[0];
  for (c = name; *c; c++)
    {
      if (islower(*c))
	*c = toupper(*c);
    }
  EXEC SQL SELECT acl_id, acl_type INTO :acl_id, :acl_type FROM servers
    WHERE name = :name;
  if (sqlca.sqlerrd[2] > 1)
    return MR_PERM;

  /* check for client in access control list */
  status = find_member(acl_type, acl_id, cl);
  if (!status)
    return MR_PERM;

  return MR_SUCCESS;
}


/* access_filesys - verify that client is owner or on owners list of filesystem
 *	named by argv[0]
 */

int access_filesys(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int users_id, list_id;
  char *name;
  EXEC SQL END DECLARE SECTION;
  int status;

  name = argv[0];
  EXEC SQL SELECT owner, owners INTO :users_id, :list_id FROM filesys
    WHERE label = :name;

  if (sqlca.sqlerrd[2] != 1)
    return MR_PERM;
  if (users_id == cl->users_id)
    return MR_SUCCESS;
  status = find_member("LIST", list_id, cl);
  if (status)
    return MR_SUCCESS;
  else
    return MR_PERM;
}


/* access_host - successful if owner of host, or subnet containing host
 */

int access_host(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int mid, sid, id, subnet_status;
  char mtype[MACHINE_OWNER_TYPE_SIZE], stype[SUBNET_OWNER_TYPE_SIZE];
  char *account_number;
  EXEC SQL END DECLARE SECTION;
  int status, idx;

  if (q->version < 6)
    idx = 0;
  else if (q->version >= 6 && q->version < 8)
    idx = 1;
  else
    idx = 2;
  
  if (q->type == MR_Q_APPEND)
    {
      /* Non-query owner must set use to zero */
      if (atoi(argv[6 + idx]) != 0)
	return MR_PERM;

      /* ... and start the hostname with a letter */
      if (isdigit(argv[0][0]))
	return MR_BAD_CHAR;

      id = *(int *)argv[8 + idx];
      EXEC SQL SELECT s.owner_type, s.owner_id, s.status
	INTO :stype, :sid, :subnet_status FROM subnet s
	WHERE s.snet_id = :id;
      mid = 0;

      /* Non query owner must provide valid billing information. */
      if (q->version >= 8)
	{
	  if (subnet_status == SNET_STATUS_BILLABLE)
	    {
	      account_number = argv[7];
	      EXEC SQL SELECT account_number FROM accountnumbers 
		WHERE account_number = :account_number;
	      if (sqlca.sqlcode == SQL_NO_MATCH)
		return MR_ACCOUNT_NUMBER;
	    }
	}

      if (find_member(stype, sid, cl))
	return MR_SUCCESS;
      else
	return MR_PERM;
    }
  else /* q-type == MR_Q_UPDATE */
    {
      EXEC SQL BEGIN DECLARE SECTION;
      int status, acomment, use, ocomment, snid;
      char contact[MACHINE_CONTACT_SIZE], address[MACHINE_ADDRESS_SIZE];
      char name[MACHINE_NAME_SIZE];
      char billing_contact[MACHINE_BILLING_CONTACT_SIZE];
      EXEC SQL END DECLARE SECTION;

      id = *(int *)argv[0];
      EXEC SQL SELECT m.name, m.use, m.contact, m.billing_contact, m.status, 
	m.address, m.owner_type, m.owner_id, m.acomment, m.ocomment, m.snet_id,
	s.owner_type, s.owner_id, s.status INTO :name, :use, :contact, 
	:billing_contact, :status, :address, :mtype, :mid, :acomment, 
	:ocomment, :snid, :stype, :sid, :subnet_status
	FROM machine m, subnet s
	WHERE m.mach_id = :id AND s.snet_id = m.snet_id;
      if (dbms_errno)
	return mr_errcode;

      /* Non query owner must provide valid billing information. */
      if (q->version >= 8)
	{
	  if ((subnet_status == SNET_STATUS_BILLABLE) &&
	      (atoi(argv[10]) != 3))
	    {
	      account_number = argv[8];
	      EXEC SQL SELECT account_number FROM accountnumbers 
		WHERE account_number = :account_number;
	      if (sqlca.sqlcode == SQL_NO_MATCH)
		return MR_ACCOUNT_NUMBER;
	    }
	}

      /* non-query-owner cannot change use or ocomment */
      if ((use != atoi(argv[7 + idx])) || (ocomment != *(int *)argv[14 + idx]))
	return MR_PERM;

      /* or rename to start with digit */
      if (isdigit(argv[1][0]) && strcmp(strtrim(name), argv[1]))
	return MR_BAD_CHAR;

      if (!find_member(stype, sid, cl))
	{
	  if (find_member(mtype, mid, cl))
	    {
	      /* host owner also cannot change contact, status, address,
		 owner, or acomment */
	      if (strcmp(argv[6], strtrim(contact)) ||
		  (status != atoi(argv[8 + idx])) ||
		  strcmp(argv[10 + idx], strtrim(address)) ||
		  strcmp(argv[11 + idx], strtrim(mtype)) ||
		  (mid != *(int *)argv[12 + idx]) || 
		  (acomment != *(int *)argv[13 + idx]))
		return MR_PERM;
	      /* Billing contact field didn't appear until version 6 */
	      if (q->version >= 6)
		if (strcmp(argv[7], strtrim(billing_contact)))
		    return MR_PERM;
	    }
	  else
	    return MR_PERM;
	}

      /* If moving to a new subnet, make sure user is on acl there */
      id = *(int *)argv[9 + idx];
      if (id != snid)
	{
	  EXEC SQL SELECT owner_type, owner_id INTO :stype, :sid
	    FROM subnet WHERE snet_id=:id;
	  if (!find_member(stype, sid, cl))
	    return MR_PERM;
	}

      return MR_SUCCESS;
    }
}


/* access_ahal - check for adding a host alias.
 * successful if host has less then 2 aliases and (client is owner of
 * host or subnet).
 * If deleting an alias, any owner will do.
 */

int access_ahal(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int cnt, id, mid, sid;
  char mtype[MACHINE_OWNER_TYPE_SIZE], stype[SUBNET_OWNER_TYPE_SIZE];
  EXEC SQL END DECLARE SECTION;
  int status;

  if (q->type == MR_Q_RETRIEVE)
    return MR_SUCCESS;

  id = *(int *)argv[1];

  if (q->type == MR_Q_APPEND && isdigit(argv[0][0]))
    return MR_BAD_CHAR;

  EXEC SQL SELECT count(name) INTO :cnt from hostalias WHERE mach_id = :id;
  if (dbms_errno)
    return mr_errcode;
  /* if the type is MR_Q_APPEND, this is ahal and we need to make sure there
   * will be no more than 2 aliases.  If it's not, it must be dhal and
   * any owner will do.
   */
  if (q->type == MR_Q_APPEND && cnt >= 2)
    return MR_PERM;
  EXEC SQL SELECT m.owner_type, m.owner_id, s.owner_type, s.owner_id
    INTO :mtype, :mid, :stype, :sid FROM machine m, subnet s
    WHERE m.mach_id = :id and s.snet_id = m.snet_id;
  status = find_member(mtype, mid, cl);
  if (status)
    return MR_SUCCESS;
  status = find_member(stype, sid, cl);
  if (status)
    return MR_SUCCESS;
  else
    return MR_PERM;
}


/* access_snt - check for retrieving network structure
 */

int access_snt(struct query *q, char *argv[], client *cl)
{
  if (q->type == MR_Q_RETRIEVE)
    return MR_SUCCESS;

  return MR_PERM;
}


/* access_printer */
int access_printer(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char type[PRINTSERVERS_OWNER_TYPE_SIZE];
  int id, mach_id;
  EXEC SQL END DECLARE SECTION;
  int status, row;

  /* Check for aprn or uprn. */
  if (q->type == MR_Q_APPEND)
    row = 0;
  else
    row = 1;

  if (q->version < 13)
    mach_id = *(int *)argv[6 + row];
  else
    mach_id = *(int *)argv[PRN_RM + row];

  EXEC SQL SELECT owner_type, owner_id INTO :type, :id
    FROM printservers WHERE mach_id = :mach_id;
  if (sqlca.sqlcode)
    return MR_PERM;

  status = find_member(type, id, cl);
  if (status)
    return MR_SUCCESS;
  else
    return MR_PERM;
}

/* access_zephyr */
int access_zephyr(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char type[ZEPHYR_OWNER_TYPE_SIZE];
  char *class;
  int id;
  EXEC SQL END DECLARE SECTION;
  int status;

  class = argv[ZA_CLASS];
  EXEC SQL SELECT owner_type, owner_id INTO :type, :id
      FROM zephyr WHERE class = :class;
  if (sqlca.sqlcode)
    return MR_PERM;

  status = find_member(type, id, cl);
  if (status)
    return MR_SUCCESS;
  else
    return MR_PERM;
}

/* access_container - check access for most container operations
 *
 * Inputs: argv[0] - cnt_id
 *	    q - query name	  
 *          cl - client name
 *
 * - check if that client is a member of the access control list
 * - OR, if the query is add_machine_to_container or delete_machine_from_container
 *	check if the client is a memeber of the mem_acl list
 * - if the query is update_container and the container is to be renamed and
 *   it is a top-level container, only priviledged users can do it
 */

int access_container(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int cnt_id, acl_id, memacl_id, mach_id, machine_owner_id, flag;
  char acl_type[CONTAINERS_ACL_TYPE_SIZE], memacl_type[CONTAINERS_ACL_TYPE_SIZE];
  char name[CONTAINERS_NAME_SIZE], *newname;
  char machine_owner_type[MACHINE_OWNER_TYPE_SIZE];
  EXEC SQL END DECLARE SECTION;
  int status;

  cnt_id = *(int *)argv[0];
  
  /* if amcn or dmcn, container id is the second argument */
  if (strcmp(q->shortname, "amcn") == 0 || strcmp(q->shortname, "dmcn") == 0)
  {
	mach_id = *(int *)argv[0];
	cnt_id = *(int *)argv[1];
  }

  EXEC SQL SELECT acl_id, acl_type, memacl_id, memacl_type, name, publicflg
    INTO :acl_id, :acl_type, :memacl_id, :memacl_type, :name, :flag
    FROM containers
    WHERE cnt_id = :cnt_id;

  if (sqlca.sqlerrd[2] != 1)
    return MR_INTERNAL;

   /* trim off the trailing spaces */
   strcpy(name, strtrim(name));

  /* Only dbadmin can rename containers. */
  if (!strcmp(q->shortname, "ucon"))
  {
    newname = argv[1];
    if (strcmp(name, newname))
      return MR_PERM;
  }

  /* check for client in access control list and return success right 
   * away if it's there. */
  if (find_member(acl_type, acl_id, cl))
    return MR_SUCCESS;

  /* If not amcn, dmcn, we lose. */
  if (strcmp(q->shortname, "amcn") && strcmp(q->shortname, "dmcn"))
    return MR_PERM;

  if (find_member(memacl_type, memacl_id, cl))
    return MR_SUCCESS;

  /* if the container is public or the query is delete, grant access if client
   * is on owner list */
  if (flag || q->type == MR_Q_DELETE)
    {
	  EXEC SQL SELECT owner_type, owner_id INTO :machine_owner_type,
	    :machine_owner_id
	    FROM machine
	    WHERE mach_id = :mach_id;

	  if (sqlca.sqlerrd[2] == 1 && strcmp("NONE", machine_owner_type) &&
		find_member(machine_owner_type, machine_owner_id, cl))
	    return MR_SUCCESS;
    }
  /* Otherwise fail. */
  return MR_PERM;
}

int check_mail_string(char *mailstring)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char mname[MACHINE_NAME_SIZE];
  EXEC SQL END DECLARE SECTION;
  char *p, *host, *hostdomain;
  struct hostent *hp;
  struct mxentry *mxrecords = NULL;
  int index;

  p = strchr(mailstring, '@');
  if (p)
    {
      host = strdup(++p);
      
      /* Replace .LOCAL at end of host with .MIT.EDU if needed. */
      hostdomain = strrchr(host, '.');
      if (hostdomain && !strcasecmp(hostdomain, ".LOCAL"))
	{
	  index = hostdomain - host;
	  host[index] = '\0';
	  host = realloc(host, strlen(host) + strlen(".MIT.EDU") + 1);
	  strcat(host, ".MIT.EDU");
	}
      
      hp = gethostbyname(host);
      if (hp)
	{
	  host = realloc(host, strlen(hp->h_name) + 1);
	  if (host)
	    strcpy(host, hp->h_name);
	}
      else
	{
	  /* Possibly a host with no A record but MX records.  Check. */
	  mxrecords = getmxrecords(host);
	  if (!mxrecords)
	    return MR_BAD_MAIL_STRING;
	  else
	    return MR_SUCCESS;
	}
      
      if (!strcasecmp(host, "MIT.EDU"))
	{
	  free(host);
	  return MR_BAD_MAIL_STRING;
	}
      
      EXEC SQL DECLARE csr_listmem CURSOR FOR
	SELECT UNIQUE m.name FROM machine m, serverhosts sh
	WHERE m.mach_id = sh.mach_id
	AND (sh.service = 'MAILHUB' or sh.service = 'POSTOFFICE');
      if (dbms_errno)
	{
	  free(host);
	  return mr_errcode;
	}
      EXEC SQL OPEN csr_listmem;
      if (dbms_errno)
	{
	  free(host);
	  return mr_errcode;
	}
      while (1)
	{
	  EXEC SQL FETCH csr_listmem INTO :mname;
	  if (sqlca.sqlcode)
	    break;
	  
	  if (!strcasecmp(host, strtrim(mname)))
	    {
	      free(host);
	      return MR_BAD_MAIL_STRING;
	    }
	}
      free(host);
    }

  return MR_SUCCESS;
}

/*
 * This ought to be in the bind library.  It's adapted from sendmail.
 */

/*
 * These are defined in RFC833. Some bind interface headers don't declare them.
 * Ghod help us if they're ever actually incompatible with what's in 
 * the arpa/nameser.h header.
 */
#ifndef PACKETSZ
#define PACKETSZ	512		/* maximum packet size */
#endif
#ifndef HFIXEDSZ
#define	HFIXEDSZ	12		/* #/bytes of fixed data in header */
#endif
#ifndef INT32SZ
#define	INT32SZ		4		/* for systems without 32-bit ints */
#endif
#ifndef INT16SZ
#define	INT16SZ		2		/* for systems without 16-bit ints */
#endif

/* minimum possible size of MX record in packet */
#define MIN_MX_SIZE	8	/* corresp to "a.com 0" w/ terminating space */

struct mxentry *getmxrecords(const char *name)
{
  char answer[PACKETSZ], *eom, *cp, *bp;
  int n, ancount, qdcount, buflen, type, pref, ind;
  static struct mxentry pmx[(PACKETSZ - HFIXEDSZ) / MIN_MX_SIZE];
  static char MXHostBuf[PACKETSZ - HFIXEDSZ]; 
  HEADER *hp;
  
  pmx->name = (char *)NULL;
  pmx->pref = -1;
  n = res_search(name, C_IN,T_MX, (unsigned char *)&answer, sizeof(answer));
  if (n == -1)
    return((struct mxentry *)NULL);
  if ((size_t)n > sizeof(answer))
    n = sizeof(answer);    	
  
  hp = (HEADER *)&answer;
  cp = answer + HFIXEDSZ;
  eom = answer + n;
  h_errno = 0;
  for (qdcount = ntohs(hp->qdcount); qdcount--; cp += n + QFIXEDSZ)
    if ((n = dn_skipname((unsigned char *)cp, (unsigned char *)eom)) < 0)
      return((struct mxentry *)NULL);
  buflen = sizeof(MXHostBuf) - 1;
  bp = MXHostBuf;
  ind = 0;
  ancount = ntohs(hp->ancount);
  while (--ancount >= 0 && cp < eom)
    {
      if ((n = dn_expand((unsigned char *)answer, (unsigned char *)eom,
			 (unsigned char *)cp, bp, buflen)) < 0)
	break;
      cp += n;
      GETSHORT(type, cp);
      cp += INT16SZ + INT32SZ;
      GETSHORT(n, cp);
      if (type != T_MX)
	{
	  cp += n;
	  continue;
	}
      GETSHORT(pref, cp);
      if ((n = dn_expand((unsigned char *)answer, (unsigned char *)eom,
			 (unsigned char *)cp, bp, buflen)) < 0)
	break;
      cp += n;
      
      pmx[ind].name = bp;
      pmx[ind].pref = pref;
      ++ind;
      
      n = strlen((const char *)bp);
      bp += n;
      *bp++ = '\0';
      
      buflen -= n + 1;
    }
  
  pmx[ind].name = (char *)NULL;
  pmx[ind].pref = -1;
  return(pmx);
}
