/* $Id: qsetup.pc 4161 2014-04-24 13:44:03Z zacheiss $
 *
 * Query setup routines
 *
 * 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 "query.h"
#include "qrtn.h"

#include <arpa/inet.h>
#include <netinet/in.h>

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

EXEC SQL INCLUDE sqlca;

RCSID("$HeadURL: svn+ssh://svn.mit.edu/moira/trunk/moira/server/qsetup.pc $ $Id: qsetup.pc 4161 2014-04-24 13:44:03Z zacheiss $");

extern char *whoami;
extern int dbms_errno, mr_errcode;

EXEC SQL BEGIN DECLARE SECTION;
extern char stmt_buf[];
EXEC SQL END DECLARE SECTION;

EXEC SQL WHENEVER SQLERROR DO dbmserr();

int hostname_check(char *name);
int hostinfo_check(char *name, int num);
int prefetch_value(struct query *q, char **argv, client *cl);
int check_nfs(int mach_idx, char *name, char *access);
int check_mailman_listname(char *name, const char *suffix);

/* Setup Routines */

/* Setup routine for add_user
 *
 * Inputs: argv[0] - login
 *         argv[1] - uid
 *
 * Description:
 *
 * - if argv[1] == UNIQUE_UID then set argv[1] = next(uid)
 * - if argv[0] == UNIQUE_LOGIN then set argv[0] = "#<uid>"
 */

int setup_ausr(struct query *q, char *argv[], client *cl)
{
  int row, err;
  EXEC SQL BEGIN DECLARE SECTION;
  int nuid;
  EXEC SQL END DECLARE SECTION;

  if (!strcmp(q->shortname, "uusr") || !strcmp(q->shortname, "uuac"))
    row = 2;
  else
    row = 1;

  if (q->version > 2)
    {
      if (strlen(argv[row + 3]) + strlen(argv[row + 4]) +
	  strlen(argv[row + 5]) + 2 > USERS_FULLNAME_SIZE)
	return MR_ARG_TOO_LONG;
    }
  else
    {
      if (strlen(argv[row + 2]) + strlen(argv[row + 3]) +
	  strlen(argv[row + 4]) + 2 > USERS_FULLNAME_SIZE)
	return MR_ARG_TOO_LONG;
    }

  if (q->version > 10)
    {
      /* For both winhomedir and winprofiledir, we allow values of the
       * following forms:
       *
       * [AFS] - Magic token for AFS home directory.
       * [LOCAL] - Magic token for AD default local values, i.e. C:\<mumble>
       * [DFS] - Magic token for DFS home directory
       * UNC pathname - \\<something>
       * local pathname - <drive letter>:<something>
       */

      if ((strcasecmp(argv[row + 12], "[AFS]")) &&
	  (strcasecmp(argv[row + 12], "[LOCAL]")) &&
	  (strcasecmp(argv[row + 12], "[DFS]")) &&
	  (!(argv[row + 12][0] == '\\' && argv[row + 12][1] == '\\')) &&
	  (!(isalpha(*argv[row + 12]) && (argv[row + 12][1] == ':'))))
	return MR_BAD_CHAR;
      
      if ((strcasecmp(argv[row + 13], "[AFS]")) &&
	  (strcasecmp(argv[row + 13], "[LOCAL]")) &&
	  (strcasecmp(argv[row + 13], "[DFS]")) &&
	  (!(argv[row + 13][0] == '\\' && argv[row + 13][1] == '\\')) &&
	  (!(isalpha(*argv[row + 13]) && (argv[row + 13][1] == ':'))))
	return MR_BAD_CHAR;
    }

  if (!strcmp(argv[row], UNIQUE_UID) || atoi(argv[row]) == -1)
    {
      if ((err = set_next_object_id("unix_uid", USERS_TABLE, 1)))
	return err;
      EXEC SQL SELECT value INTO :nuid FROM numvalues WHERE name = 'unix_uid';
      if (sqlca.sqlerrd[2] != 1)
	return MR_INTERNAL;
      sprintf(argv[row], "%d", nuid);
    }

  if (!strcmp(argv[0], UNIQUE_LOGIN) || atoi(argv[row]) == -1)
    sprintf(argv[0], "#%s", argv[row]);

  if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
    return mr_errcode;

  /* If this is an MR_Q_UPDATE query, we're done. */
  if (row == 2)
    return MR_SUCCESS;

  /* For an add query, we need to fill in the creator id. */
  sprintf(argv[q->argc + q->vcnt + 1], "%d", cl->client_id);
  return MR_SUCCESS;
}


/* setup_dusr - verify that the user is no longer being referenced
 * and may safely be deleted.
 */

int setup_dusr(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int flag, id, cnt;
  char resv[USERS_RESERVATIONS_SIZE];
  EXEC SQL END DECLARE SECTION;

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

  /* For now, only allow users to be deleted if their status is
   * one of 0, 4, or 8 (the various registerable statuses)
   * and we have no reservations about deleting them.
   */
  EXEC SQL SELECT status, reservations INTO :flag, :resv
    FROM users WHERE users_id = :id;
  if ((flag != 0 && flag != 4 && flag != 8) || *resv)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
    WHERE member_id = :id AND member_type = 'USER';
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(label) INTO :cnt FROM filesys
    WHERE owner = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
    WHERE acl_id = :id AND acl_type = 'USER';
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(name) INTO :cnt FROM servers
    WHERE acl_id = :id AND acl_type = 'USER';
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(acl_id) INTO :cnt FROM hostaccess
    WHERE acl_id = :id AND acl_type = 'USER';
  if (cnt > 0)
    return MR_IN_USE;
  if (dbms_errno)
    return mr_errcode;

  EXEC SQL DELETE FROM quota WHERE entity_id = :id AND type = 'USER';
  EXEC SQL DELETE FROM krbmap WHERE users_id = :id;
  return MR_SUCCESS;
}


