/*
 * logintime.c
 *
 * CGI program to retrieve the equivalent of the "Last Update"
 * field from vos examine.
 *
 * Matt Power <mhpower@mit.edu>, 17 March 1996
 *
 * derived from vos.c
 *
 * Build procedure for SunOS 4.1.4:
 *  OBJ=/mit/afsdev/bld/sun4m_412/obj/volser
 *  DEST=/mit/afsdev/bld/sun4m_412/dest
 *  LIB=${DEST}/lib
 *  cc -I${DEST}/include -I/usr/athena/include -target sun4 -c logintime.c
 *  cc -o logintime logintime.o ${OBJ}/vsprocs.o ${OBJ}/vsutils.o \
 *  ${OBJ}/lockprocs.o ${OBJ}/volint.xdr.o ${OBJ}/volerr.o ${OBJ}/libvolser.a \
 *  ${LIB}/afs/vlib.a ${LIB}/afs/libacl.a ${LIB}/afs/libsys.a \
 *  ${LIB}/afs/libvldb.a ${LIB}/libubik.a ${LIB}/afs/libauth.a \
 *  ${LIB}/afs/libsys.a ${LIB}/afs/libcmd.a ${LIB}/librxkad.a \
 *  ${LIB}/libdes.a ${LIB}/librx.a ${LIB}/liblwp.a \
 *  ${LIB}/afs/libcom_err.a ${LIB}/afs/libkauth.a \
 *  ${LIB}/afs/util.a /usr/athena/lib/libhesiod.a
 *
 *
 * Build procedure for Solaris 2.5.1
 *  AFSUSER=/mit/afsuser/sun4m_53
 *  LIB=${AFSUSER}/lib
 *  BLD=/mit/afsdev/bld/sun4m_412/obj/volser
 *  gcc -I${BLD} -I${AFSUSER}/include -I/usr/athena/include -c logintime.c
 *  gcc -I${BLD} -I${AFSUSER}/include -c ${BLD}/volint.cs.c
 *  gcc -I${BLD} -I${AFSUSER}/include -c ${BLD}/vsprocs.c
 *  gcc -I${BLD} -I${AFSUSER}/include -c ${BLD}/vsutils.c
 *  gcc -I${BLD} -I${AFSUSER}/include -c ${BLD}/lockprocs.c
 *  gcc -I${BLD} -I${AFSUSER}/include -c ${BLD}/volint.xdr.c
 *  gcc -I${BLD} -I${AFSUSER}/include -c ${BLD}/volerr.c
 *  gcc -o logintime logintime.o \
 *  volint.cs.o vsprocs.o vsutils.o lockprocs.o \
 *  volint.xdr.o volerr.o \
 *  ${LIB}/afs/vlib.a ${LIB}/afs/libacl.a ${LIB}/afs/libsys.a \
 *  ${LIB}/afs/libvldb.a ${LIB}/libubik.a ${LIB}/afs/libauth.a \
 *  ${LIB}/afs/libsys.a ${LIB}/afs/libcmd.a ${LIB}/librxkad.a \
 *  ${LIB}/libdes.a ${LIB}/librx.a ${LIB}/liblwp.a \
 *  ${LIB}/afs/libcom_err.a ${LIB}/afs/libkauth.a \
 *  ${LIB}/afs/util.a /usr/athena/lib/libhesiod.a -lsocket -lnsl -lresolv -lucb
 *
 *
 * copyright notices for vos.c follow
 */


/*====================================================================
 * Copyright (C) 1990, 1989 Transarc Corporation - All rights reserved 
 *====================================================================*/
/*
 * P_R_P_Q_# (C) COPYRIGHT IBM CORPORATION 1988
 * LICENSED MATERIALS - PROPERTY OF IBM
 * REFER TO COPYRIGHT INSTRUCTIONS FORM NUMBER G120-2083
 */


/*==============================================================
 * 	Sailesh Chutani
 *	Transarc Corporation
 *	September 15, 1989
 *==============================================================
 */


