/* $Id: common.pc 3956 2010-01-05 20:56:56Z zacheiss $
 *
 * Staff/Student load common code
 *
 * Copyright (C) 1999 by the Massachusetts Institute of Technology
 * For copying and distribution information, please see the file
 * <mit-copyright.h>.
 */

#include <mit-copyright.h>
#include <moira.h>
#include <moira_site.h>
#include <moira_schema.h>
#include "common.h"

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

EXEC SQL INCLUDE sqlca;
extern void sqlglm(char *, unsigned int *, unsigned int *);

RCSID("$HeadURL: svn+ssh://svn.mit.edu/moira/trunk/moira/regtape/common.pc $ $Id: common.pc 3956 2010-01-05 20:56:56Z zacheiss $");

EXEC SQL BEGIN DECLARE SECTION;
extern char *prog;
extern int who;
EXEC SQL END DECLARE SECTION;

extern char *whoami;

#define MIN_ID_VALUE 100
#define MAX_ID_VALUE 131072

/* Remove non-digits from a phone number. */
void fixphone(char *phone)
{
  char *d = phone;

  while (*phone)
    {
      if (isdigit(*phone))
	*d++ = *phone;
      phone++;
    }
  *d = '\0';
}

/* Remove characters from address that aren't safe for passwd files. */
void fixaddress(char *address)
{
  char *p;

  while ((p = strchr(address, ',')))
    *p = ';';
  while ((p = strchr(address, ':')))
    *p = ';';
}  