/* setup_dpob:  Take care of keeping track of the post office usage.
 */
int setup_dpob(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int id, user;
  char type[USERS_POTYPE_SIZE];
  EXEC SQL END DECLARE SECTION;

  user = *(int *)argv[0];
  EXEC SQL SELECT potype, pop_id INTO :type, :id FROM users
    WHERE users_id = :user;
  if (dbms_errno)
    return mr_errcode;

  if (!strcmp(strtrim(type), "POP"))
    set_pop_usage(id, -1);
  return MR_SUCCESS;
}


/* setup_dmac - verify that the machine is no longer being referenced
 * and may safely be deleted.
 */

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

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

  EXEC SQL SELECT status INTO :flag FROM machine
    WHERE mach_id = :id;
  if (flag != 3)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(login) INTO :cnt FROM users
    WHERE potype = 'POP' AND pop_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(login) INTO :cnt FROM users
    WHERE potype = 'EXCHANGE' and exchange_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM serverhosts
    WHERE mach_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM nfsphys
    WHERE mach_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM hostaccess
    WHERE mach_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM printers
    WHERE mach_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(rm) INTO :cnt FROM printers
    WHERE rm = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(rq) INTO :cnt FROM printers
    WHERE rq = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM printservers
    WHERE mach_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM hostalias
    WHERE mach_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
    WHERE member_type = 'MACHINE' and member_id = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL DELETE FROM mcmap WHERE mach_id = :id;
  if (dbms_errno)
    return mr_errcode;

  EXEC SQL DELETE FROM mcntmap WHERE mach_id = :id;
  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}

/* setup_asnt - verify that the data entered for the subnet is sane.
 * In particular, make sure that the "low" and "high" addresses are
 * correctly ordered, i.e., high > low.
 */

int setup_asnt(struct query *q, char *argv[], client *cl)
{
  int high, low, row, status;
  char *account_number;

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

  low = atoi(argv[row + 7]);
  high = atoi(argv[row + 8]);
  status = atoi(argv[row + 2]);
  account_number = argv[row + 4];
  
  /* Don't allow Private subnets to be created without a valid billing
   * number.
   */
  if (status == SNET_STATUS_PRIVATE_10MBPS || 
      status == SNET_STATUS_PRIVATE_100MBPS ||
      status == SNET_STATUS_PRIVATE_1000MBPS)
    {
      EXEC SQL SELECT account_number FROM accountnumbers
	WHERE account_number = :account_number;
      if (sqlca.sqlcode == SQL_NO_MATCH)
	return MR_ACCOUNT_NUMBER;
    }
      
  /* Special case 0.0.0.0 and 255.255.255.255 */
  if (!(low == 0 || low == -1 || high == 0 || high == -1))
    if (low > high)
      return MR_ADDRESS;

  /* If this is update_subnet, we're done. */
  if (row == 1)
    return MR_SUCCESS;

  /* For an add_subnet query, allocate and fill in a new snet_id */
  if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
    return mr_errcode;

  return MR_SUCCESS;
}

/* setup_dsnt - verify that the subnet is no longer being referenced
 * and may safely be deleted.
 */

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

  id = *(int *)argv[0];
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM machine
    WHERE snet_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  return MR_SUCCESS;
}


/* setup_dclu - verify that the cluster is no longer being referenced
 * and may safely be deleted.
 */

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

  id = *(int *)argv[0];
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM mcmap
    WHERE clu_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  EXEC SQL SELECT COUNT(clu_id) INTO :cnt FROM svc
    WHERE clu_id = :id;
  if (cnt > 0)
    return MR_IN_USE;
  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}


/* setup_alis - if argv[5] is non-zero and argv[6] is UNIQUE_ID, then allocate
 * a new gid and put it in argv[6].  Otherwise if argv[6] is UNIQUE_ID but
 * argv[5] is not, then remember that UNIQUE_ID is being stored by putting
 * a -1 there.  Remember that this is also used for ulis, with the indexes
 * at 6 & 7.  Also check that the list name does not contain uppercase
 * characters, control characters, @, or :.
 *
 *  Newlines in list descriptions do bad things to the aliases file
 *  moira generates, so make sure the description doesn't contain any, too.
 */

static int badlistchars[] = {
  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, 1, 0, 1, 0, 0, 1, 1, 1, 1, 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,
};

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

