/*
 *------------------------------------------------------------------
 *
 * $source: /afs/net.mit.edu/project/net_dev/tech/RCS/transact.c,v $
 * $Revision: 3.3 $
 * $Date: 93/02/04 16:13:31 $
 * $State: Exp $
 * $Author: thorne $
 * $Locker: thorne $
 *
 * $Log:	transact.c,v $
 * Revision 3.3  93/02/04  16:13:31  thorne
 * changed kerberos auth return codes.
 * made changes to protect against node created with filenames not permitted
 * 
 * Revision 3.2  93/02/03  11:23:00  thorne
 * new release kerberos etc
 * 
 * Revision 1.8  92/08/28  16:09:57  ark
 * Summer 1992 final version
 * 
 * Revision 1.7  92/08/27  17:11:35  ark
 * Retropatched error message file to allow old clients to work
 * 
 * Revision 1.6  92/08/24  15:47:28  ark
 * WAIS searching added to client & server (J command)
 * 
 * Revision 1.5  92/08/12  14:19:10  ark
 * New version of 'o' transaction tells what output format to use;
 * this is a hack to allow old (but broken) clients to still work.
 * 
 * Revision 1.4  92/08/11  13:44:48  ark
 * Bug fixes: 'n' transaction leaving file descriptors open;
 *            'b' transaction now disallows searches on empty strings.
 * 
 * Revision 1.3  92/08/06  18:44:38  ark
 * Admin command "B" no longer crashes when there are many connections.
 * 
 * Revision 1.2  92/08/04  16:28:24  ark
 * Test production version 8/4/92
 * 
 * Revision 1.1  92/07/22  11:09:36  ark
 * Saber loads quietly; ANSI use standardized; command line options; no behavioral changes
 * 
 * Revision 1.0  92/07/10  12:34:55  ark
 * Initial revision
 * 
 * Revision 1.1  91/07/15  10:40:41  thorne
 * Initial revision

  Copyright (C) 1989 by the Massachusetts Institute of Technology

   Export of this software from the United States of America is assumed
   to require a specific license from the United States Government.
   It is the responsibility of any person or organization contemplating
   export to obtain such a license before exporting.

WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
distribute this software and its documentation for any purpose and
without fee is hereby granted, provided that the above copyright
notice appear in all copies and that both that copyright notice and
this permission notice appear in supporting documentation, and that
the name of M.I.T. not be used in advertising or publicity pertaining
to distribution of the software without specific, written prior
permission.  M.I.T. makes no representations about the suitability of
this software for any purpose.  It is provided "as is" without express
or implied warranty.
*/

#include <stdio.h>
#include <sys/file.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <strings.h>
#include <sys/ioctl.h> /**/
#include <netinet/in.h>
#include "network.h"
#include "web.h"
#include "pdb.h"
#include "nlist.h"
#include "messages.h"
#ifdef KERBEROS
#include <krb.h>
#endif
/* Added for WAIS searching */
#ifdef WAIS
#include "irsearch.h"
#endif
int chgd_since(char *str);
int source_srch(char *str);
int find_ver(void);
void fix_date(NODE * n);
void traverse(void);
void find_key(void);
/*
static void _find_key_aux(long nid);
*/
void change_format(void);
void send_help(void);
void find(void);
static void send_nlist(void);
static void dump_nlist(NLIST * nlist, int numnode, char *_sendbuf,
		       char *_sendbuf_pos, int cur_sock);
void log_trans(char *line);
void get_nextid(void);
void inc_nextid(void);
void save_web(void);
static void save_aux(NODE *n);
int set_dates(void);
static int date_aux(NODE *n,int x);
int web_dump(void);
static void _web_dump_aux(NODE *n);
void add_links(void);
void reorder(int before);
void admin(void);
void provider(void);
void rem_link(void);
void rem_node(void);
void rep_node(void);
void add_node(void);
void send_node(void);
void send_file(void);
void rec_file(void);
int test_admin(char *passwd);
char *test_prov(char *str);
void send_connections(void);
void get_servers(void);
int chg_src_info(char *new_srcline);
char *get_source_info(char *provname);
int valid_source(char *srce);
int is_provider(char *source);
int test_owner(char *source);
int is_node(NODE *n);
void proc_trans();
int parse_trans();
int hdl_transact(CONN *conn);
int is_public(char *source);


int      cur_sock;
int max_stale_time; /* in shutdown mode wait until all conn have been 
		       inactive for this many minutes */
static int      next_id;
static char     in_buff[LG_BUFSIZ];
static int      full_format = FALSE;
static int      format_type;
static CONN     *currconn; /* this is not always correct because of LWPs so
			      be careful how you use it */
extern CONN     conntab[];
extern int      PENDING_SHUTDOWN;
extern char     *shutdown_msg;
extern char     *alt_banner;
extern int ALT_BANNER;
extern int      debug;
extern int      menu;
extern int      disptype;
extern int      errno;
extern int      is_directory(int fd);
extern char     *msglist[]; /* the list of error messages */
extern char     *next_field();
extern void     do_attach(char *name, char *mountpoint, int detach);
extern void     add_recv(int fd, char *file_name, int sock);
extern void     log_read(char *line);
extern void     ensure_access(char file[], char attach_src[], int detach);
extern void     nio_send_file(int sock, char *filename, long startpoint, long requested);
extern void     change_token(char *line, int token_no,char *value);
extern int      num_nonblanks(char *str);


static char    *uid; /* the user id who sent the current transaction */
static char    *trans_data; /* everything after the 1st ":" */  
static char     trans_type;
static int      web_changed;	/* the web has changed since last written */
#define	conn_provider()	(currconn->c_flags & C_PROVIDER) 
#define	strnode(str)	web_node(atol(str))

int
hdl_transact(CONN *conn)
{
	char            log_line[LG_BUFSIZ];
	int             length;

	currconn = conn;
	cur_sock = conn->c_socket;
	uid = conn->c_uid;
	conn->c_trans_cnt++; 
	/* Old full_format variable kept to prevent random problems.
	   New format_type variable is what should now be used 
	   (see network.h) ---ark */
	full_format = (conn->c_flags & C_FULLFMT);
	format_type = conn->c_output_fmt;

	bzero(in_buff, LG_BUFSIZ);
	length = read(cur_sock, in_buff, LG_BUFSIZ);
	if (debug)
	  printf("length read = %d\n",length);
	if (length <= 0) {
	  if (length)
	    perror("socket read");
	  /*                if (errno == EWOULDBLOCK)
			    return 0;
			    else 
	   This caused looping error -ST */
	  return 1;
	}
	if (in_buff[length - 1] == LF)
		length--;
	if (in_buff[length - 1] == CR)
		length--;
	in_buff[length] = '\0';

	if (!parse_trans() || length == 0)
		return 1;	/* quit and close connection */
	if (trans_type == T_ADMIN)
	  sprintf(log_line, "%s:%d:%c", uid, cur_sock, trans_type);
	else if (trans_type == T_TRYPROVIDER)
	  sprintf(log_line, "%s:%d:%c:%5s", uid, cur_sock, trans_type,trans_data);
	else
	  sprintf(log_line, "%s:%d:%c:%s", uid, cur_sock, trans_type, trans_data);

	log_trans(log_line);	/* log the transaction */
	proc_trans();
	return 0;		/* ok */
}