/*
Modifications:

Mar 17 1996 Matt Power	- converted to CGI program for volume update-time queries

Oct 6 1992 Jason Gait	- patch all exit(1) paths to STDERR

Oct 6 1992 Jason Gait	- patch BackupVolume to verify that any existing backup
			  volume is on the same server as the readwrite volume.

Oct 6 1992 Jason Gait	- patch RestoreVolume to fix type in diagnostic output.

Oct 5 1992 Jason Gait	- patch BackSys to test for coredump when volume
			  specified instead of prefix. (Unable to reproduce
			  defect, so no patch for it but test harness left in.)

Oct 5 1992 Jason Gait	- patch ListVLDB so it doesn't coredump when server
			  not specified.

Oct 5 1992 Jason Gait	- patch BackSys so it doesn't coredump when server
			  not specified.

Oct 2 1992 Jason Gait   - patch remove to eliminate support for undocumented
			  (and disastrous) force flag.

Oct 1 1992 Jason Gait   - patch move operation to trap control-c. Warns
			  against interrupting move and asks for
			  confirmation. This code is in volser/vsprocs.c now.

Sept 30 1992 Jason Gait - patch move operation to test for space before
			  starting the operation. The patch is not a
			  complete fix for three reasons:

			  1. there is still no guarantee that the source
			     partition has enough space for the local clone.
			  2. source volume might be updated while the clone
			     is being moved.
			  3. target partition might have space allocated to
			     other volumes before move is completed.
*/
#define ALARM_SETTING 90

#define VUTQ_EARLY -2
#define VUTQ_LATE -1

#define VUTQ_NOTGET 1
#define VUTQ_SHORT 2
#define VUTQ_NORES 3
#define VUTQ_UNFIELD 4
#define VUTQ_TOOLONG 5
#define VUTQ_NOTUSER 6
#define VUTQ_ALARM 7
#define VUTQ_EXFAIL 8
#define VUTQ_NOTEXIST 9
#define VUTQ_NOTACCESS 10
#define VUTQ_GENFAIL 11

#include <afs/param.h>
#include <afs/stds.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#ifdef AFS_AIX_ENV
#include <sys/statfs.h>
#endif
#include <netdb.h>
#include <sys/errno.h>
#include <lock.h>
#include <netinet/in.h>
#include <rx/xdr.h>
#include <rx/rx.h>
#include <afs/nfs.h>
#include <afs/vlserver.h>
#include <afs/auth.h>
#include <afs/cellconfig.h>
#include <afs/keys.h>
#include <ubik.h>
#include <afs/afsint.h>
#include <afs/cmd.h>
#include <rx/rxkad.h>
#include "vol.h"
#include "volser.h"
#include "volint.h"
#include "lockdata.h"
/*
#ifdef	AFS_AIX32_ENV
*/

#include <signal.h>	/* for signal handler in move operation */

/*
#endif
*/

#include <hesiod.h>

static char *the_user_volume;
static char *the_user_cell;

extern int verbose;
int rxInitDone = 0;
struct rx_connection *tconn;
int32 tserver;
extern struct ubik_client *cstruct;
char confdir[] = AFSCONF_CLIENTNAME;
extern struct rx_connection *UV_Bind();
extern  struct rx_securityClass *rxnull_NewClientSecurityObject();
extern int UV_SetSecurity();
extern VL_SetLock();
extern VL_ReleaseLock();
extern VL_DeleteEntry();
extern VL_ListEntry();

extern VL_ChangeAddr();

extern int vsu_ExtractName();
extern PrintError();
extern int MapPartIdIntoName();
extern int MapHostToNetwork();
extern int MapNetworkToHost();
extern void EnumerateEntry();
extern void SubEnumerateEntry();

/* return host address in network byte order */
int32 GetServer(aname)
char *aname; {
    register struct hostent *th;
    int32 addr;
    int b1, b2, b3, b4;
    register int32 code;

    code = sscanf(aname, "%d.%d.%d.%d", &b1, &b2, &b3, &b4);
    if (code == 4) {
	/* parsed as 128.2.9.4, or similar, just use it */
	addr = (b1<<24) | (b2<<16) | (b3<<8) | b4;
	return htonl(addr); /* convert to network order (128 in byte 0) */
    }
    th = gethostbyname(aname);
    if (!th) return 0;
    bcopy(th->h_addr, &addr, sizeof(addr));
    return addr;
}