int setup_alis(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int ngid, cnt, mailman, mailman_id, lid;
  char *name, *desc;
  EXEC SQL END DECLARE SECTION;
  unsigned char *p;
  int idx, err, best = -1, usage, i;

  if (!strcmp(q->shortname, "alis"))
    idx = 0;
  else if (!strcmp(q->shortname, "ulis"))
    idx = 1;
  name = argv[idx];

  if (q->version == 2)
    desc = argv[9 + idx];
  else if (q->version == 3)
    desc = argv[10 + idx];
  else if (q->version == 4)
    desc = argv[12 + idx];
  else if (q->version >= 10)
    desc = argv[14 + idx];

  if (idx == 1)
    {
      lid = *(int *)argv[0];

      if (acl_access_check(lid, cl))
	return MR_PERM;
    }

  for (p = (unsigned char *) name; *p; p++)
    {
      if (badlistchars[*p])
	return MR_BAD_CHAR;
    }

  for (p = (unsigned char *) desc; *p; p++)
    {
      if (*p == '\n')
	return MR_BAD_CHAR;
    }

  /* Check that it doesn't conflict with a pre-existing weirdly-cased
   * name. */
  EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
    WHERE LOWER(name) = :name AND name != :name;
  if (cnt)
    return MR_EXISTS;

  if (!strcmp(argv[6 + idx], UNIQUE_GID) || atoi(argv[6 + idx]) == -1)
    {
      if (atoi(argv[5 + idx]))
	{
	  if ((err = set_next_object_id("gid", LIST_TABLE, 1)))
	    return err;
	  EXEC SQL SELECT value INTO :ngid FROM numvalues
	    WHERE name = 'gid';
	  if (dbms_errno)
	    return mr_errcode;
	  sprintf(argv[6 + idx], "%d", ngid);
	}
      else
	strcpy(argv[6 + idx], "-1");
    }

  /* Don't let someone rename a list to one of the magic mailman names
   * (foo-admin, etc) if foo already exists as a mailman list.
   */
  for (i = 0; mailman_suffixes[i]; i++)
    {
      if ((err = check_mailman_listname(name, mailman_suffixes[i])) 
	  != MR_SUCCESS)
	return err;
    }

  if (q->version >= 10)
    {
      /* Don't let them take this name for a mailman list if we can't
       * reserve the -admin, -owner, and -request names.
       */
      if (atoi(argv[8 + idx]))
	{
	  EXEC SQL SELECT  COUNT(name) INTO :cnt FROM list
	    WHERE (name = :name || '-admin' OR name = :name || '-owner' OR
		   name = :name || '-request');
	  if (cnt)
	    return MR_EXISTS;
	}

      /* Handle the [ANY] case for mailman server. */
      mailman_id = *(int *)argv[9 + idx];
      if (mailman_id == -1)
	{
	  EXEC SQL DECLARE csr_mailman CURSOR FOR
	    SELECT mach_id FROM serverhosts WHERE service = 'MAILMAN'
	    AND enable = 1;
	  if (dbms_errno)
	    return mr_errcode;
	  EXEC SQL OPEN csr_mailman;
	  if (dbms_errno)
	    return mr_errcode;

	  while (1)
	    {
	      EXEC SQL FETCH csr_mailman INTO :mailman_id;
	      if (sqlca.sqlcode)
		break;
	      
	      EXEC SQL SELECT COUNT(name) INTO :usage FROM list
		WHERE mailman_id = :mailman_id;

	      if (best < 0 || usage < best)
		{
		  best = usage;
		  *(int *)argv[9 + idx] = mailman_id;
		  break;
		}
	    }
	  EXEC SQL CLOSE csr_mailman;
	  if (dbms_errno)
	    return mr_errcode;

	  if (best == -1)
	    return MR_SERVICE;
	}
    }
  else
    {
      /* Client too old to know about the mailman code.
       * Use existing value of mailman boolean in the table.
       * Don't do this for add_list from an old client, since the row
       * they're creating won't exist yet, and there's no way for them to
       * create a list with the mailman bit set, anyway.
       */
      if (idx == 1)
	{
	  EXEC SQL SELECT mailman INTO :mailman FROM list WHERE list_id = :lid;
	  if (mailman)
	    {
	      EXEC SQL SELECT  COUNT(name) INTO :cnt FROM list
		WHERE (name = :name || '-admin' OR name = :name || '-owner' OR
		       name = :name || '-request');
	      if (cnt)
		return MR_EXISTS;
	    }
	}
    }

  if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
    return mr_errcode;

  return MR_SUCCESS;
}


/* setup_dlis - verify that the list is no longer being referenced
 * and may safely be deleted.
 */

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

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

  EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
    WHERE member_id = :id AND member_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
    WHERE member_id = :id AND member_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(member_id) INTO :cnt FROM imembers
    WHERE list_id = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(label) INTO :cnt FROM filesys WHERE owners = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(tag) INTO :cnt FROM capacls WHERE list_id = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
    WHERE acl_id = :id AND acl_type = 'LIST' AND list_id != :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
    WHERE memacl_id = :id AND memacl_type = 'LIST' AND list_id != :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(name) INTO :cnt FROM servers
    WHERE acl_id = :id AND acl_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(login) INTO :cnt FROM users
    WHERE sponsor_id = :id AND sponsor_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(entity_id) INTO :cnt FROM quota
    WHERE entity_id = :id AND type = 'GROUP';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(acl_id) INTO :cnt FROM hostaccess
    WHERE acl_id = :id AND acl_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(class) INTO :cnt FROM zephyr z
    WHERE z.xmt_type = 'LIST' AND z.xmt_id = :id
    OR z.sub_type = 'LIST' AND z.sub_id = :id
    OR z.iws_type = 'LIST' AND z.iws_id = :id
    OR z.iui_type = 'LIST' AND z.iui_id = :id
    OR z.owner_type = 'LIST' and z.owner_id = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(name) INTO :cnt FROM printers
    WHERE lpc_acl = :id OR ac = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM printservers
    WHERE owner_type = 'LIST' AND owner_id = :id
    OR lpc_acl = :id;
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT count(name) INTO :cnt FROM containers
    WHERE acl_id = :id AND acl_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT count(name) INTO :cnt FROM containers
    WHERE memacl_id = :id AND memacl_type = 'LIST';
  if (cnt > 0)
    return MR_IN_USE;

  return MR_SUCCESS;
}


/* setup_dsin - verify that the service is no longer being referenced
 * and may safely be deleted.
 */