int
parse_trans()
{
	char           *c;

	trans_type = *in_buff;
	c = index(in_buff, DLM);
	if (c)
		trans_data = c + 1;
	else if (in_buff[1] && trans_type)
		trans_data = in_buff + 1;
	else
		trans_data = "";
	if (debug)
	  printf("Transaction(type=`%c' data=[%s])\n", trans_type, trans_data);
	if (trans_type == T_QUIT)
		return 0;
	return 1;
}

/* Switch on transaction type, calling handler procedure */
void
proc_trans()
{
	char *          get_source_info(char *str);
	char            *src_line,*cp;
	int rc;
	switch (trans_type) {

	case T_ADDNODE:
	        add_node();
	        break;		/* add a new topic */
        case T_ADDLINK:
		add_links();
		break;		/* add link */
	case T_RELOAD:          /* reload the web from disk */
	       if (test_admin(uid)) {
		 web_reload();
	         send_msg(msglist[ OK ]);
	       } else
		 send_msg(msglist[ NOT_AUTH ]);
		break;		
	case T_GET_SERVER_INFO:
		get_servers();
		break;		/* close a file desc */
	case T_RMLINK:
		rem_link();
		break;		/* unlink - remove a link */
	case T_RMNODE:
		rem_node();
		break;		/* delete a node */
	case T_GETFILE:
		rec_file();
		break;		/* receive a file */
	case T_SENDFILE:
		send_file();
		return;
	case T_REPLACENODE:
		rep_node();
		break;		/* replace topic info (not links) */
	case T_SENDNODE:
		send_node();
		break;		/* send a single node string */
	case T_TRYPROVIDER:
		provider();
		break;		/* try and be a provider */
	case T_ADMIN:
		admin();
		break;		/* try and be an admin */
	case T_ENDPROVIDER:
		if ((conn_provider()) || test_admin(uid)) {
			save_web();
			send_msg(msglist[ OK ]);
		} else
			send_msg(msglist[ NOT_AUTH ]);
		break;		
	case T_FIND:		/* do a find + return list */
		find();
		break;
	case T_OUTPUTFMT:       /* Made obsolete by NODE_FORMAT below */
		send_msg(msglist[ HUH ]);
		break;
	case T_NODE_FORMAT:     /* Change output format -- fix client bug */
		change_format(); 
		break;
	case T_HELP:		/* return list from HELP_MENU_ID */
		send_help();
		break;
	case T_REORDER_BEFORE:	/* reorder the chld links */
		reorder(TRUE);
		break;
	case T_REORDER_AFTER:	/* reorder the chld links */
		reorder(FALSE);
		break;
	case T_TRAVERSE:	/* do a traverse + return list */
		traverse();
		break;
	case T_SAVEWEB: /* save the web to disk */ {
			if (test_admin(uid)) {
				save_web();
				send_msg(msglist[ OK ]);
			} else
				send_msg(msglist[ NOT_AUTH ]);
			break;
		}
	case T_SET_DATES: {
			if (test_admin(uid)) {
			  rc = set_dates();
			  if (rc == 0) /* we're done */
			    send_msg(msglist[ OK ]);
			  else        /* there's more to do */
			    send_msg(msglist[ MORE ]);
			} else
			  send_msg(msglist[ NOT_AUTH ]);
			break;
		}  

	case T_CHG_BANNER: /* change the banner message */ {
			if (test_admin(uid)) {
			  cp = find_token(trans_data,1);
			   if (strlen(cp) != 0){
			     alt_banner = domalloc((unsigned int) strlen(cp) + 5);
			     sprintf(alt_banner,"%d:%s\n",BANNER_MSG,cp);
			     ALT_BANNER = TRUE; }
			   else
			     ALT_BANNER = FALSE;
			   send_msg(msglist[ OK ]);
			} else
				send_msg(msglist[ NOT_AUTH ]);
			break;
		}
	case T_SHUTDOWN: /* shutdown the server when all conn have been 
			    inactive for max_stale_time */ {
			if (test_admin(uid)) {
				max_stale_time = atoi(trans_data);
				cp  = find_token(trans_data,2);
				if (cp) {
				  shutdown_msg = domalloc((unsigned int) strlen(cp) + 6);
				  sprintf(shutdown_msg,"100:%s\n",cp);
				}
				else
				  shutdown_msg = NULL;
				PENDING_SHUTDOWN = TRUE;
				send_msg(msglist[ OK ]);
			} else
				send_msg(msglist[ NOT_AUTH ]);
			break;
		}
	case T_SHOW_CONN: /* show the connections */ {
			if (test_admin(uid)) {
				send_connections();
			      } else
				send_msg(msglist[ NOT_AUTH ]);
			break;
		}
	case T_FINDKEY:	/* find by key + return list */
		find_key();
		break;
	case T_VERSION:	/* find the current version number */
		find_ver();
		break;
	case T_SOURCE:		/* make sure this source is valid for this
				 * uid */
		if (test_owner(trans_data))
			send_msg(msglist[ OK ]);
		else
			send_msg(msglist[ NOT_AUTH ]);
		break;
	case T_SRC_INFO:
		/* if there is a second token use it as the source name,
		   otherwise use first token as nid */
		cp = find_token(trans_data,2);
		if (cp != NULL)
		  src_line = get_source_info(cp);
	        else
		  src_line = get_source_info(n_source(web_node(atol(trans_data))));
		if (src_line)
		  send_msg(src_line);
		else
		  send_msg(msglist[ CANT_FIND_SOURCE ]);
		do_free(src_line);
	        break;
#ifdef WAIS
	case T_FULL_TXT_SEARCH:
		full_text_search();
		break;
#endif

	case T_CHG_SRC_INFO:
		chg_src_info(trans_data);
		break;
	case T_CHGD_SINCE:
		chgd_since(trans_data);
		break;
	case T_SOURCE_SRCH:
		source_srch(trans_data);
		break;
#ifdef KERBEROS
	case T_KRB_AUTH:
		krb_auth(trans_data);
		break;
#endif
	default:
		send_msg(msglist[ HUH ]);

	}
}
#ifdef KERBEROS
krb_auth(char *buf)
{
  KTEXT authent;
  KTEXT_ST auth;
  AUTH_DAT ad;
  char amsg[50],astr[200],*source,inst[INST_SZ];
  int rc,cnamelen,len;
  struct sockaddr_in cl_addr;
  char *ptr;
 
  strcpy(inst,"*");
  authent = &auth;
  len = atol(buf); /* fill in KTEXT_ST struct */
  authent->length = len;
  ptr = index(buf,DLM);
  if (ptr){
    ptr++;
    bcopy(ptr,auth.dat,len);
  }
  authent->mbz = 0;

  cnamelen = sizeof (cl_addr);
  if (getpeername (cur_sock, (struct sockaddr *) & cl_addr, &cnamelen) < 0)
    perror ("getsockname");
  bzero(&ad,sizeof(AUTH_DAT));

  if (debug)
    printf("length of ktext = %d\n",authent->length);
  rc = krb_rd_req(authent,TI_SERVICE,inst,cl_addr.sin_addr.s_addr,&ad,"");
if (debug)
  printf("return from rd_req = %d\n",rc);

  if (rc != 0)  /* if rc bad send error message & return*/
    {
      sprintf(amsg,"%s:%d:%s",msglist[KERBEROS_ERROR],rc,krb_err_txt[rc]);
      send_msg(amsg);
      return;
    }
  sprintf(astr,"%s.%s@%s:kerberos",ad.pname,ad.pinst,ad.prealm);
if (debug)
  printf("astr = <%s>\n",astr);
  ptr = astr;
  if (source = test_prov(ptr)) {
    currconn->c_flags |= (C_PROVIDER); 
    strncpy(uid, ad.pname ,20);
    sprintf(amsg, "0:%s:%ld", source,ad.checksum);
    send_msg(amsg);
    return;
  }
  send_msg(msglist[ NOT_AUTH ]);    
  return;
}
#endif