static void DisplayFormat(pntr,server,part,totalOK,totalNotOK,totalBusy,fast,longlist,disp)
volintInfo *pntr;
int32 server,part;
int *totalOK,*totalNotOK,*totalBusy;
int fast,longlist, disp;
{
  char *update_time;

	if (pntr->status == VOK) 
	  {
	    update_time = (pntr->updateDate < pntr->creationDate) ?
	      ctime(&pntr->creationDate) : ctime(&pntr->updateDate);
            update_time[10] = '\0';
	    update_time[19] = '\0';
	    update_time[24] = '\0';
	    printf("<TITLE>%s: MIT/Athena directory update time</TITLE>\n", the_user_volume + 5);
	    printf("</HEAD>\n");
	    printf("<BODY>\n");
	    printf("%s's home directory was last updated: %s, %s %s<BR>\n",
		   the_user_volume + 5, update_time, update_time + 20,
		   update_time + 11);
	    printf("[likely a login time, but could be an update by the system expunger]<BR>\n");
	    printf("<A HREF=\"logintime.html\">Return to query page</A>\n");
	  }
	else
	  {
	    printf("<TITLE>Failed MIT/Athena login-time query for %s</TITLE>\n", the_user_volume + 5);
	    printf("</HEAD>\n");
	    printf("<BODY>\n");
            printf("<H2>Query Failed</H2>\n");
	    printf("<P>This person's home directory is not accessible from www.mit.edu\n");
	    printf("at the moment. You may wish to try again later.\n</P>");
	    printf("<P><A HREF=\"logintime.html\">Return to query page</A></P>\n");
	  }
	printf("</BODY>\n");
	printf("</HTML>\n");
      }

/* set <server> and <part> to the correct values depending on 
 * <voltype> and <entry> */
static void GetServerAndPart(entry,voltype,server,part,previdx)
struct nvldbentry *entry;
int32 *server,*part;
int voltype;
int *previdx;
{
    int i;
    int success = 0;

    *server = -1;
    *part = -1;
    if((voltype == RWVOL)|| (voltype == BACKVOL)){
	for(i = 0; i < entry->nServers ; i++){
	    if(entry->serverFlags[i] & ITSRWVOL){
		*server = entry->serverNumber[i];
		*part = entry->serverPartition[i];
		*previdx = i;
		success = 1;
	    }
	}
    }
    if(voltype == ROVOL){
	for(i = 0; i < entry->nServers ; i++){
	    if((entry->serverFlags[i] & ITSROVOL) && (i > *previdx)){
		*server = entry->serverNumber[i];
		*part = entry->serverPartition[i];
		*previdx = i;
		success = 1;
		break;
	    }
	}
    }
    if(!success) *previdx = -1;
    return;
}

static void VolumeStats(pntr,entry,server,part,voltype)
volintInfo *pntr;
struct nvldbentry *entry;
int voltype;
int32 server,part;
{
    int totalOK,totalNotOK,totalBusy;
    int32 vcode,vcode2;
    
    DisplayFormat(pntr,server,part,&totalOK,&totalNotOK,&totalBusy,0,1,1);
    return;
}

#if VOLINT_STATS
/*------------------------------------------------------------------------
 * PRIVATE ExamineVolume
 *
 * Description:
 *	Routine used to examine a single volume, contacting the VLDB as
 *	well as the Volume Server.
 *
 * Arguments:
 *	as : Ptr to parsed command line arguments.
 *
 * Returns:
 *	0 for a successful operation,
 *	Otherwise, one of the ubik or VolServer error values.
 *
 * Environment:
 *	Nothing interesting.
 *
 * Side Effects:
 *	As advertised.
 *------------------------------------------------------------------------*/
#endif /* VOLINT_STATS */