int setup_dsin(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int ec, cnt;
  char *svrname;
  EXEC SQL END DECLARE SECTION;

  svrname = argv[0];
  EXEC SQL SELECT COUNT(service) INTO :cnt FROM serverhosts
    WHERE service = UPPER(:svrname);
  if (cnt > 0)
    return MR_IN_USE;

  EXEC SQL SELECT inprogress INTO :ec FROM servers
    WHERE name = UPPER(:svrname);
  if (dbms_errno)
    return mr_errcode;
  if (ec)
    return MR_IN_USE;

  return MR_SUCCESS;
}


/* setup_dshi - verify that the service-host is no longer being referenced
 * and may safely be deleted.
 */

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

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

  EXEC SQL SELECT inprogress INTO :ec FROM serverhosts
    WHERE service = UPPER(:svrname) AND mach_id = :id;
  if (dbms_errno)
    return mr_errcode;
  if (ec)
    return MR_IN_USE;

  return MR_SUCCESS;
}


/**
 ** setup_add_filesys - verify existance of referenced file systems
 **
 ** Inputs:     Add
 **   argv[1] - type
 **   argv[2] - mach_id
 **   argv[3] - name
 **   argv[5] - rwaccess
 **
 ** Description:
 **   - for type = RVD:
 **	   * allow anything
 **   - for type = NFS/IMAP:
 **        * extract directory prefix from name
 **        * verify mach_id/dir in nfsphys
 **        * verify rwaccess in {r, w, R, W}
 **
 **  Side effect: sets variable _var_phys_id to the ID of the physical
 **	filesystem (nfsphys_id for NFS, 0 for RVD)
 **
 ** Errors:
 **   MR_NFS - specified directory not exported
 **   MR_FILESYS_ACCESS - invalid filesys access
 **
 **/

EXEC SQL BEGIN DECLARE SECTION;
int _var_phys_id;
EXEC SQL END DECLARE SECTION;

int setup_afil(struct query *q, char *argv[], client *cl)
{
  char *type, *name;
  int mach_id;
  EXEC SQL BEGIN DECLARE SECTION;
  int ok;
  char ftype[FILESYS_TYPE_SIZE + 10], *rwaccess;
  EXEC SQL END DECLARE SECTION;

  type = argv[1];
  mach_id = *(int *)argv[2];
  name = argv[3];
  rwaccess = argv[5];
  _var_phys_id = 0;

  sprintf(ftype, "fs_access_%s", type);
  EXEC SQL SELECT COUNT(trans) INTO :ok FROM alias
    WHERE name = :ftype AND type = 'TYPE' and trans = :rwaccess;
  if (dbms_errno)
    return mr_errcode;
  if (ok == 0)
    return MR_FILESYS_ACCESS;

  if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
    return mr_errcode;

  if (!strcmp(type, "NFS") || !strcmp(type, "IMAP"))
    return check_nfs(mach_id, name, rwaccess);

  return MR_SUCCESS;
}


/* Verify the arguments, depending on the FStype.  Also, if this is an
 * NFS filesystem, then update any quotas for that filesystem to reflect
 * the new phys_id.
 */

int setup_ufil(struct query *q, char *argv[], client *cl)
{
  int mach_id, status;
  char *type, *name;
  EXEC SQL BEGIN DECLARE SECTION;
  int fid, total, who, ok;
  char *entity, ftype[FILESYS_TYPE_SIZE + 10], *access;
  short int total_null;
  EXEC SQL END DECLARE SECTION;

  _var_phys_id = 0;
  type = argv[2];
  mach_id = *(int *)argv[3];
  name = argv[4];
  access = argv[6];
  fid = *(int *)argv[0];
  who = cl->client_id;
  entity = cl->entity;

  sprintf(ftype, "fs_access_%s", type);
  EXEC SQL SELECT COUNT(trans) INTO :ok FROM alias
    WHERE name = :ftype AND type = 'TYPE' AND trans = :access;
  if (dbms_errno)
    return mr_errcode;
  if (ok == 0)
    return MR_FILESYS_ACCESS;

  EXEC SQL SELECT type INTO :ftype FROM filesys
    WHERE filsys_id = :fid;
  if (dbms_errno)
    return mr_errcode;

  if (!strcmp(type, "NFS") || !strcmp(type, "IMAP"))
    {
      status = check_nfs(mach_id, name, access);
      EXEC SQL UPDATE quota SET phys_id = :_var_phys_id
	WHERE filsys_id = :fid;
      if (dbms_errno)
	return mr_errcode;
      return status;
    }
  else if (!strcmp(type, "AFS") && strcmp(strtrim(ftype), "AFS")
	   && strcmp(strtrim(ftype), "ERR"))
    {
      total = 0;
      EXEC SQL DELETE FROM quota
	WHERE type = 'ANY' AND filsys_id = :fid;
      EXEC SQL SELECT SUM (quota) INTO :total:total_null FROM quota
	WHERE filsys_id = :fid AND phys_id != 0;
      if (dbms_errno)
	return mr_errcode;
      if (!total_null && (total != 0))
	{
	  EXEC SQL INSERT INTO quota (quota, filsys_id, phys_id, entity_id,
				      type, modtime, modby, modwith)
	    VALUES (:total, :fid, 0, 0, 'ANY', SYSDATE, :who, :entity);
	  if (dbms_errno)
	    return mr_errcode;
	}
    }
  else
    {
      EXEC SQL UPDATE quota SET phys_id = 0 WHERE filsys_id = :fid;
      if (dbms_errno)
	return mr_errcode;
    }
  return MR_SUCCESS;
}


/* Find the NFS physical partition that the named directory is on.
 * This is done by comparing the dir against the mount point of the
 * partition.  To make sure we get the correct match when there is
 * more than one, we sort the query in reverse order by dir name.
 */