/* is this just a dummy node ? */
int
is_node(NODE *n) 
{
  if (strncmp(n_file(n),"/dev/null",8) == 0)
    return FALSE;
  return TRUE;
}

/* is_public: return nonzero if given source is "public" or "world" */
int
is_public(char *source)
{
  if (!source)  /* added by ST */
    return FALSE;
  return((!strcasecmp(source,"public") || !strcasecmp(source,"world"))
	 && (uid != NULL));
}

/* 
 * has_public_access: return nonzero if the current node is public, and
 *  the provider does NOT have permission to alter public nodes.  Only 
 *  all-providers have such permission currently.
 */
int
has_public_access(char *source)
{
  if (is_public(source))
    return(is_provider("all"));
  else return 1;
}


/* is_provider: return nonzero if current provider (given in global "uid")
   provides for the given class "source". */
int
is_provider(char *source)
{
  FILE *provfile;
  char            testline[PROV_LN_SZ], *cp;
  char            provline[PROV_LN_SZ];

  provfile = fopen(PROV_FILE, "r");
  if (!provfile) {
    perror(PROV_FILE);
    return 0;
  }
  
  sprintf(testline, "%s:%s", source, uid);

  /* Look through provider file, checking current source against 
     all provider names for the current user in the file */
  while (fgets(provline, PROV_LN_SZ, provfile)) 
    {
      /* Remove password from end of provider line */
      cp = (char *) rindex(provline, ':');
      if (cp)
	{
	  *cp = '\0';
	  if (!strcasecmp(testline, provline))
	    {
	      fclose(provfile);
	      return 1;
	    }
	}
    }
  fclose(provfile);
  return 0;      
}


/* test_owner: see if the user given in the global "uid" owns the given 
   source */
int
test_owner(char *source)
{				
  
  /* 
     Person owns source if any of the following holds:
     1) Source is public
     2) Provider matches exactly, 
     3) User matches the special provider "all" 
   */
  return (is_public(source) || is_provider(source) || is_provider("all"));
}


/* Test to see if a source has been entered in the provider table.
 * The provider table structure is <source>:<user_id>:<password> */
int
valid_source(char *srce)
{
	FILE           *fopen(), *provfile;
	char            provline[PROV_LN_SZ], *cp;
	int             i;

	provfile = fopen(PROV_FILE, "r");
	if (!provfile) {
		perror(PROV_FILE);
		return 0;
	}
	while (fgets(provline, PROV_LN_SZ, provfile) != '\0') {
		cp = index(provline, ':');
		if (cp) {
			*cp = '\0';
			i = strlen(provline);
			if (strncasecmp(srce, provline, i) == 0) {
				fclose(provfile);
				return 1;
			}
		}
	}
	fclose(provfile);
	return 0;		/* close file */

}

/* gets the long entry of provider information from the SOURCE_FILE */
char *
get_source_info(char *provname)
{				
	FILE           *fopen(), *srcfile;
	char            *srcline, *cp;
	int             i;

	srcline = domalloc((unsigned int) SRC_LN_SZ);
	srcfile = fopen(SOURCE_FILE, "r");
	if (!srcfile) {
		perror(SOURCE_FILE);
		return NULL;
	}
	while (fgets(srcline, SRC_LN_SZ, srcfile) != '\0') {
		cp = index(srcline, ':');
		if (cp) {
      			i = cp - srcline;
			if (i != 0) 
			  if (strncasecmp(provname, srcline, i) == 0) {
			    fclose(srcfile);
			    cp  = index(srcline,'\n');
			    if (cp)
			      *cp = '\0';
			    return srcline;
			  }
		      }
	      }
	fclose(srcfile);
	return NULL;		/* close file */
}

int
chg_src_info(char *new_srcline)
{				/* change the long entry of provider 
				   information in the SOURCE_FILE */

	FILE           *fopen(), *srcfile,*tmp_srcfile;
	char            *srcline, *cp,*source;
	int             i,done = FALSE;

	source = get_token(new_srcline,1);
	if (!test_owner(source))
	  {
	    send_msg(msglist[ NOT_AUTH ]);
	    do_free(source);
	    return; 
	  }


	srcline = domalloc((unsigned int) SRC_LN_SZ);
	srcfile = fopen(SOURCE_FILE, "r");
	if (!srcfile) {
		perror(SOURCE_FILE);
		return;
	}
	tmp_srcfile = fopen(TMP_SOURCE_FILE, "w");
	if (!tmp_srcfile) {
	  fclose(srcfile);
	  perror(TMP_SOURCE_FILE);
	  return;
	}
	while (fgets(srcline, SRC_LN_SZ, srcfile) != '\0') {
		cp = index(srcline, ':');
		if (cp) {
      			i = cp - srcline;
			if (strncasecmp(source, srcline, i) == 0) /* match */{
			  fprintf(tmp_srcfile,"%s\n",new_srcline);
			  done = TRUE;
			}
			else  /* just copy */
			  fprintf(tmp_srcfile,"%s",srcline);
		}
	      }
	if (!done) {  /* put in to add source line if it isn't there,
			 as long as it's in the provider table */
	  fprintf(tmp_srcfile,"%s\n",new_srcline);
	}
	fclose(tmp_srcfile);
	fclose(srcfile);

	rename(SOURCE_FILE,SOURCE_FILE_BACKUP);
	rename(TMP_SOURCE_FILE,SOURCE_FILE);
	send_msg(msglist[ OK ]);

	do_free(source);
	return;		/* close file */
      }