void process_entry(struct entry *e, int secure)
{
  int changed;
  char buf[MAX_FIELD_WIDTH], *from, *to;
  EXEC SQL BEGIN DECLARE SECTION;
  char *first, *last, *middle, *sid;
  char *name = e->name, *xtitle = e->xtitle, *xaddr = e->xaddress;
  char *xphone1 = e->xphone1, *xphone2 = e->xphone2;
  char type[USERS_TYPE_SIZE], oaddr[USERS_OFFICE_ADDR_SIZE];
  char ophone[USERS_OFFICE_PHONE_SIZE], dept[USERS_DEPARTMENT_SIZE];
  char haddr[USERS_HOME_ADDR_SIZE], hphone[USERS_HOME_PHONE_SIZE];
  char dfirst[USERS_FIRST_SIZE], dlast[USERS_LAST_SIZE];
  char dmiddle[USERS_MIDDLE_SIZE];
  int id, status, fmodby, xnlen = USERS_XNAME_SIZE - 1;
  EXEC SQL END DECLARE SECTION;

  sid = e->id;
  last = e->last;
  if (strlen(last) > USERS_LAST_SIZE - 1)
    last[USERS_LAST_SIZE - 1] = '\0';
  first = e->first;
  if (strlen(first) > USERS_FIRST_SIZE - 1)
    first[USERS_FIRST_SIZE - 1] = '\0';
  middle = e->middle;
  if (strlen(middle) > USERS_MIDDLE_SIZE - 1)
    middle[USERS_MIDDLE_SIZE - 1] = '\0';
  id = 0;

  /* Get user info */
  EXEC SQL SELECT users_id, first, last, middle, type, office_addr,
    office_phone, home_addr, home_phone, department, status, fmodby
    INTO :id, :dfirst, :dlast, :dmiddle, :type, :oaddr,
    :ophone, :haddr, :hphone, :dept, :status, :fmodby
    FROM users
    WHERE clearid = :sid AND status != 3;
  if (sqlfail())
    {
      if (sqlca.sqlcode == SQL_DUPLICATE)
	{
	  com_err(whoami, 0, "duplicate ID number %s on user %s %s",
		  sid, first, last);
	  return;
	}
      else
	sqlexit();
    }
  if (id == 0)
    {
      newuser(e, secure);
      return;
    }
  strtrim(dfirst);
  strtrim(dlast);
  strtrim(dmiddle);
  strtrim(type);

  /* Update type/state if necessary.  (Exclude several spacial cases.) */
  if (strcmp(e->type, type) &&
      strcmp(type, "STAFF") && strcmp(type, "SIPBMEM") &&
      strcmp(type, "KNIGHT"))
    {
      com_err(whoami, 0, "updating type for %s %s from \"%s\" to \"%s\"",
	      first, last, type, e->type);
      if (status == US_NOT_ALLOWED)
	status = US_NO_LOGIN_YET;
      else if (status == US_ENROLL_NOT_ALLOWED)
	status = US_ENROLLED;
      strcpy(type, e->type);
      EXEC SQL UPDATE users
	SET type = NVL(:type, CHR(0)), status = :status,
	modtime = SYSDATE, modby = :who, modwith = :prog
	WHERE users_id = :id;
      if (sqlca.sqlcode)
	{
	  dbmserr("updating user", sqlca.sqlcode);
	  exit(1);
	}
    }

  /* Update name if necessary */
  if (strcmp(first, dfirst) || strcmp(last, dlast) || strcmp(middle, dmiddle))
    {
      com_err(whoami, 0, "updating real name for %s%s%s %s (was %s%s%s %s)",
	      first, *middle ? " " : "", middle, last,
	      dfirst, *dmiddle ? " " : "", dmiddle, dlast);
      EXEC SQL UPDATE users
	SET first = NVL(:first, CHR(0)), last = NVL(:last, CHR(0)),
	middle = NVL(:middle, CHR(0)), modby = :who, modwith = :prog,
	modtime = SYSDATE
	WHERE users_id = :id;
      if (sqlca.sqlcode)
	{
	  dbmserr("updating name", sqlca.sqlcode);
	  exit(1);
	}
    }

  /* Update finger info fields if they have changed and the user
   * didn't set them blank.
   */
  changed = 0;

  if (strncmp(strtrim(oaddr), e->oaddr, USERS_OFFICE_ADDR_SIZE - 1) &&
      (*oaddr || fmodby != id))
    {
      changed++;
      com_err(whoami, 0, "office for %s %s changed from \"%s\" to \"%s\"",
	      first, last, oaddr, e->oaddr);
      strlcpy(oaddr, e->oaddr, USERS_OFFICE_ADDR_SIZE);
    }

  if (strncmp(strtrim(ophone), e->ophone, USERS_OFFICE_PHONE_SIZE - 1) &&
      (*ophone || fmodby != id))
    {
      changed++;
      com_err(whoami, 0, "Phone for %s %s changed from \"%s\" to \"%s\"",
	      first, last, ophone, e->ophone);
      strlcpy(ophone, e->ophone, USERS_OFFICE_PHONE_SIZE);
    }

  if (strncmp(strtrim(haddr), e->haddr, USERS_HOME_ADDR_SIZE - 1) &&
      (*haddr || fmodby != id))
    {
      changed++;
      com_err(whoami, 0, "home addr for %s %s changed from \"%s\" to \"%s\"",
	      first, last, haddr, e->haddr);
      strlcpy(haddr, e->haddr, USERS_HOME_ADDR_SIZE);
    }

  if (strncmp(strtrim(hphone), e->hphone, USERS_HOME_PHONE_SIZE - 1) &&
      (*hphone || fmodby != id))
    {
      changed++;
      com_err(whoami, 0, "Phone for %s %s changed from \"%s\" to \"%s\"",
	      first, last, hphone, e->hphone);
      strlcpy(hphone, e->hphone, USERS_HOME_PHONE_SIZE);
    }

  if (strncmp(strtrim(dept), e->dept, USERS_DEPARTMENT_SIZE - 1) &&
      (*dept || fmodby != id))
    {
      changed++;
      com_err(whoami, 0, "Department for %s %s changed from \"%s\" to \"%s\"",
	      first, last, dept, e->dept);
      strlcpy(dept, e->dept, USERS_DEPARTMENT_SIZE);
    }

  if (changed)
    {
      com_err(whoami, 0, "updating finger for %s %s", first, last);
      EXEC SQL UPDATE users
	SET office_addr = NVL(:oaddr, CHR(0)),
	office_phone = NVL(:ophone, CHR(0)), home_addr = NVL(:haddr, CHR(0)),
	home_phone = NVL(:hphone, CHR(0)), department = NVL(:dept, CHR(0)),
	fmodtime = SYSDATE, fmodby = :who, fmodwith = :prog,
	xname = NVL(SUBSTR(:name, 0, :xnlen), CHR(0)),
	xdept = NVL(:dept, CHR(0)), xtitle = NVL(:xtitle, CHR(0)),
	xaddress = NVL(:xaddr, CHR(0)), xphone1 = NVL(:xphone1, CHR(0)),
	xphone2 = NVL(:xphone2, CHR(0)), xmodtime = SYSDATE
	WHERE users_id = :id;
      if (sqlca.sqlcode)
	{
	  dbmserr(NULL, sqlca.sqlcode);
	  exit(1);
	}
    }
  else
    {
      EXEC SQL UPDATE users
	SET xname = NVL(SUBSTR(:name, 0, :xnlen), CHR(0)), 
	xdept = NVL(:dept, CHR(0)), xtitle = NVL(:xtitle, CHR(0)), 
	xaddress = NVL(:xaddr, CHR(0)), xphone1 = NVL(:xphone1, CHR(0)), 
	xphone2 = NVL(:xphone2, CHR(0)), xmodtime = SYSDATE
	WHERE users_id = :id;
      if (sqlca.sqlcode)
	{
	  dbmserr(NULL, sqlca.sqlcode);
	  exit(1);
	}
    }
}