int check_nfs(int mach_id, char *name, char *access)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char dir[NFSPHYS_DIR_SIZE];
  int mid = mach_id;
  EXEC SQL END DECLARE SECTION;
  int status;
  char *cp1;
  char *cp2;

  status = MR_NFS;
  EXEC SQL DECLARE csr101 CURSOR FOR
    SELECT nfsphys_id, dir FROM nfsphys
    WHERE mach_id = :mid
    ORDER BY 2 DESC;
  if (dbms_errno)
    return mr_errcode;
  EXEC SQL OPEN csr101;
  if (dbms_errno)
    return mr_errcode;
  while (1)
    {
      EXEC SQL FETCH csr101 INTO :_var_phys_id, :dir;
      if (sqlca.sqlcode)
	break;
      cp1 = name;
      cp2 = strtrim(dir);
      while (*cp2)
	{
	  if (*cp1++ != *cp2)
	    break;
	  cp2++;
	}
      if (!*cp2)
	{
	  status = MR_SUCCESS;
	  break;
	}
    }
  EXEC SQL CLOSE csr101;
  if (dbms_errno)
    return mr_errcode;
  return status;
}


/* setup_dfil: free any quota records and fsgroup info associated with
 * a filesystem when it is deleted.  Also adjust the allocation numbers.
 */

int setup_dfil(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int id, total, phys_id;
  short int none;
  EXEC SQL END DECLARE SECTION;

  id = *(int *)argv[0];
  EXEC SQL SELECT SUM (quota) INTO :total:none FROM quota
    WHERE filsys_id = :id;

  if (none)
    total = 0;

  /** What if there are multiple phys_id's per f/s? (bad data) **/
  EXEC SQL SELECT phys_id INTO :phys_id FROM filesys
    WHERE filsys_id = :id;
  EXEC SQL UPDATE nfsphys SET allocated = allocated - :total
    WHERE nfsphys_id = :phys_id;

  if (!none)
    EXEC SQL DELETE FROM quota WHERE filsys_id = :id;
  EXEC SQL DELETE FROM fsgroup WHERE filsys_id = :id;
  EXEC SQL DELETE FROM fsgroup WHERE group_id = :id;
  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}


/* setup_dnfp: check to see that the nfs physical partition does not have
 * any filesystems assigned to it before allowing it to be deleted.
 */

int setup_dnfp(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int id, cnt;
  char *dir;
  EXEC SQL END DECLARE SECTION;

  id = *(int *)argv[0];
  dir = argv[1];
  EXEC SQL SELECT count(fs.rowid) INTO :cnt FROM filesys fs, nfsphys np
    WHERE fs.mach_id = :id AND fs.phys_id = np.nfsphys_id
    AND np.mach_id = :id AND np.dir = :dir;
  if (cnt > 0)
    return MR_IN_USE;
  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}


/* setup_dqot: Remove allocation from nfsphys before deleting quota.
 *   argv[0] = filsys_id
 *   argv[1] = type if "update_quota" or "delete_quota"
 *   argv[2 or 1] = users_id or list_id
 */

int setup_dqot(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int quota, fs, id, physid;
  char *qtype;
  EXEC SQL END DECLARE SECTION;

  fs = *(int *)argv[0];
  if (!strcmp(q->name, "update_quota") || !strcmp(q->name, "delete_quota"))
    {
      qtype = argv[1];
      id = *(int *)argv[2];
    }
  else
    {
      qtype = "USER";
      id = *(int *)argv[1];
    }

  EXEC SQL SELECT quota INTO :quota FROM quota
    WHERE type = :qtype AND entity_id = :id AND filsys_id = :fs;
  EXEC SQL SELECT phys_id INTO :physid FROM filesys
    WHERE filsys_id = :fs;
  EXEC SQL UPDATE nfsphys SET allocated = allocated - :quota
    WHERE nfsphys_id = :physid;

  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}


/* prefetch_value():
 * This routine fetches an appropriate value from the numvalues table.
 * It is a little hack to get around the fact that SQL doesn't let you
 * do something like INSERT INTO table (foo) VALUES (other_table.bar).
 *
 * It is called from the query table as (*v->pre_rtn)(q, Argv, cl) or
 * from within a setup_...() routine with the appropriate arguments.
 *
 * Correct functioning of this routine may depend on the assumption
 * that this query is an MR_Q_APPEND.
 */

int prefetch_value(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *name = q->validate->object_id;
  int value;
  EXEC SQL END DECLARE SECTION;
  int status, limit, argc;

  /* set next object id, limiting it if necessary */
  if (!strcmp(name, "unix_uid") || !strcmp(name, "gid"))
    limit = 1; /* So far as I know, this isn't needed.  Just CMA. */
  else
    limit = 0;
  if ((status = set_next_object_id(name, q->rtable, limit)) != MR_SUCCESS)
    return status;

  /* fetch object id */
  EXEC SQL SELECT value INTO :value FROM numvalues WHERE name = :name;
  if (dbms_errno)
    return mr_errcode;
  if (sqlca.sqlerrd[2] != 1)
    return MR_INTERNAL;

  argc = q->argc + q->vcnt;   /* end of Argv for MR_Q_APPENDs */
  sprintf(argv[argc], "%d", value);

  return MR_SUCCESS;
}

/* prefetch_filesys():
 * Fetches the phys_id from filesys based on the filsys_id in argv[0].
 * Appends the filsys_id and the phys_id to the argv so they can be
 * referenced in an INSERT into a table other than filesys.  Also
 * see comments at prefetch_value().
 *
 * Assumes the existence of a row where filsys_id = argv[0], since a
 * filesys label has already been resolved to a filsys_id.
 */