static ExamineVolume(as)
register struct cmd_syndesc *as;
{
    struct nvldbentry entry;
    int32 vcode;
    volintInfo *pntr,*oldpntr;
    int32 volid;
    int32 code;
    int voltype;
    int32 aserver,apart;
    int previdx;
    char extension[10];
    int done;
    int firsttime = 1;
#if VOLINT_STATS
    volintXInfo *xInfoP, *origxInfoP;	/*Ptr to current/orig extended vol info*/
    int wantExtendedInfo;		/*Do we want extended vol info?*/
#endif /* VOLINT_STATS */

    previdx = -1;
    vcode = 0;
    pntr = (volintInfo *)0;
#if VOLINT_STATS
    xInfoP = origxInfoP = (volintXInfo *)0;
#endif /* VOLINT_STATS */
    as->parms[0].items->data = the_user_volume;
    volid = vsu_GetVolumeID(as->parms[0].items->data, cstruct);
    if(volid == 0) {
        record_error(VUTQ_LATE, VUTQ_NOTEXIST);
	return -1;
    }

#if VOLINT_STATS
    if (as->parms[1].items)
	wantExtendedInfo = 1;
    else
	wantExtendedInfo = 0;
#endif /* VOLINT_STATS */

    vcode = VLDB_GetEntryByID(volid,-1, &entry);
    if(vcode) {
        record_error(VUTQ_LATE, VUTQ_NOTACCESS);
	return (vcode);
    }
     MapHostToNetwork(&entry);
     if(entry.volumeId[RWVOL] == volid){
	 voltype = RWVOL;
	 strcpy(extension,"");
     }
     if(entry.volumeId[ROVOL] == volid){
	 voltype = ROVOL;
	 strcpy(extension,".readonly");
     }
     if(entry.volumeId[BACKVOL] == volid){
	 voltype = BACKVOL;
	 strcpy(extension,".backup");
     }
    done = 0;
    
    while(1){
	GetServerAndPart(&entry,voltype,&aserver,&apart,&previdx);
	if((voltype == RWVOL) &&(aserver == -1 || apart == -1)){
	    record_error(VUTQ_LATE, VUTQ_NOTEXIST);
	    return ENOENT;
	}
	if(previdx == -1)
	    break;

#if VOLINT_STATS
	if (wantExtendedInfo)
	    code = UV_XListOneVolume(aserver, apart, volid, &xInfoP);
	else
	    code = UV_ListOneVolume(aserver, apart, volid, &pntr);
#else
	code = UV_ListOneVolume(aserver,apart,volid,&pntr);
#endif /* VOLINT_STATS */
	if (code && code != ENODEV) {
	    record_error(VUTQ_LATE, VUTQ_EXFAIL);
	    if(pntr) free(pntr);
#if VOLINT_STATS
	    if (xInfoP)
		free(xInfoP);
#endif /* VOLINT_STATS */
	    return(1);
	}
	oldpntr = pntr;
#if VOLINT_STATS
	if (wantExtendedInfo)
	    origxInfoP = xInfoP;
#endif /* VOLINT_STATS */
	if(code != ENODEV){
	    if(firsttime){
		firsttime = 0;
	    }
#if VOLINT_STATS
		VolumeStats(pntr, &entry, aserver, apart, voltype);
#else
	    VolumeStats(pntr,&entry,aserver,apart,voltype);
#endif /* VOLINT_STATS */
	}
	else {
	    record_error(VUTQ_LATE, VUTQ_NOTACCESS);
	    if(oldpntr) free(oldpntr);
	    return ENOENT;
	}
	if(oldpntr) free(oldpntr);
#if VOLINT_STATS
	if (origxInfoP)
	    free(origxInfoP);
#endif /* VOLINT_STATS */
	if(voltype == RWVOL || voltype == BACKVOL)
	    break;
    }
    /* PostVolumeStats(&entry);   not wanted for CGI program */
    return (0);
}

static MyBeforeProc(as, arock)
struct cmd_syndesc *as;
char *arock; {
    register int32 code;
    register int32 sauth;

    sauth = 0;
    if(as->parms[14].items)	/* -serverauth specified */
	sauth = 1;
    if (code = vsu_ClientInit((as->parms[13].items != 0), confdir, the_user_cell, sauth,
			      &cstruct, UV_SetSecurity)) {
        record_error(VUTQ_GENFAIL);
	exit(1);
    }
    rxInitDone = 1;
    if(as->parms[15].items)	/* -verbose flag set */
	verbose = 1;
    else
	verbose = 0;
    return 0;
}

int osi_audit()
{
/* this sucks but it works for now.
*/
return 0;
}

#include "AFS_component_version_number.c"