void newuser(struct entry *e, int secure)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int issecure = secure, users_id, uid, st, xnlen = USERS_XNAME_SIZE - 1;
  int oadlen = USERS_OFFICE_ADDR_SIZE - 1;
  int ophlen = USERS_OFFICE_PHONE_SIZE - 1; 
  char login[USERS_LOGIN_SIZE];
  char *id, *last, *first, *middle, *type;
  char *name, *dept, *haddr, *hphone, *oaddr, *ophone;
  char *xtitle, *xaddress, *xphone1, *xphone2;
  EXEC SQL END DECLARE SECTION;

  users_id = set_next_users_id();
  uid = set_next_uid();
  sprintf(login, "#%d", uid);
  if (!strcmp(e->type, "LINCOLN"))
    st = US_NO_LOGIN_YET_KERBEROS_ONLY;
  else
    st = US_NO_LOGIN_YET;
  last = e->last;
  first = e->first;
  middle = e->middle;
  id = e->id;
  type = e->type;
  name = e->name;
  dept = e->dept;
  haddr = e->haddr;
  hphone = e->hphone;
  oaddr = e->oaddr;
  ophone = e->ophone;
  xtitle = e->xtitle;
  xaddress = e->xaddress;
  xphone1 = e->xphone1;
  xphone2 = e->xphone2;

  com_err(whoami, 0, "adding user %s %s", e->first, e->last);

  EXEC SQL INSERT INTO users
    (login, users_id, unix_uid, shell, winconsoleshell, last, first, 
     middle, status, clearid, type, modtime, modby, modwith, fullname, 
     department, home_addr, home_phone, office_addr, office_phone, fmodtime,
     fmodby, fmodwith, potype, pmodtime, pmodby, pmodwith,
     xname, xdept, xtitle, xaddress, xphone1, xphone2, xmodtime, secure,
     created, creator, winhomedir, winprofiledir, sponsor_type, sponsor_id, expiration)
    VALUES (:login, :users_id, :uid, '/bin/athena/bash', 'cmd',
	    NVL(:last, CHR(0)), NVL(:first, CHR(0)), NVL(:middle, CHR(0)),
	    :st, NVL(:id, CHR(0)), NVL(:type, CHR(0)), SYSDATE, :who, :prog,
	    NVL(:name, CHR(0)), NVL(:dept, CHR(0)), NVL(:haddr, CHR(0)),
	    NVL(:hphone, CHR(0)), NVL(SUBSTR(:oaddr, 0, :oadlen), CHR(0)), 
	    NVL(SUBSTR(:ophone, 0, :ophlen), CHR(0)), SYSDATE, :who, :prog, 
	    'NONE', SYSDATE, :who, :prog, 
	    NVL(SUBSTR(:name, 0, :xnlen), CHR(0)), NVL(:dept, CHR(0)),
	    NVL(:xtitle, CHR(0)), NVL(:xaddress, CHR(0)),
	    NVL(:xphone1, CHR(0)), NVL(:xphone2, CHR(0)), SYSDATE, :issecure,
	    SYSDATE, :who, '[DFS]', '[DFS]', 'NONE', 0, CHR(0));
  if (sqlca.sqlcode)
    {
      dbmserr("adding user", sqlca.sqlcode);
      exit(1);
    }
}