int prefetch_filesys(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int fid, phid;
  EXEC SQL END DECLARE SECTION;
  int argc;

  fid = *(int *)argv[0];
  EXEC SQL SELECT phys_id INTO :phid FROM filesys WHERE filsys_id = :fid;
  if (dbms_errno)
    return mr_errcode;

  argc = q->argc + q->vcnt;
  sprintf(argv[argc++], "%d", phid);
  sprintf(argv[argc], "%d", fid);

  return MR_SUCCESS;
}


/* setup_ghst():
 */

int setup_ghst(struct query *q, char **argv, client *cl)
{
  if (strcmp(argv[0], "*") || strcmp(argv[1], "*") ||
      strcmp(argv[2], "*") || strcmp(argv[3], "*"))
    return MR_SUCCESS;
  else
    return MR_PERM;
}

/* setup_ahst():
 */

int setup_ahst(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *name, oldname[MACHINE_NAME_SIZE], vendor[MACHINE_VENDOR_SIZE];
  char model[MACHINE_MODEL_SIZE], os[MACHINE_OS_SIZE];
  int value, id, ssaddr, smask, shigh, slow, cnt;
  unsigned int saddr, mask, high, low;
  EXEC SQL END DECLARE SECTION;
  int row, idx;
  struct in_addr addr;

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

  if (!strcmp(q->shortname, "uhst"))
    {
      row = 1;
      EXEC SQL SELECT name, vendor, model, os
	INTO :oldname, :vendor, :model, :os
	FROM machine WHERE mach_id = :id;
    }
  else
    row = 0;

  if (q->version < 6)
    idx = 0;
  else if (q->version >= 6 && q->version < 8)
    idx = 1;
  else
    idx = 2;

  /* Sanity check name, vendor, model, and os. */
  if ((row == 0 || strcasecmp(argv[1], oldname)) &&
      !hostname_check(argv[row]))
    return MR_BAD_CHAR;
  if ((row == 0 || strcasecmp(argv[2], vendor)) &&
      !hostinfo_check(argv[row + 1], 0))
    return MR_BAD_CHAR;
  if ((row == 0 || strcasecmp(argv[3], model)) &&
      !hostinfo_check(argv[row + 2], 1))
    return MR_BAD_CHAR;
  if ((row == 0 || strcasecmp(argv[4], os)) &&
      !hostinfo_check(argv[row + 3], 0))
    return MR_BAD_CHAR;

  /* check for duplicate name */
  name = argv[row];
  EXEC SQL SELECT count(mach_id) INTO :cnt FROM hostalias
    WHERE name = UPPER(:name);
  if (dbms_errno)
    return mr_errcode;
  if (cnt != 0)
    return MR_EXISTS;

  /* check address */
  if (!strcmp(argv[9 + row + idx], "unassigned"))
    value = -1;
  else if (!strcmp(argv[9 + row + idx], "unique"))
    {
      if (*(int *)argv[8 + row + idx] == 0)
	value = -1;
      else
	value = -2;
    }
  else
    {
      value = ntohl(inet_addr(argv[9 + row + idx]));
      if (value == -1)
	return MR_ADDRESS;
    }
  if (value == 0)
    return MR_ADDRESS;
  if (value != -1)
    {
      /*
       * an address or unique was specified.
       */
      id = *(int *)argv[8 + row + idx];
      EXEC SQL SELECT saddr, mask, high, low INTO :ssaddr, :smask,
	:shigh, :slow FROM subnet WHERE snet_id = :id;
      if (dbms_errno)
	return mr_errcode;
      saddr = (unsigned) ssaddr;
      mask = (unsigned) smask;
      high = (unsigned) shigh;
      low = (unsigned) slow;
      if (value != -2)
	{
	  /*
	   * someone specified an IP address for the host record
	   */
	  if ((value & mask) != saddr || value < low || value > high)
	    return MR_ADDRESS;
	  /*
	   * run the address argument through inet_addr(). This
	   * has the effect that any out of bounds host addrs will
	   * be converted to a valid host addr. We do this now
	   * so that the uniqueness check works. We should also
	   * link in an inet_addr() that returns an error for
	   * this case.
	   */
	  addr.s_addr = inet_addr(argv[9 + row + idx]);
	  name = inet_ntoa(addr);
	  EXEC SQL SELECT count(mach_id) INTO :cnt FROM machine
	    WHERE address = :name;
	  if (dbms_errno)
	    return mr_errcode;
	  if (cnt > 0)
	    {
	      /*
	       * make IP address is unique. If this a modify request
	       * (row == 1), then we expect one record to exist.
	       */
	      if (row == 0 || (row == 1 && cnt > 1))
		return MR_ADDRESS;
	      if (row == 1 && cnt == 1)
		{
		  EXEC SQL SELECT mach_id INTO :id FROM machine
		    WHERE address = :name;
		  if (id != *(int *)argv[0])
		    return MR_ADDRESS;
		}
	    }
	}
      else
	{
	  /*
	   * a "unique" address was specified. Walk through the
	   * range specified in the network record, return
	   * error if no room left.
	   */
	  for (id = low; id <= high; id++)
	    {
	      if (((id & 0xff) == 0) || ((id & 0xff) == 255))
		continue;
	      addr.s_addr = htonl(id);
	      name = inet_ntoa(addr);
	      EXEC SQL SELECT count(mach_id) INTO :cnt FROM machine
		WHERE address = :name;
	      if (dbms_errno)
		return mr_errcode;
	      if (cnt == 0)
		break;
	    }
	  if (cnt != 0)
	    return MR_NO_ID;
	  else
	    value = htonl(id);
	}
      strcpy(argv[9 + row + idx], name);
    }
  else
    strcpy(argv[9 + row + idx], "unassigned");

  /* status checking */
  value = atoi(argv[7 + row + idx]);
  if (row == 0 && !(value == 1 || value == 0))
    return MR_TYPE;
  if (row == 1)
    {
      id = *(int *)argv[0];
      EXEC SQL SELECT status INTO :cnt FROM machine WHERE mach_id = :id;
      if (dbms_errno)
	return mr_errcode;
      if (value != cnt)
	{
	  EXEC SQL UPDATE machine SET statuschange = SYSDATE
	    WHERE mach_id = :id;
	}
    }

  /*
   * If this is an update_host query, we're done.
   */
  if (row == 1)
    return MR_SUCCESS;

  /*
   * For an add_host query, allocate and fill in a new machine id,
   * and then insert the creator id.
   */
  if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
    return mr_errcode;

  sprintf(argv[q->argc + q->vcnt + 1], "%d", cl->client_id);
  return MR_SUCCESS;
}