main(argc, argv)
int argc;
char **argv; {
    int i;
    char *getenv(), *query_string, *request_method, *uq;
    char **filsys;
    void (*vutq_alarm)();

    register struct cmd_syndesc *ts;

  request_method = getenv("REQUEST_METHOD");
  if (!request_method || strcasecmp(request_method, "GET"))
    {
      record_error(VUTQ_EARLY, VUTQ_NOTGET);
      exit(0);
    }

  query_string = getenv("QUERY_STRING");
  if (!query_string || strlen(query_string) < 3)
    {
      record_error(VUTQ_EARLY, VUTQ_SHORT);
      exit(0);
    }

    ts = (struct cmd_syndesc *) malloc(sizeof(struct cmd_syndesc));
    if (!ts)
      {
	record_error(VUTQ_EARLY, VUTQ_NORES);
	exit(0);
      }
    ts->parms[0].items = (struct cmd_item *) malloc(sizeof(struct cmd_item));
    if (!(ts->parms[0].items))
      {
	record_error(VUTQ_EARLY, VUTQ_NORES);
	exit(0);
      }
    for (i = 1; i < CMD_MAXPARMS; ++i)
      {
	ts->parms[i].items = (struct cmd_item *) 0;
      }
    ts->parms[13].items = (struct cmd_item *) 1; /* noauth */


  uq = (char *) malloc(strlen(query_string));
  if (!uq)
    {
      record_error(VUTQ_EARLY, VUTQ_NORES);
      exit(0);
    }
  url_decode(query_string, uq);

  if (strncmp(uq, "u=", 2))
    {
      record_error(VUTQ_EARLY, VUTQ_UNFIELD);
      exit(0);
    }
  
  for (i = strlen(uq) - 1; i > 2; --i)
    {
      if (uq[i] == ' ' || uq[i] == '\t')
	{
	  uq[i] = '\0';
	}
      else
	{
	  break;
	}
    }

  if (strlen(uq) > 10)
    {
      record_error(VUTQ_EARLY, VUTQ_TOOLONG);
      exit(0);
    }

  if (strlen(uq) < 4 || !strcmp(uq + 2, "other")
      || !strcmp(uq + 2, "backup")
      || !strcmp(uq + 2, "readonly"))
    {
      record_error(VUTQ_EARLY, VUTQ_SHORT);
      exit(0);
    }

  if (non_username(uq + 2))
    {
      record_error(VUTQ_EARLY, VUTQ_NOTUSER);
      exit(0);
    }

  the_user_volume = (char *) malloc(strlen(uq + 2) + 6);
  if (!the_user_volume)
    {
      record_error(VUTQ_EARLY, VUTQ_NORES);
      exit(0);
    }

  sprintf(the_user_volume, "user.%s", uq + 2);

  filsys = hes_resolve(uq + 2, "filsys");
  
  if (filsys == NULL)
    {
      switch (hes_error())
	{
	case HES_ER_NOTFOUND:
	  record_error(VUTQ_EARLY, VUTQ_NOTEXIST);
	  exit(0);
	  break;
	default:
	  record_error(VUTQ_EARLY, VUTQ_NOTACCESS);
	  exit(0);
	  break;
	}
    }

      if (strlen(*filsys) < 5 || strncmp(*filsys, "AFS ", 4))
	{
	  record_error(VUTQ_EARLY, VUTQ_NOTACCESS);
	  exit(0);
	}
      if (!strncmp(*filsys + 4, "/afs/athena", 11))
	{
	  the_user_cell = (char *) malloc(strlen("athena.mit.edu") + 1);
	  if (!the_user_cell)
	    {
	      record_error(VUTQ_EARLY, VUTQ_NORES);
	      exit(0);
	    }
	  strcpy(the_user_cell, "athena.mit.edu");
	}
      else if (!strncmp(*filsys + 4, "/afs/sipb", 9))
	{
	  the_user_cell = (char *) malloc(strlen("sipb.mit.edu") + 1);
	  if (!the_user_cell)
	    {
	      record_error(VUTQ_EARLY, VUTQ_NORES);
	      exit(0);
	    }
	  strcpy(the_user_cell, "sipb.mit.edu");
	}
      else if (!strncmp(*filsys + 4, "/afs/net", 8))
	{
	  the_user_cell = (char *) malloc(strlen("net.mit.edu") + 1);
	  if (!the_user_cell)
	    {
	      record_error(VUTQ_EARLY, VUTQ_NORES);
	      exit(0);
	    }
	  strcpy(the_user_cell, "net.mit.edu");
	}
      else
	{
	  record_error(VUTQ_EARLY, VUTQ_NOTACCESS);
	  exit(0);
	}

  print_header();
  signal(SIGALRM, vutq_alarm);
  alarm(ALARM_SETTING);

#ifdef	AFS_AIX32_ENV
    /*
     * The following signal action for AIX is necessary so that in case of a 
     * crash (i.e. core is generated) we can include the user's data section 
     * in the core dump. Unfortunately, by default, only a partial core is
     * generated which, in many cases, isn't too useful.
     */
    struct sigaction nsa;
    
    sigemptyset(&nsa.sa_mask);
    nsa.sa_handler = SIG_DFL;
    nsa.sa_flags = SA_FULLDUMP;
    sigaction(SIGSEGV, &nsa, NULL);
#endif
    MyBeforeProc(ts);

    ExamineVolume(ts);
    if (rxInitDone) rx_Finalize();
    exit(0);
}