int set_next_users_id(void)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int flag, value, retval;
  EXEC SQL END DECLARE SECTION;

  EXEC SQL SELECT value INTO :value FROM numvalues
    WHERE name = 'users_id';
  if (sqlfail())
    sqlexit();
  if (sqlca.sqlerrd[2] != 1)
    {
      EXEC SQL ROLLBACK;
      com_err(whoami, MR_INTERNAL, "values table inconsistancy");
      exit(1);
    }

  flag = 0;
  EXEC SQL SELECT users_id INTO :flag FROM users
    WHERE users_id = :value;
  if (sqlfail())
    sqlexit();
  if (sqlca.sqlerrd[2] == 0)
    flag = 0;
  while (flag)
    {
      value++;
      flag = 0;
      EXEC SQL SELECT users_id INTO :flag FROM users
	WHERE users_id = :value;
      if (sqlfail())
	sqlexit();
      if (sqlca.sqlerrd[2] == 0)
	flag = 0;
    }

  retval = value++;
  EXEC SQL UPDATE numvalues SET value = :value
    WHERE name = 'users_id';
  if (sqlca.sqlcode)
    {
      dbmserr("assigning ID", sqlca.sqlcode);
      exit(1);
    }
  return retval;
}

int set_next_uid(void)
{
  EXEC SQL BEGIN DECLARE SECTION;
  int flag, initial, value, retval;
  EXEC SQL END DECLARE SECTION;

  EXEC SQL SELECT value INTO :initial FROM numvalues
    WHERE name = 'unix_uid';
  if (sqlfail())
    sqlexit();
  if (sqlca.sqlerrd[2] != 1)
    {
      EXEC SQL ROLLBACK;
      com_err(whoami, MR_INTERNAL, "values table inconsistancy");
      exit(1);
    }

  value = initial;
  flag = 0;
  EXEC SQL SELECT COUNT(unix_uid) INTO :flag
    FROM users WHERE unix_uid = :value;
  if (sqlfail())
    sqlexit();
  if (sqlca.sqlerrd[2] == 0)
    flag = 0;
  while (flag)
    {
      value++;
#ifdef ULTRIX_ID_HOLE
      if (value > 31999 && value < 32768)
	value = 32768;
#endif
      if (value > MAX_ID_VALUE)
	value = MIN_ID_VALUE;
      if (value == initial)
	{
	  com_err(whoami, 0, "Out of uids!");
	  EXEC SQL ROLLBACK WORK;
	  exit(1);
	}
      flag = 0;
      EXEC SQL SELECT COUNT(unix_uid) INTO :flag
	FROM users WHERE unix_uid = :value;
      if (sqlfail())
	sqlexit();
    }

  retval = value++;
  if (value > MAX_ID_VALUE)
    value = MIN_ID_VALUE;
  EXEC SQL UPDATE numvalues SET value = :value WHERE name = 'unix_uid';
  if (sqlca.sqlcode)
    {
      dbmserr("assigning ID", sqlca.sqlcode);
      exit(1);
    }
  return retval;
}

void sqlexit(void)
{
  dbmserr(NULL, sqlca.sqlcode);
  EXEC SQL ROLLBACK WORK;
  exit(1);
}

void dbmserr(char *where, int what)
{
  char err_msg[256];
  int bufsize = 256, msglength = 0;

  sqlglm(err_msg, &bufsize, &msglength);
  err_msg[msglength] = '\0';

  if (where)
    com_err(whoami, 0, "DBMS error %swhile %s", err_msg, where);
  else
    com_err(whoami, 0, "DBMS error %s", err_msg);
}