void
get_servers(void)
{
  FILE           *fopen(), *srvfile;
  char           *srvline,buf[BUFSIZ],*cp,line[100];
  int            num = 0;
  
  srvline = domalloc((unsigned int) SRC_LN_SZ);
  srvfile = fopen(SERVER_FILE, "r");
  if (!srvfile) {
    perror(SERVER_FILE);
    send_msg(msglist[ CANT_FIND_SERVER_FILE ]);
    return;
  }
  cp = buf;
  while (fgets(cp, SRV_LN_SZ, srvfile) != '\0') 
    {
      num++;
      cp = index(cp,'\0');
    } 
  fclose(srvfile);
  sprintf(line,"%d:servers\n",num);
  send(line);
  send(buf);send_eom(); 
  deliver();
  do_free(srvline);
  return;
}

  /* BUFFERSIZE must be big enough to handle all possible connections
     + a header */
#define BUFFERSIZE (200 + FD_SETSIZE*110)

/* Send a description of the current connections */
void
send_connections(void)
{
  int i;
  char *buf, *prov, *cp;
  time_t  secs;


  buf = (char *) domalloc((unsigned) BUFFERSIZE);
  bzero(buf, BUFFERSIZE);

  sprintf(buf,"\
%-5s %-5s  %-7s %-8s  %-25s  %-15s %s\n",
"Conn#", "Sock", "Sesslen", "Inactive", "Host", "ClientType", "Prov");

  time(&secs);
  for (i = 0; i < FD_SETSIZE; i++)
    {
      if (conntab[i].c_socket != -1)
        {
          cp = index(buf,'\0');
          if (conntab[i].c_flags & C_PROVIDER)
            prov = "PROV";
          else
            prov = "";
          sprintf(cp,"\
%-5d %-5d  %-7.1f %-8.1f  %-25s  %-15s %s\n"
                  ,i,conntab[i].c_socket,
                  (float) ((secs - conntab[i].c_made) /60.0),
                  (float) ((secs - conntab[i].c_last) /60.0),
                  conntab[i].c_hostname,
                  conntab[i].c_type,
                  prov);
        }
    }
  /*
   * Put End-of-Message on buffer & send it out.  sendbuf() frees the
   *  buffer for us.
   */
  strcat(buf, EOM);
  sendbuf(buf, strlen(buf), cur_sock);
}
#undef BUFFERSIZE

static char     src[20];

/* test to see if a uid is a provider */
char           *
test_prov(char *str)
{

	FILE           *fopen(), *provfile;
	char            provline[PROV_LN_SZ], *id_pswd;
	int             i;

	provfile = fopen(PROV_FILE, "r");
	if (!provfile) {
	        perror(PROV_FILE);
		return 0;
	}
	while (fgets(provline, PROV_LN_SZ, provfile) != '\0') {
		id_pswd = index(provline, ':');
		if (id_pswd) {
			*id_pswd = '\0';
			strcpy(src, provline);
			id_pswd++;
			i = strlen(id_pswd);
			*(id_pswd + i - 1) = '\0';
			if (strcasecmp(str, id_pswd) == 0) {
				fclose(provfile);
				return src;
			}
		}
	}
	fclose(provfile);
	return NULL;		/* close file */

}

/* test to see if a uid is an admin */
int
test_admin(char *passwd)
{
	FILE           *fopen(), *adminfile;
	char            adminline[ADMIN_LN_SZ];

	adminfile = fopen(ADMIN_FILE, "r");
	if (!adminfile) {
		perror(ADMIN_FILE);
		return 0;
	}
	while (fgets(adminline, ADMIN_LN_SZ, adminfile) != '\0') {
		adminline[strlen(adminline) - 1] = '\0';
		if (strcasecmp(passwd, adminline) == 0) {
			fclose(adminfile);
			return 1;
		}
	}
	fclose(adminfile);
	return 0;		/* close file */

}

/* receive a file */
void
rec_file(void)  
{
	NODE           *n;
	int             fd;
	char            testpath[50];
	char            *file_name, tempfile[MAXPATHLEN];
	struct stat     st;

	n = web_node(atol(trans_data));
	if (!test_owner(n_source(n))) {
	  send_msg(msglist[ NOT_AUTH ]);
	  return;
	}

	/* Only allow people to write to the directory of TechInfo files or
	   below, not anywhere in the filesystem they want */
	sprintf(testpath,"%s%s",STORE_DIRECTORY,n_source(n));
	if (debug)
	  printf("testpath = <%s>, filename = <%s>, len = %d\n",testpath,n_file(n),strlen(testpath));
	if (strncasecmp(testpath,n_file(n),strlen(testpath))) {
           send_msg(msglist[ BAD_PATH ]);
	   return;  /* they are trying to put this file in the wrong place */
	 }

     	file_name = domalloc((unsigned int) MAXPATHLEN);
	strcpy(file_name,n_file(n));

	/* See if destination file (NOT temporary file, below) is directory 
	   7/22/91 ark */
	if (!stat(file_name, &st))
	  if (st.st_mode & S_IFDIR)
	    {
	      send_msg(msglist[ DEST_IS_DIRECTORY ]);
	      return;
	    }

	/* Write to temporary file; name is changed in close_recfile() */
	sprintf(tempfile,"%s.tmp",file_name);
	fd = open(tempfile, O_TRUNC | O_CREAT | O_WRONLY, 0644);
#ifdef ATHENA
	if (fd <= 0) {
	  do_attach(PIPS_LOCKER,(char *) 0,1);
       	 /*  system("/var/ti/local/setup");  get tickets and try attaching */
	  fd = open(tempfile, O_TRUNC | O_CREAT | O_WRONLY, 0644);
	}
#endif
	if (fd <= 0) {
		send_msg(msglist[ CANT_OPEN ]);
		do_free(file_name);
		return;
	}
	if (debug)
	  printf("Opened file %s from locker %s as FD #%d.\n", n_file(n), n_locker(n), fd);

	send_msg(msglist[ OK ]);
	add_recv(fd,file_name,cur_sock); /* set it up so that recv will be 
					    handled later */
	if (setflag(n,N_DONT_STAT))
	  fix_date(n); /* we're not stating the file so update the date now */
	
	if (strncasecmp(PIPS_LOCKER,n_locker(n),strlen(PIPS_LOCKER)) == 0)
	  setflag(n,N_DONT_STAT);  
	return;
}

/* send the file associated with a certain node to the client. */
void
send_file(void) 
{
  NODE           *n;
  long            startpoint = 0, requested = 32000;
  char           *fileptr, *sp, *ep, log_line[300];

  n = web_node(atol(trans_data));
  if (flagset(n, N_FAKE)) {
      send_msg(msglist[ NOT_DOCUMENT ]);  
      return;

    }
  /* test if private node */
  if  (flagset(n,N_PRIVATE_IP))
    {
      if (debug)
	printf("PRIVATE\n");
      if (!host_authorized(cur_sock,AUTH_FILE))
	{
	  send_msg(msglist[ PRIVATE ]); /* NEED MSG ***/
	  return;
	}
    }

  if  (flagset(n,N_PRIVATE_KRB))
    {
      char filenm[100];

      sprintf(filenm,"%s%d.acl",ACL_DIR,n_id(n));
      if (debug)
	printf("PRIVATE\n");
      if (!host_authorized(cur_sock,filenm))
	{
	  send_msg(msglist[ PRIVATE ]); /* NEED MSG ***/
	  return;
	}
    }


  /* 
   * Note: this error message has no error code because of a protocol design
   *  flaw described in netio.c:nio_send_file.  Do not use this code except
   *  when sending a file.
   */

  fileptr = n_file(n);

  if (!*fileptr) {
    send_msg(msglist[ NOT_DOCUMENT ]);  
    return;
  }

  sp = find_token(trans_data,2);
  ep = find_token(trans_data,3);
  if (sp)
    startpoint = atol(sp);
  if (ep)
    requested = atol(ep);
if (startpoint == 0)
  {  /* only log if they are getting the ist chunk of file */
    sprintf(log_line, "%ld:%s:%s", n_id(n), n_title(n), n_source(n));
    log_read(log_line);
  }
 if (debug)
   printf("Sending file [%s]:[%s] to socket %d.\n", n_locker(n), fileptr, cur_sock);
  ensure_access(fileptr, n_locker(n), 0);
  
  nio_send_file(cur_sock, fileptr, startpoint, requested);
}