int url_decode(a, c)
     unsigned char *a, *c;
{
  int i = 0, j = 0;
  unsigned char hex2ch();
  
  while (a[i])
    {
      if (a[i] == '%')
        {
          if (strlen(a + i) >= 3)
            {
              c[j++] = hex2ch(a[i + 1], a[i + 2]);
              i += 3;
            }
          else
            {
              break;
            }
        }
      else
        {
          c[j++] = a[i++];
        }
    }
  c[j] = '\0';
}

unsigned char hex2ch(ch1, ch2)
     char ch1, ch2;
{
  int i, j, k;

  i = letter2i(ch1);
  if (i == -1)
    {
      return(' ');
    }
  j = letter2i(ch2);
  if (j == -1)
    {
      return(' ');
    }
  k = (16 * i) + j;
  return((unsigned char) k);
}

int letter2i(ch)
     char ch;
{
  int i;

  if (ch >= '0' && ch <= '9')
    {
      i = ch - '0';
    }
  else if (ch >= 'a' && ch <= 'f')
    {
      i = ch - 'a' + 10;
    }
  else if (ch >= 'A' && ch <= 'F')
    {
      i = ch - 'A' + 10;
    }
  else
    {
      i = -1;
    }
  return(i);
}

int non_username(astr)
     char *astr;
{
  int i;

  for (i = 0; astr[i] != '\0'; ++i)
    {
      if (!username_char(astr[i]))
	{
	  return(1);
	}
    }
  return(0);
}

int username_char(ch)
     char ch;
{
  return ((isalnum(ch) || ch == '_') ? 1 : 0);
}
    
int record_error(etime, etype)
     int etime, etype;
{
  if (etime == VUTQ_EARLY)
    {
      print_header();
    }
  printf("<TITLE>Failed MIT/Athena login-time query</TITLE>\n");
  printf("</HEAD>\n");
  printf("<BODY>\n");
  printf("<H2>Query Failed</H2>\n");
  printf("<P>\n");
  switch(etype)
    {
    case VUTQ_NOTGET:
      fputs("This document supports only the GET method.", stdout);
      break;
    case VUTQ_SHORT:
      fputs("No username was found for this query.", stdout);
      break;
    case VUTQ_NORES:
      fputs("There was insufficient server memory to perform this query.", stdout);
      break;
    case VUTQ_UNFIELD:
      fputs("An unknown field was included in the query.", stdout);
      break;
    case VUTQ_TOOLONG:
      fputs("The username was too long to be valid. The limit is 8 characters.", stdout);
      break;
    case VUTQ_NOTUSER:
      fputs("The username had unexpected characters (ones other than letters, numbers, and underscore).", stdout);
      break;
    case VUTQ_ALARM:
      fprintf(stdout, "The server gave up on this query after %ld seconds.", ALARM_SETTING);
      break;
    case VUTQ_EXFAIL:
      fputs("The query could not be completed.", stdout);
      break;
    case VUTQ_NOTEXIST:
      fputs("The user's directory could not be found.", stdout);
      break;
    case VUTQ_NOTACCESS:
      fputs("The user's directory could not be accessed.", stdout);
      break;
    case VUTQ_GENFAIL:
      fputs("The query program failed to initialize.", stdout);
      break;
    default:
      fprintf(stdout, "Unknown error %ld occurred.", etype);
      break;
    }
  printf("</P>\n");
  printf("<P><A HREF=\"logintime.html\">Return to query page</A></P>\n");
  printf("<HR><P>\nPlease report problems with this service to\n");
  printf("<A HREF=\"comment\">webmaster@mit.edu</A>.\n</P>\n");
  printf("</BODY>\n");
  printf("</HTML>\n");
}

print_header()
{
  printf("Content-type: text/html\n\n");
  printf("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n");
  printf("<HTML>\n");
  printf("<HEAD>\n");
  printf(
   "<LINK REV=\"made\" HREF=\"mailto:webmaster@mit.edu\">\n");
  printf(
   "<LINK REV=\"owner\" HREF=\"mailto:webmaster@mit.edu\">\n");
  printf("<META HTTP-EQUIV=\"Reply-to\" ");
  printf("CONTENT=\"webmaster@mit.edu\">\n");
}

void vutq_alarm()
{
  record_error(VUTQ_LATE, VUTQ_ALARM);
  exit(0);
}