/* setup_ahal():
 */

int setup_ahal(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *name;
  int cnt;
  EXEC SQL END DECLARE SECTION;
  char *p;

  name = argv[0];
  if (!hostname_check(argv[0]))
    return MR_BAD_CHAR;

  EXEC SQL SELECT count(mach_id) INTO :cnt FROM machine WHERE
    name = UPPER(:name);
  if (dbms_errno)
    return mr_errcode;
  if (cnt > 0)
    return MR_EXISTS;

  return MR_SUCCESS;
}

/* setup_ahha(): Check characters in hwaddr, and make sure it's not
 * a duplicate.
 */
int setup_ahha(struct query *q, char **argv, client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char *hwaddr = argv[1];
  int count;
  EXEC SQL END DECLARE SECTION;
  char *p;

  if (strlen(hwaddr) == 0)
    return MR_BAD_CHAR;

  if (*hwaddr && strcasecmp(hwaddr, "unknown"))
    {
      for (p = hwaddr; *p; p++)
	{
	  if (isupper(*p))
	    *p = tolower(*p);
	  if (!isxdigit(*p))
	    return MR_BAD_CHAR;
	}
      if (p != hwaddr + 12)
	return MR_ADDRESS;

      EXEC SQL SELECT COUNT(hwaddr) INTO :count
	FROM hwaddrmap WHERE hwaddr = :hwaddr;
      if (count)
	return MR_NOT_UNIQUE;
    }

  return MR_SUCCESS;
}

/* setup_aprn(): Make sure name/duplexname don't conflict with
 * anything. If [ANY] was specified for the spooling host, pick the
 * least loaded print server that serves this kind of printer.
 */
int setup_aprn(struct query *q, char **argv, client *cl)
{
  int best = -1, row, idx = 0;
  char *p;
  EXEC SQL BEGIN DECLARE SECTION;
  int mid, usage, count;
  char types[STRINGS_STRING_SIZE], *hwaddr, *name, *duplexname, *oldname;
  char *service;
  EXEC SQL END DECLARE SECTION;

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

  if (q->version >= 13)
    idx = 3;

  name = argv[PRN_NAME + row];
  duplexname = argv[PRN_DUPLEXNAME + row];
  oldname = argv[0];

  if (!*name)
    return MR_BAD_CHAR;
  else
    {
      if (q->type == MR_Q_APPEND)
	{
	  EXEC SQL SELECT COUNT(name) INTO :count FROM printers
	    WHERE name = :name OR duplexname = :name;
	}
      else
	{
	  EXEC SQL SELECT COUNT(name) INTO :count FROM printers
	    WHERE ( name = :name OR duplexname = :name )
	    AND name != :oldname;
	}
      if (dbms_errno)
	return mr_errcode;
      if (count)
	return MR_NOT_UNIQUE;
    }

  if (*duplexname)
    {
      if (q->type == MR_Q_APPEND)
	{
	  EXEC SQL SELECT COUNT(name) INTO :count FROM printers
	    WHERE name = :duplexname OR duplexname = :duplexname;
	}
      else
	{
	  EXEC SQL SELECT COUNT(name) INTO :count FROM printers
	    WHERE ( name = :duplexname OR duplexname = :duplexname )
	    AND name != :oldname;
	}

      if (dbms_errno)
	return mr_errcode;
      if (count)
	return MR_NOT_UNIQUE;
    }

  if (!strcmp(name, duplexname))
    return MR_NOT_UNIQUE;

  mid = *(int *)argv[6 + row + idx];
  if (mid == -1)
    {
      if (!strcasecmp(argv[PRN_TYPE + row], "DORM") ||
	  !strcasecmp(argv[PRN_TYPE + row], "CLUSTER"))
	service = "CUPS-CLUSTER";
      else
	service = "CUPS-PRINT";

      EXEC SQL DECLARE csr_rm CURSOR FOR
	SELECT ps.mach_id, s.string FROM printservers ps, strings s
	WHERE ps.mach_id IN
	( SELECT mach_id FROM serverhosts WHERE service = :service
	  AND enable = 1 )
	AND ps.printer_types = s.string_id;

      if (dbms_errno)
	return mr_errcode;
      EXEC SQL OPEN csr_rm;
      if (dbms_errno)
	return mr_errcode;

      while (1)
	{
	  EXEC SQL FETCH csr_rm INTO :mid, :types;
	  if (sqlca.sqlcode)
	    break;

	  for (p = strtok(types, ", "); p; p = strtok(NULL, ", "))
	    {
	      if (!strcasecmp(argv[PRN_TYPE + row], p))
		{
		  EXEC SQL SELECT COUNT(name) INTO :usage
		    FROM printers WHERE rm = :mid;

		  if (best < 0 || usage < best)
		    {
		      best = usage;
		      *(int *)argv[6 + row + idx] = mid;
		      break;
		    }
		}
	    }
	}
      EXEC SQL CLOSE csr_rm;
      if (dbms_errno)
	return mr_errcode;

      if (best == -1)
	return MR_SERVICE;
    }
  else
    {
      EXEC SQL SELECT mach_id INTO :mid FROM printservers
	WHERE mach_id = :mid;
      if (sqlca.sqlcode)
	return MR_SERVICE;
    }

  return MR_SUCCESS;
}

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

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

  EXEC SQL SELECT COUNT(rm) INTO :cnt FROM printers
    WHERE rm = :id;
  if (cnt > 0)
    return MR_IN_USE;

  return MR_SUCCESS;
}

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

  id = *(int *)argv[0];
  /* check to see if there are machines in this container */
  EXEC SQL SELECT COUNT(mach_id) INTO :cnt FROM mcntmap
    WHERE cnt_id = :id;
  if (cnt > 0)
    return MR_IN_USE;

  /* check to see if there are subcontainers in this container */

  /* get the container name */
  
  EXEC SQL SELECT name INTO :containername
    FROM containers
    WHERE cnt_id = :id; 

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

  EXEC SQL SELECT COUNT(cnt_id) INTO :cnt FROM containers
    WHERE LOWER(name) LIKE LOWER(:containername || '/' || '%');

  if (cnt > 0)
    return MR_IN_USE;

  if (dbms_errno)
    return mr_errcode;
  return MR_SUCCESS;
}

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

  cnt_id = *(int *)argv[0];
  /* Check if someone has already set the list for this container */
  EXEC SQL SELECT list_id INTO :list_id FROM containers
    WHERE cnt_id = :cnt_id;
  if (list_id != 0)
    return MR_EXISTS;

  if (dbms_errno)
    return mr_errcode;

  return MR_SUCCESS;
}