void
send_node(void) /* send all information about a particular node */
{
  /* Bogus!  Old Mac clients must have the flags field zeroed out 
     or they crash.  Check. */
  switch (format_type)
    {
    case SEND_FLAGS_FORMAT:
    case TELNET_FORMAT:
      send_msg((web_node_str(web_node(atol(trans_data)))));
      break;
    case NO_FLAGS_FORMAT:
      send_msg((web_node_str_old(web_node(atol(trans_data)))));
      break;
    }
}
str_match(criterion, sample)    
     char *criterion, *sample;
{
  register char *c, *s;

  c = criterion; s = sample;

  while(TRUE) {
    if (*c == *s && *s == '\0') return(TRUE);
    if (*c == '*') {
      if (*(c+1) == '\0') return(TRUE);
      while(*s != '\0')
        if (str_match(c+1, s++)) return(TRUE);
      return(FALSE);
    }
    if (*c != *s) return(FALSE);
    c++; s++;
  }
}

filepath_ok(char *path)
{          
/* test that the filepath choosen is not on our list of exluded file paths */

       FILE           *fopen(), *exl_file;
        char            line[PROV_LN_SZ], *cp;
        int             i;


       exl_file = fopen(EXCLUSION_FILE, "r");
       if (!exl_file) {
	 perror(EXCLUSION_FILE);
	 return TRUE;      /* no file so I guess its ok!! */
       }
        while (fgets(line, PROV_LN_SZ, exl_file) != '\0') {
	  cp  = index(line,'\n');
	  if (cp)
	    *cp = '\0';
	  if (str_match(line,path))
	    {
	      if (debug)
		printf("line = <%s> path = <%s>\n",line,path);
	      fclose(exl_file);
	      return FALSE;
	    }
	}
       fclose(exl_file);
       return TRUE;            /* close file */
     }


/* 
 * add_node:  adds a node to the web assiging it the "next_id", and
 *   returning this number to the client. No linking is done.
 *   If no filename is given from the client, we make one up based
 *   on the node id #.
 */
void
add_node(void) 
{
	NODE           *n;
	char            line[BUFSIZ];
	char           *cp, *source,*flgs;
	char            str[10], filename[150];
	long             flags;

	source = get_token(trans_data, 6);
	if (test_owner(source) && valid_source(source)) {
		flgs = find_token(trans_data, 2);
		flags = atol(flgs); 
		cp = index(trans_data, DLM);
		get_nextid();
		sprintf(line, "%d%s", next_id, cp);
		sprintf(str, "0:%d", next_id);
		if (flags & N_SYSGEN_FN) {	/*  need to make a file name */
		  change_token(line, 7, PIPS_LOCKER); 
		  sprintf(filename,
			  "%s%s/%d.TXT",STORE_DIRECTORY, source, next_id);
		  change_token(line, 8, filename); 
		}
		else
		  {
		    cp = get_token(line,8);
		    if (!filepath_ok(cp)) {
		      send_msg(msglist[ BAD_PATH]);
		      return;
		    }	
			do_free(cp);
		      }  
		if (debug)
		  printf("Adding new node: %s\n", line);
		inc_nextid();
		n = web_rep_node(line);
		fix_date(n);
		web_changed++;
		send_msg(str);	/* returning node id */
	} else
		send_msg(msglist[ NOT_AUTH ]);
	do_free(source);
}

/* replace all data of a node except links */
void
rep_node(void) 
{
  NODE           *n;
  char           *source,*file,*cp;
  char           *flgs,line[BUFSIZ],filename[150],flagstr[10];
  long            flags;
	
  n = strnode(trans_data);
	if (!is_node(n)) 
	  {
	    send_msg(msglist[ CANT_FIND_NODE ]);  
	    return;
	  }
  source = get_token(trans_data, 6);
  
  /*
   * testing that the source is correct on the node before + after the
   * change 
   */
  
  if (test_owner(n_source(n)) && test_owner(source)
      && valid_source(source)) 
    {
      
      strcpy(line,trans_data);
      
      /* we should check to see if we need to generate a file name */
      
      flgs = find_token(trans_data, 2);
      flags = atol(flgs); 
      flags |= N_RESOLVED; /* set the resolved flag regardless of 
			      what the client says, the server has 
			      already resolved it */
      
       sprintf(flagstr,"%ld",flags);
      change_token(line, 2, flagstr); 
      if (flags & N_SYSGEN_FN) {	/*  need to make a file name */
	change_token(line, 7, PIPS_LOCKER); 
	sprintf(filename,
		"%s%s/%d.TXT",STORE_DIRECTORY, source, next_id);
	change_token(line, 8, filename); 
	if (debug)
	  printf("Replacing node: %s\n", line);
      }
      else
	{
	  cp =  get_token(line,8);
	  if (!filepath_ok(cp)) {
	    send_msg(msglist[ BAD_PATH]);
	    return;
	  }	
	  do_free(cp);
	}   
      /* need to move file if in our storage and the file name changed */
      
      file = get_token(line,8);
      if ((strcmp(file,n_file(n)) != 0) && 
	  (strncmp(n_file(n),STORE_DIRECTORY,
		   strlen(STORE_DIRECTORY)) == 0))
	{ 
	  /* the filename has changed AND its in our filespace */
	  rename(n_file(n),filename);
	}
      do_free(file);
      web_rep_node(line);
      if (not_menu(n))
	fix_date(n); /* only update the date if not a text or 
			image node */
      web_changed++;
      send_msg(msglist[ OK ]);    
    } 
  else
    send_msg(msglist[ NOT_AUTH ]);
  do_free(source);
}

/* Delete a node and all its references, if it has no children. 
   Tries to remove the file the node references. */
void
rem_node(void)
{
	NODE           *n;
	char testpath[50],cmd[80];

	n = strnode(trans_data);
	if (!is_node(n)) {
	  send_msg(msglist[ CANT_FIND_NODE ]);
	  return;
	}

	if (n_numchild(n) > 0) {
		send_msg(msglist[ DEL_CHILD_1ST]);	/* has children */
		return;
	}

	if (test_owner(n_source(n))) {
	  if (text_node(n)){
	    sprintf(testpath,"%s%s",STORE_DIRECTORY,n_source(n));
	    if (strncasecmp(testpath,n_file(n),strlen(testpath))==0) {
	      sprintf(cmd,"rm %s",n_file(n));
	      system(cmd);
	      sprintf(cmd,"rm %s~",n_file(n));
	      system(cmd);}
	  }
		web_del_node(n);
		web_changed++;
		send_msg(msglist[ OK ]);
	} else
		send_msg(msglist[ NOT_AUTH ]);
}

/* remove a link */
void
rem_link(void)
{
	NODE           *parent;
	NODE           *child;

	parent = strnode(trans_data);
	if (!is_node(parent)) {
	  send_msg(msglist[ CANT_FIND_NODE ]);
	  return;
	}

	if (test_owner(n_source(parent))) {
		child = strnode(index(trans_data, DLM) + 1);
		web_del_link(parent, child);
		web_changed++;
		send_msg(msglist[ OK ]);
	} else
		send_msg(msglist[ NOT_AUTH ]);
}

/* If the password given by the client is correct, establish him as a provider.
 * In any case, give an appropriate message */
void
provider(void)
{
  char           *cp, *source, amsg[25];
   
  if ((strstr(trans_data,"kerberos") != NULL)) 
    {                   /* check to make sure a kerberos user is not
			   trying to gain access in the clear */
      send_msg(msglist[ BAD_PASSWD ]);
      return;
    }

  if (source = test_prov(trans_data)) {
    currconn->c_flags |= (C_PROVIDER); 
    cp = index(trans_data, DLM);
    if (cp)
      *cp = '\0';
    strncpy(uid, trans_data,20);
    sprintf(amsg, "0:%s", source);
    send_msg(amsg);
  } else
    send_msg(msglist[ BAD_PASSWD ]);
}

/* If the client's uid is ok, establish him as an administrator. */
void
admin(void)
{
	if (test_admin(trans_data)) {
		strcpy(uid, trans_data);
		send_msg(msglist[ OK ]);
	} else
		send_msg(msglist[ NOT_AUTH ]);
}

/* Insert a child into a different spot in the child list-menu.
 * This is not an exchange of places, but a push down (if before = 0)
 * or a push up (if before nonzero) 
 */
void
reorder(int before) 
{
	NODE           *n;
	char           *linkstr, *ptr;
	char           *nid2, *nid3;
	int             i, found = 0, fnd = 0;

	nid2 = get_token(trans_data, 2);	/* the nid whose position
						 * nid3 will take */
	nid3 = get_token(trans_data, 3);

	n = web_node(atol(trans_data));
	if (!is_node(n)) {
	  send_msg(msglist[ CANT_FIND_NODE ]);
	  return;
	}
	if (!test_owner(n_source(n))) {
		send_msg(msglist[ NOT_AUTH ]);
		return;
	}
	if (debug)
	  printf("Switching links %s and %s.\n", nid2, nid3);

	linkstr = (char *) domalloc((unsigned int) (n_numchild(n) * 6) + 1);
	*linkstr = '\0';
	ptr = linkstr;

	for (i = 0; i < n_numchild(n); i++) {
		if (n_id(n_child(n, i)) == atol(nid3)) {
			fnd++;
			continue;	/* if this is the one being moved
					 * skip it */
		}
		if ((n_id(n_child(n, i)) != atol(nid2)) || found)
			sprintf(ptr, ",%ld", n_id(n_child(n, i)));
		else {
		  if (before) /* put node before */
			sprintf(ptr, ",%s,%ld", nid3, n_id(n_child(n, i)));
		  else
			sprintf(ptr, ",%ld,%s", n_id(n_child(n, i)), nid3);
		  found++;
		}
		ptr = (char *) rindex(linkstr, '\0');	/* find the end of the
							 * linkstr */
	}			/* end of for */
	ptr = ++linkstr;	/* skip the first comma */

	if ((!found) || (!fnd)) {
		send_msg(msglist[ CANT_REORDER_NODES ]);	/* couldn't find a nid */
		return;
	}
	web_reorder_links(n, ptr);
	web_changed++;
	send_msg(msglist[ OK ]);
}

/* add child links to a parent node  & reorder possibly */
void
add_links(void) 
{
	NODE           *parent;
	char           *child_str;
	char           *reorder_str;
	int            reord = FALSE, rc;
	char           trans_buf[100];

	strcpy(trans_buf,trans_data);
	child_str = index(trans_buf, DLM);
	if (child_str != NULL){
	  child_str++;
	  child_str[-1] = '\0';
	}
	else {
	  send_msg(msglist[ CANT_FIND_NODE ]);
	  return;
	}

	reorder_str = index(child_str, DLM);
	if (reorder_str != NULL){
	  reord = TRUE;
	  reorder_str++;
	  reorder_str[-1] = '\0';
	  if (debug)
	    {
	      printf("reord_str = <%s>\n",reorder_str);
	      printf("child_str = <%s>\n",child_str);
	    }

	}
	parent = strnode(trans_data);
	if (!is_node(parent)) {
	  send_msg(msglist[ CANT_FIND_NODE ]);
	  return;
	}
	if (test_owner(n_source(parent))) {
		rc = web_add_links(parent, child_str);
		web_changed++;
		if (reord) {
		  sprintf(trans_data,"%ld:%s:%s",n_id(parent),
			  reorder_str,child_str);
		  if (debug)
		    printf("trans_data = <%s>\n",trans_data);
		  reorder(TRUE);
		}
		  else
		    if (rc)
		      send_msg(msglist[ OK ]);
		    else
		      send_msg(msglist[ ITEM_EXISTS]);  
	} else
		send_msg(msglist[ NOT_AUTH ]);
}

static void
_web_dump_aux(NODE *n)
{
	char           *nstr;

	if (!n) {
		puts("Bad node!");
		return;
	}
	nstr = web_node_str(n);
	nstr[strlen(nstr)] = LF;
	send(nstr);
}

int
web_dump(void)
{
  web_allnodes(_web_dump_aux);
  send_eom();
  return 0;
}
static int endnum,startnum; 

int
date_aux(NODE *n, int x)
{
struct stat st;
short newdate;
int rc;

if (x >= startnum) {
/* if text node && flag not set 
   stat file and modify last changed date */
  /*  clrflag(n,N_DONT_STAT); just for the first run if flag is not
      clear already*/
if (text_node(n) && !(flagset(n,N_DONT_STAT)))
  {
    rc = stat(n_file(n),&st);  
    if (rc) {  
      if (strcasecmp(PIPS_LOCKER,n_locker(n)) != 0)
	ensure_access(n_file(n), n_locker(n), 1);
      rc = stat(n_file(n),&st);
    }
    if (!rc) {
      newdate = (short) ((st.st_mtime/86400 + 1));
      n_date(n) = newdate;
      if (strlen(trans_data) != 0) {   /* this doesnt make much sense
					  it would be better to write a
					  seperate func for setting flags */
	/* if (strncasecmp(PIPS_LOCKER,n_locker(n),strlen(PIPS_LOCKER)) == 0)
	  setflag(n,N_DONT_STAT);   just for the first run!!!! */
      }
    }
  }
}
if (x > endnum)
  return TRUE;
return FALSE;
}
static int firstnode,lastnode;
int
set_dates(void)
{
  int rc;
  union wait status;
  struct rusage rusage;
  char *cp;

cp = find_token(trans_data,1);
if (cp)
  startnum = atoi(cp);
cp = find_token(trans_data,2);
if (cp)
  endnum = atoi(cp);
printf("startnum = %d, endnum = %d\n",startnum,endnum);
  rc = web_allnodes2(date_aux);
  if (rc == 0) {
    web_changed++;
    save_web();
  }
  return rc;
}