/* hostname_check()
 * validate the rfc1035/rfc1123-ness of a hostname
 */

int hostname_check(char *name)
{
  char *p;
  int count;

  /* Sanity check name: must contain only letters, numerals, and
   * hyphen, and not start or end with a hyphen. Also make sure no
   * label (the thing the .s seperate) is longer than 63 characters,
   * or empty.
   */

  for (p = name, count = 0; *p; p++)
    {
      count++;
      if ((!isalnum(*p) && *p != '-' && *p != '.') ||
	  (*p == '-' && p[1] == '.'))
	return 0;
      if (*p == '.')
	{
	  if (count == 1)
	    return 0;
	  count = 0;
	}
      if (count == 64)
	return 0;
    }
  if (*(p - 1) == '-')
    return 0;
  return 1;
}

int hostinfo_check(char *info, int num)
{
  char *p;

  if (!*info)
    return 1;

  /* Sanity check host hostinfo: must start with a letter (or number
   * if num is true), contain only letters, numerals, and hyphen, and
   * not end with a hyphen.
   */

  if (!isalpha(*info) && (!num || !isdigit(*info)))
    return 0;
  for (p = info; *p; p++)
    {
      if ((!isalnum(*p) && *p != '-' && *p != '.') ||
	  (*p == '-' && p[1] == '.'))
	return 0;
    }
  if (!isalnum(*(p - 1)))
    return 1;

  return 1;
}

int setup_aali(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int cnt;
  char *name, *type, *trans;
  EXEC SQL END DECLARE SECTION;
  name = argv[0];
  type = argv[1];
  trans = argv[2];

  if (strcmp(strtrim(type), "FILESYS"))
    return MR_SUCCESS;

  EXEC SQL SELECT count(label) INTO :cnt FROM filesys WHERE
    label = :name;
  if (cnt > 0)
    return MR_EXISTS;

  return MR_SUCCESS;
}

int setup_acon(struct query *q, char *argv[], client *cl)
{
  EXEC SQL BEGIN DECLARE SECTION;
  char containername[CONTAINERS_NAME_SIZE];
  EXEC SQL END DECLARE SECTION;
  
  char* ptr;
  
  memset(containername, 0, sizeof(containername));
  strcpy(containername, argv[0]);
  ptr = strrchr(containername, '/');
  /* sub container, check for parents */
  if (ptr)
    {
      *ptr = '\0';
      EXEC SQL SELECT * FROM containers
	WHERE lower(name) = lower(:containername);	
      if (sqlca.sqlerrd[2] != 1)
	return MR_CONTAINER_NO_PARENT;
    }
  
  if ((mr_errcode = prefetch_value(q, argv, cl)) != MR_SUCCESS)
    return mr_errcode;
  
  return MR_SUCCESS;
}

int check_mailman_listname(char *name, const char *suffix)
{
  char *p;
  EXEC SQL BEGIN DECLARE SECTION;
  int i, cnt;
  EXEC SQL END DECLARE SECTION;

  p = strstr(name, suffix);
  if (p)
    {
      if (strlen(name) == (p - name + strlen(suffix)))
	{
	  /* list is of the form "name-suffix" */
	  i = (p - name);
	  EXEC SQL SELECT COUNT(name) INTO :cnt FROM list
	    WHERE name = SUBSTR(:name, 1, :i) AND mailman = 1;
	  if (cnt > 0)
	    return MR_EXISTS;
	}
    }

  return MR_SUCCESS;
}