static int      dfd;

static void
save_aux(NODE *n)
{
  char           *nstr;
  int             len;
  
  /* This uses the new version of web_node_str, i.e. DO save the flags */
  nstr = web_node_str(n);
  len = strlen(nstr);
  nstr[len] = LF;
  write(dfd, nstr, len + 1);
}

/* save the current web to disk */
void
save_web(void) 
{
	char            log_line[256];
	int             rc,local = FALSE;
        union wait status;
        struct rusage rusage;
	if (web_changed) {
	     web_changed = 0;
	     rc = fork();
	     if (rc <= 0) {
	       char tmp_file[MAXPATHLEN];
	       if (rc < 0)
		 {
		   sprintf(log_line,"errno from fork = %d, rc = %d",errno,rc);
		   log_trans(log_line);
		 }

	       sprintf(tmp_file,"%sXXXXXX",WEB_TMP_FILE);
	       mktemp(tmp_file);/* use a random number, so that
					     if 2 processes are writing out
					     the web then they wont collide */

		  get_nextid();	/* read next id from disk */
                  dfd = open(tmp_file, O_WRONLY | O_CREAT | O_TRUNC,0644);
		  if (dfd < 0) {
		    do_attach(PIPS_LOCKER,(char *) 0,1);
		/*    system("/site/local/setup"); */
		    dfd = open(tmp_file, O_WRONLY | O_CREAT | O_TRUNC,0644);
		  }
		  if (dfd < 0) { /* couldn't open */
		    dfd = open(LOCAL_WEB_FILE, O_WRONLY | O_CREAT | O_TRUNC,0644);
		    if (dfd < 0) {  /* couldn't open */
			   printf("***** couldn't write the web anywhere!!\n");
			   exit(0);
		       }
		    sprintf(log_line, "saving the web to local disk- next id = %d", next_id);
		    local = TRUE;
		  }
		  else /* tmp file opened successfully */
		    sprintf(log_line, "saving the web to disk- next id = %d", next_id); 
		  printf("TechInfo:  Writing web to disk.\n");
		  web_allnodes(save_aux);
		  close(dfd);
		  if (!local) {
		    rename(WEB_FILE,WEB_BAK_FILE);
		    rename(tmp_file,WEB_FILE);
		    chmod(WEB_FILE,0644);
		  }
		  log_trans(log_line);
	       sprintf(log_line,"the rc from fork = %d",rc);
	       log_trans(log_line);
		/*  if (rc == 0)	{
		    printf("TechInfo:  Done saving web.\n");
		    exit(0); } */
	       exit(0);  /* exit no matter what! If we're here we either 
			    are the child and should go away, or the fork 
			    failed (no memory?), so we better exit now
			    while the web has been saved */
	     }
	     else /* if parent */ {
	       rc = wait3(&status,WNOHANG,&rusage);
	     }
	   }
}

/* increments next id in memory + on disk */
void
inc_nextid(void)
{
	char            line[20];

	if ((dfd = open(NEXTID_FILE, O_WRONLY | O_TRUNC | O_CREAT)) <= 0)
	  {
		printf("error incrementing next id\n");
		return;
	      }
	next_id++;
	sprintf(line, "%d\n", next_id);
	write(dfd, line, strlen(line));
	close(dfd);
	return;
}

/* reads the next id from disk */
void
get_nextid(void)
{
	char            line[20];
	int             red;

	if ((dfd = open(NEXTID_FILE, O_RDONLY)) <= 0){
		printf("error getting next id\n");
		return;
		}
	red = read(dfd, line, 20);
	if (red <= 0){
		printf("error reading next id\n");
		return;
		}
	line[red] = '\0';
	next_id = atoi(line);
	close(dfd);
	return;
}

/* write the transaction to the log file */
void
log_trans(char *line)
{
	char            logline[LG_BUFSIZ];
	time_t          secs;

	time(&secs);
	sprintf(logline, "%s:%s", line, ctime(&secs));
	if ((dfd = open(TRANS_LOG, O_APPEND | O_CREAT | O_WRONLY, 0644)) == 0)
		printf("error logging transaction\n");
	write(dfd, logline, strlen(logline));
	close(dfd);
	return;
}

/*
 * These send/dump nlist routines have unfortunately had to delve into the
 * realm of NLIST's, that nlist.c only used to know about. Didn't have the
 * time to make it a nice object-oriented abstract data type. 
 */

static void
dump_nlist(NLIST * nlist,
	   int numnode,
	   char *_sendbuf,	/* These override send()'s */
	   char *_sendbuf_pos,	/* default parameters */
	   int cur_sock)
{				/* (in the macro). */
	char            buf[512];
	int             i;
	NODE           *n;
	char           *nlist_fmt_line_part_two();

	if (full_format)
	  disptype = FULL;/* Do we need this? */
	sprintf(buf,"%d:\n",numnode); /* send the number of nodes to expect */
	if (!send(buf))
	  return;
	for (i = 0; i < numnode; i++) 
	  {
	    /* Don't try to run off end of nlist array; numnode may be larger
	       than maximum allowable subscript.  7/24/92 ark */
	    if (i >= NLIST_MAX_LEN)
	      {
		/* For now, just quit, though we could try to send an error
		   message. */
		break;
	      }

	    
	    n = nlist[i].n;

	    /* 
	     * When dumping an nlist, we first send the # of nodes that 
	     * we're about to send.  Of course, there should be an OK/error 
	     * code prepended, but there isn't. 
	     */
	    switch (format_type)
	      {
	      case NO_FLAGS_FORMAT:
		sprintf(buf, "%d%c", nlist[i].lev, DLM);  
		strcat(buf, web_short_node_str_old(n));
		break;
	      case SEND_FLAGS_FORMAT:	
		sprintf(buf, "%d%c", nlist[i].lev, DLM);
		strcat(buf, web_short_node_str(n));
		break;
	      case TELNET_FORMAT:
		sprintf(buf, "%s",nlist_fmt_line_part_two(i, nlist));
		break;
	      }
	    
	    if (!send(buf))
	      return;
	    if (!send_eol())
	      return;
	  }
 	if (!send_eom())
	  return;
	deliver(); 
}


static void
send_nlist(void)
{
	int             numnode = nlist_numnode();
	NLIST          *nlist = nlist_current();
	char           *buf, *sendbuf_pos;
	
	if (debug)
	  printf("Dumping nlist with %d nodes to #%d.\n", numnode, cur_sock);
	buf =  domalloc((unsigned int) 300*(numnode + 1));
	sendbuf_pos = buf;
	dump_nlist(nlist, numnode, buf, sendbuf_pos, cur_sock);
	return;
}

/* Keyword search--search for given word beginning at given node.  If no node
   is given, start at node 0, which is assumed to be the root node.  If no
   word is given, return no nodes. */
void
find(void)
{
  NODE *node = NULL;
  char *cp;

  cp = (char *) index(trans_data,DLM);
  if (cp) {
    *cp = '\0'; cp++;
    node = web_node(atol(cp));
  }
  
  /* 
   * If the search string is empty, return nothing (otherwise you'd get back
   *  the whole web.  But because of yet-another-protocol-flaw, no code is
   *  returned as a result of this command, but instead the number of matching
   *  nodes is sent, followed by a colon.  Thus we can't send an error 
   *  message or it will be interpreted as a positive # of matching nodes.
   *  So we just say that no nodes matched. 
   */

  if (num_nonblanks(trans_data) == 0)
    {
      send_msg("0:");
      return;
    }
  
  nlist_reset();
  web_find_topic(trans_data, nlist_add, node);
  send_nlist();
}

/* Change output format */
void 
change_format()
{
  int format;

  format = atoi(trans_data);
  if ((format < 1) || (format > NUM_FORMATS))
    {
      send_msg(msglist[ UNKNOWN_FORMAT ]);
      return;
    }
  currconn->c_output_fmt = format;
  send_msg(msglist[ OK ]);
}

void
send_help(void)
{
  nlist_reset();
  nlist_build(2,web_node(HELP_MENU_ID),(u_short) 1);
  send_nlist();
}

/*
static void
_find_key_aux(long nid)
{
  nlist_add(web_node(nid), 0);
}
*/

/* find nodes which match keyword & return a node list */
void
find_key(void) 
{

	nlist_reset();
	/* 
	 * int             hits;
	 * hits = key_search(trans_data, _find_key_aux); 
	 */
	send_nlist();
}

/* This function is new for WAIS searching */
#ifdef WAIS
full_text_search() /* run a full text search on the inverted index using
		      the wais search engine  */
{
  char   *cp,  *search_string, *nid;
  long   i, max_docs;
  hit *results_array;
  
  /* Format of command:
     J:<search string>:<optional node id>:<optional max number of docs> */
  search_string = trans_data;
  if (cp = index(trans_data, DLM))   /* returns a NULL if there are no more */
    *cp++ = '\0';
  else
    cp = "0";
  nid = cp;                          /* if there are delimiters, put a NULL */
  if (cp = index(cp, DLM))           /* in place of the delimiter and  */
    *cp++ = '\0';                    /* move up a character. */  
  if (cp == NULL)
    max_docs = 40;
  else
    max_docs = atol(cp);

  if (debug)
    printf("Text search: string=[%s] nid=[%s] max_docs=[%d]\n", 
	   search_string, nid, max_docs);
  

  if (results_array = 
      run_search(TI_INDEX_NAME, TI_INDEX_DIR, search_string, max_docs))

    {
      nlist_reset();
      
      if (*nid == '0' || *nid == '\0')
	{
	  
	  for (i = 0; i < max_docs; i++)
	    {
	      	      
	      if (results_array[i].weight == 0)
		break;
	      else
		nlist_add(web_node(results_array[i].document_id), 0);
	    }
	}
      else
	{
	  for (i = 0; i < max_docs; i++)
	    setflag(web_node(results_array[i].document_id), N_HIT);
	  
	  web_traverse(TRAV_DOWN, web_node(atol(nid)), nlist_add_srchflag,
		       (u_short) 99);
	  
	}
      send_nlist();
      return;
    }

  /* AAAAGH!  When sending a nodelist, clients expect the # of nodes to 
     be returned first, so we can't even send an error message here without
     causing them to bomb. */
  send_msg("0:");
}
#endif
/* traverse the web & return a node list */
void
traverse(void) 
{
	char           *cp, *direction, *nid, *depth;

	direction = trans_data;
	if (cp = index(trans_data, DLM))
		*cp++ = '\0';
	else
		cp = trans_data;
	nid = cp;
	if (cp = index(cp, DLM))
		*cp++ = '\0';
	else
		cp = nid;
	depth = cp;
	if (debug)
	  printf("Traversing web: direction=[%s] node=[%s] depth=[%s]\n", direction, nid, depth);

	nlist_reset();
	nlist_build(atoi(direction), web_node(atol(nid)), (u_short) atoi(depth));

	send_nlist();
}

/* alter the date stored in the node to be today */
void
fix_date(NODE * n) 
{
	long            secs;
	short           date;

	time(&secs);
	date = (short) ((secs / 86400) + 1);
	n_date(n) = date;	
}

/* find the version number of the client associated with a certain machine */
int
find_ver(void) 
{
	FILE           *fopen(), *verfile;
	char            verline[ADMIN_LN_SZ];
	int             len;

	verfile = fopen(VER_FILE, "r");
	if (!verfile)
	  return 1;

	len = strlen(trans_data);
	currconn->c_type = domalloc((unsigned int) len+1);	/* set c_type in conntab */
	strcpy(currconn->c_type,trans_data);
	while (fgets(verline, ADMIN_LN_SZ, verfile) != '\0') {
		verline[strlen(verline) - 1] = '\0';
		if (strncasecmp(trans_data, verline,strlen(trans_data)) == 0) {
			fclose(verfile);
			send_msg(verline);
			return 0;
		}
	}
	fclose(verfile);
	send_msg(msglist[ VERSION_NOT_FOUND ]);

	return 0;		/* close file */
}

int srch_yr; /* globals used by nlist_changed_since */
int srch_mo;
int srch_day;

/* return an nlist with all nodes (non-menu) which have changed since
   a given date */
int 
chgd_since(char *str)
{
  NODE *node;
  char *cp = str;
  
  node = web_node(atoi(cp));
 /*  printf("node = %ld\n",n_id(node)); */
  srch_mo = atoi(next_field(&cp));
  srch_day = atoi(next_field(&cp));
  srch_yr = atoi(next_field(&cp));
  printf("mo = %d, day = %d, year = %d\n",srch_mo,srch_day,srch_yr);
  nlist_reset();
  web_traverse(TRAV_DOWN|TRAV_NOREPEAT,node,nlist_changed_since, (u_short) 99);
  send_nlist();
}

char *srch_source; /* globals used by nlist_source_srch */

/* return an nlist with all nodes which have changed a certain source */
int 
source_srch(char *str)
{
  NODE *node;
  char *cp = str;
  
srch_source = cp;
  next_field(&cp);   
  node = web_node(atoi(cp));  
  printf("node = %ld\n",n_id(node));  
  printf("source = <%s>\n",srch_source);
  nlist_reset();
  web_traverse(TRAV_DOWN|TRAV_NOREPEAT,node,nlist_source_srch, (u_short) 99);
/* should use web_allnodes for whole list */
  send_nlist();
}


