static char rcsid[] = "$Id: query_man.c,v 2.2 2000/01/21 17:37:33 sxw Exp $";
/* 
 *  query_man.c -- Broker query manager interface
 *
 *  DEBUG:  section  75, level 1		Broker query manager routines
 *          section  75, level 4		Broker query_list debugging
 *  AUTHOR: Harvest derived
 *
 *  Harvest Indexer http://www.tardis.ed.ac.uk/harvest/
 *  ---------------------------------------------------
 *
 *  The Harvest Indexer is a continued development of code developed by
 *  the Harvest Project. Development is carried out by numerous individuals
 *  in the Internet community, and is not officially connected with the
 *  original Harvest Project or its funding sources.
 * 
 *  Please mail harvest@tardis.ed.ac.uk if you are interested in participating
 *  in the development effort.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*  ----------------------------------------------------------------------
 *  Copyright (c) 1994, 1995.  All rights reserved.
 *  
 *    The Harvest software was developed by the Internet Research Task
 *    Force Research Group on Resource Discovery (IRTF-RD):
 *  
 *          Mic Bowman of Transarc Corporation.
 *          Peter Danzig of the University of Southern California.
 *          Darren R. Hardy of the University of Colorado at Boulder.
 *          Udi Manber of the University of Arizona.
 *          Michael F. Schwartz of the University of Colorado at Boulder.
 *          Duane Wessels of the University of Colorado at Boulder.
 *  
 *    This copyright notice applies to software in the Harvest
 *    ``src/'' directory only.  Users should consult the individual
 *    copyright notices in the ``components/'' subdirectories for
 *    copyright information about other software bundled with the
 *    Harvest source code distribution.
 *  
 *  TERMS OF USE
 *    
 *    The Harvest software may be used and re-distributed without
 *    charge, provided that the software origin and research team are
 *    cited in any use of the system.  Most commonly this is
 *    accomplished by including a link to the Harvest Home Page
 *    (http://harvest.cs.colorado.edu/) from the query page of any
 *    Broker you deploy, as well as in the query result pages.  These
 *    links are generated automatically by the standard Broker
 *    software distribution.
 *    
 *    The Harvest software is provided ``as is'', without express or
 *    implied warranty, and with no support nor obligation to assist
 *    in its use, correction, modification or enhancement.  We assume
 *    no liability with respect to the infringement of copyrights,
 *    trade secrets, or any patents, and are not responsible for
 *    consequential damages.  Proper use of the Harvest software is
 *    entirely the responsibility of the user.
 *  
 *  DERIVATIVE WORKS
 *  
 *    Users may make derivative works from the Harvest software, subject 
 *    to the following constraints:
 *  
 *      - You must include the above copyright notice and these 
 *        accompanying paragraphs in all forms of derivative works, 
 *        and any documentation and other materials related to such 
 *        distribution and use acknowledge that the software was 
 *        developed at the above institutions.
 *  
 *      - You must notify IRTF-RD regarding your distribution of 
 *        the derivative work.
 *  
 *      - You must clearly notify users that your are distributing 
 *        a modified version and not the original Harvest software.
 *  
 *      - Any derivative product is also subject to these copyright 
 *        and use restrictions.
 *  
 *    Note that the Harvest software is NOT in the public domain.  We
 *    retain copyright, as specified above.
 *  
 *  HISTORY OF FREE SOFTWARE STATUS
 *  
 *    Originally we required sites to license the software in cases
 *    where they were going to build commercial products/services
 *    around Harvest.  In June 1995 we changed this policy.  We now
 *    allow people to use the core Harvest software (the code found in
 *    the Harvest ``src/'' directory) for free.  We made this change
 *    in the interest of encouraging the widest possible deployment of
 *    the technology.  The Harvest software is really a reference
 *    implementation of a set of protocols and formats, some of which
 *    we intend to standardize.  We encourage commercial
 *    re-implementations of code complying to this set of standards.  
 *  
 */
#include "broker.h"
#include "log.h"

#ifndef MAX_ATTRIBUTES
#define MAX_ATTRIBUTES	256
#endif

/* Global variables */
qlist_t *query_list = NULL;
int t_flag = 0;
long sincetime = 0L;
char *errstr = NULL;
char *QM_op = NULL;		/* name of operation to perform */
int QM_opaqueflag = 0;
int QM_descflag = 0;
int QM_gotphrase = 0;
char *QM_attlist[MAX_ATTRIBUTES];
int QM_nattlist = 0;
extern char *DIRpath;
extern char *input_buffer;
extern int input_bufsz;
extern int input_bufferp;

/* Global functions */
extern int yyparse();
extern int yyrestart();

/* Local variables */
static int nqueries = 1, nbulk = 1, nacomd = 1;

/* Local functions */
static void admin_log();

/* Shared data structures */
Buffer *admin_return = NULL;

/* 
 *  QM_query_manager - Based on an event, process a query and 
 *  return information on the socket.
 */
int QM_query_manager(inev)
event_t *inev;
{
	int ret = 0;
	int ttype = inev->type;
	int sock = inev->sock;
	char *tmps = inev->queri;
	time_t ptime = 0;

	/* initialize global variables */
	query_list = NULL;
	t_flag = 0;
	sincetime = 0L;
	if (errstr != NULL) {
		xfree(errstr);
		errstr = NULL;
	}
	switch (ttype) {
	case UQUERY:
		Log("Processing Query %d: %s\n", nqueries++, tmps);
		LOGQUERY(tmps);
		break;
	case ADMIN:
		admin_log(nacomd, tmps);
		nacomd++;
		LOGADMIN(tmps);
		break;
	case QBULK:
	case ABULK:
		Log("Processing Bulk Transfer %d: %s\n", nbulk++, tmps);
		LOGBULK(tmps);
		break;
	default:
		Log("Processing client request: %s\n", tmps);
		break;
	}

	/*
	 *  This sucks.  flex and lex don't work remotely the same.  For both
	 *  flex, we need to read from a memory string rather than a file.
	 *  So we set our input_buffer variables to the query.  Then we
	 *  call yyparse() to parse the query.  If we're running flex, then
	 *  we need to call yyrestart() *after* the yyparse();  if you call
	 *  it before the first yyparse() - core dump.  We're using stdin 
	 *  as the argument; it's ignored anyway.  If you use NULL or something
	 *  else - core dump.  As for lex, query.l defines a dummy yyrestart()
	 *  where we do our own thing.  -Darren
	 */
	input_buffer = tmps;
	input_bufsz = strlen(tmps);
	input_bufferp = 0;
	Debug(75, 1, ("QM_query_manager: query string is (%d bytes)\n\t%s\n",
		input_bufsz, input_buffer));
	do_IND_Init_Flags();
	QM_opaqueflag = 0;	/* Show opaque data or not? */
	QM_descflag = 0;	/* Display Descriptions or not */
	QM_gotphrase = 0;	/* A quote string was present */
	QM_nattlist = 0;
	memset(QM_attlist, '\0', MAX_ATTRIBUTES * sizeof(char *));
	if (QM_op != NULL) {
		xfree(QM_op);
		QM_op = NULL;
	}

	ret = yyparse();
	yyrestart(stdin);

	if (ret == 1) {
		switch (ttype) {
		case UQUERY:
			Log("User query is invalid.\n");
			LOGQUERYERR(PARSERR);
			QM_free_qlist(query_list);
			SWRITE(sock, PARSERR, PARSERR_S);
			break;
		case ADMIN:
			Log("Administrative command is invalid.\n");
			LOGADMIN_R(ADMINERR);
			SWRITE(sock, ADMINERR, ADMINERR_S);
			break;
		case QBULK:
			Log("Bulk query is invalid.\n");
			LOGBULKERR(PARSERR);
			QM_free_qlist(query_list);
			(void)QM_send_bulk_err(sock);
			break;
		case ABULK:
			Log("Bulk query is invalid.\n");
			LOGBULKERR(PARSERR);
			(void)QM_send_bulk_err(sock);
			break;
		default:
			errorlog("QM_query_manager: We should never be here!: %s, line %d, ttype=%d\n",
				__FILE__, __LINE__, ttype);
			break;
		}
		close(sock);
		return ERROR;
	}
	/* 
	 *  Before we do a user query, we need to perform 2 write's on 
	 *  the socket to the client to test whether or not the client 
	 *  will be able to receive the query results.
	 *  We have to do two writes because the first will complete even
	 *  though the other side is gone.
	 */
	if (t_flag == UQUERY) {
		(void) write(sock, PIPECHK, strlen(PIPECHK));
		if (write(sock, PIPECHK, strlen(PIPECHK)) == -1) {
			errorlog("Client is gone -- aborting user query.\n");
			close(sock);
			if (query_list) QM_free_qlist(query_list);
			return ERROR;
		}
	}
	switch (t_flag) {
	case UQUERY:
		if (query_list) {
			(void) do_IND_do_query(query_list, sock, t_flag, ptime);
			QM_free_qlist(query_list);
		} else {
			LOGQUERYERR(PARSERR);
			SWRITE(sock, PARSERR, PARSERR_S);
		}
		break;
	case QBULK:
		ptime = (time_t) sincetime;
		if (query_list) {
			(void) do_IND_do_query(query_list, sock, t_flag, ptime);
			QM_free_qlist(query_list);
		} else {
			LOGBULKERR(PARSERR);
			(void)QM_send_bulk_err(sock);
		}
		break;
	case ABULK:
		ptime = (time_t) sincetime;
		RG_bulk_query(sock, ptime);
		break;
	case QDELETE:
		if (query_list) {
			(void)do_IND_Index_Start();
			ret = do_IND_do_query(query_list, sock, t_flag, ptime);
			(void)do_IND_Index_Flush();
			QM_free_qlist(query_list);
			(void)RG_Sync_Registry();
			if (ret == SUCCESS) {
				LOGADMIN_R(ADMINSUC);
				SWRITE(sock, ADMINSUC, ADMINSUC_S);
			} else {
				LOGADMIN_R(ADMINERR);
				SWRITE(sock, ADMINERR, ADMINERR_S);
			}
		} else {
			LOGADMIN_R(ADMINERR);
			SWRITE(sock, ADMINERR, ADMINERR_S);
		}
		break;
	case ADMIN:
		LOGADMIN_R(ADMINSUC);
		if (admin_return) {
			Debug(75,1,("QM_query_manager: returning %s\n", admin_return->data));
			SWRITE(sock, admin_return->data, admin_return->length);
			free_buffer(admin_return);
		} else {
			Debug(75,1,("QM_query_manager: returning %s\n", ADMINSUC));
			SWRITE(sock, ADMINSUC, ADMINSUC_S);
		}
		admin_return = NULL;
		break;
	default:
		errorlog("QM_query_manager: Fatal internal error 2: %d.\n", 
			 t_flag);
		break;
	}
	(void) close(sock);
	return SUCCESS;
}

/* Free a query argument list */
int QM_free_args(arg)
arg_t *arg;
{
	arg_t *t, *next;
	for (t=arg; t; t=next) {
		next = t->next;
		Debug (75, 4, ("Freeing arg=%x; next=%x\n", t, next));
		switch (t->type) {
		case FUNCTION:
			Debug (75, 4, ("Arg is FUNCTION, calling QM_free_qlist(ptr=%x)\n", t->ptr));
			if (t->ptr)
				QM_free_qlist ((qlist_t *) t->ptr);
			break;
		case STRING:
			Debug (75, 4, ("Arg is STRING, freeing %x='%s'\n",
				t->ptr, t->ptr));
			if (t->ptr) {
				xfree (t->ptr);
				t->ptr = NULL;
			}
			break;
		default:
			Debug (75, 4, ("Arg is Unknown!\n"));
			break;
		}
		xfree (t);
		t = NULL;
	}
	return SUCCESS;
}

/* Recursively free a query list */
int QM_free_qlist(qptr)
qlist_t *qptr;
{
	if (qptr) {
		switch (qptr->type) {
		case LOGICAL:
		Debug (75, 4, ("node is LOGICAL; llist=%x  rlist=%x\n",
			qptr->llist, qptr->rlist));
			if (qptr->rlist)
				QM_free_qlist((qlist_t *) qptr->rlist);
			if (qptr->llist)
				QM_free_qlist((qlist_t *) qptr->llist);
			break;
		case FUNCTION:
		Debug (75, 4, ("node is FUNCTION; llist=%x  rlist=%x\n",
			qptr->llist, qptr->rlist));
			if (qptr->rlist)
				QM_free_args((arg_t *) qptr->rlist);
			if (qptr->llist)
				QM_free_qlist((qlist_t *) qptr->llist);
			break;
		case SELECT:
		Debug (75, 4, ("node is SELECT; llist=%x  rlist=%x\n",
			qptr->llist, qptr->rlist));
			if (qptr->rlist) {
				xfree(qptr->rlist);
				qptr->rlist = NULL;
			}
			if (qptr->llist) {
				xfree(qptr->llist);
				qptr->llist = NULL;
			}
			break;
		}
		xfree(qptr);
		qptr = NULL;
	}
	return SUCCESS;
}

#define BulkBeginStr "@DELETE{  }\n@REFRESH{  }\n@UPDATE{\n"
static int QM_bulk_count;

int QM_send_bulk_begin(sock)
int sock;
{
	Debug(75, 1, ("Starting a Broker-to-Broker transfer...\n"));
	SWRITE(sock, BULK_SUC, strlen(BULK_SUC));
	SWRITE(sock, BulkBeginStr, strlen(BulkBeginStr));
	QM_bulk_count = 0;
	return SUCCESS;
}

int QM_send_bulk_end(sock)
int sock;
{
	SWRITE(sock, "}\n", 2);	/* close the @UPDATE */
	SWRITE(sock, BULK_END, strlen(BULK_END));
	Log("Bulk transfer returned %d objects.\n", QM_bulk_count);
	Debug(75, 1, ("Finished Broker-to-Broker transfer...\n"));
	return SUCCESS;
}


/* ----------------------------------------------------------------- 
   QM_send_bulk_fd -- send a file to the socket ofile, based on FD
   ----------------------------------------------------------------- */
int QM_send_bulk_fd(FD, ofile, tmp)
fd_t FD;
FILE *ofile;
reg_t *tmp;	/* needed for logging */
{
	FILE *ifile;
	char buf[BUFSIZ];
	int n;

	if (tmp == NULL)
		return ERROR;

	LOGBULK_R(tmp);

	if ((ifile = SM_Read_Obj(FD)) == NULL) {
		return ERROR;
	}
	while ((n = fread(buf, 1, BUFSIZ, ifile)) > 0) {
		fwrite(buf, 1, n, ofile);
	}
	fclose(ifile);
	fflush(ofile);
	QM_bulk_count++;
	return SUCCESS;
}

/*
 *  QM_return_attributes - Selects and returns the desired attributes
 *  for FD to rsock socket.
 */
void QM_return_attributes(FD, rsock)
fd_t FD;
int rsock;
{
	FILE *fp;
	Template *T = NULL;
	AVPair *pair;
	int i;
	char buf[BUFSIZ];

	if (QM_nattlist <= 0)	/* any attributes to return? */
		return;

	/* Parse the SOIF */
	if ((fp = SM_Read_Obj(FD)) != NULL) {
		init_parse_template_file(fp);
		T = parse_template();
		finish_parse_template();
		fclose(fp);
	}
	
	/* Valid SOIF? */
	if (T == NULL)
		return;

	/* Now return the attributes */
	for (i = 0; i < QM_nattlist; i++) {
		if ((pair = extract_AVPair(T->list, QM_attlist[i])) != NULL) {
			sprintf(buf, "%03d - %s %ld\n",
				BR_ATTRIBUTE,
				QM_attlist[i],
				(long) pair->vsize);
			(void) write(rsock, buf, strlen(buf));
			(void) write(rsock, pair->value, pair->vsize);
		}
	}
	free_template(T);	/* clean up */
}

#ifdef QM_RET_EMBED_ATT
/*
 *  QM_return_embed_attrs - Selects and returns the desired attributes
 *  of the form embed<nn>-attr for matched lines for FD to rsock socket.
 *
 *  Experimental --DW
 */
void QM_return_embed_attrs(FD, rsock, num)
fd_t FD;
int rsock;
int num;
{
        FILE *fp;
        Template *T = NULL;
        AVPair *pair;
        int i;
        char buf[BUFSIZ];
        char embedattr[128];

        if (QM_nattlist <= 0)   /* any attributes to return? */
                return;

        /* Parse the SOIF */
        if ((fp = SM_Read_Obj(FD)) != NULL) {
                init_parse_template_file(fp);
                T = parse_template();
                finish_parse_template();
                fclose(fp);
        }

        /* Valid SOIF? */
        if (T == NULL)
                return;

        /* Now return the attributes */
        for (i = 0; i < QM_nattlist; i++) {
                sprintf (embedattr, "embed<%d>-%s", num, QM_attlist[i]);
                if ((pair = extract_AVPair(T->list, embedattr)) != NULL) {
                        sprintf(buf, "%03d - %s %ld\n",
				BR_ATTRIBUTE,
                                QM_attlist[i],
				(long) pair->vsize);
                        (void) write(rsock, buf, strlen(buf));
                        (void) write(rsock, pair->value, pair->vsize);
                }
        }
        free_template(T);       /* clean up */
}

/*
** QM_find_embed_attrs
**
** Searches for 'embed<num>-' on a matched line (Glimpse only).
** If found, passes 'num' to QM_return_embed_attrs()
*/
void QM_find_embed_attrs (FD, rsock, ml)
fd_t FD;
int rsock;
char *ml;
{
	int num;
	char jnk[64];

	if (sscanf (ml, "Matched line:  embed<%d>-", &num) == 1) {
		QM_return_embed_attrs(FD, rsock, num);
	} else
	if (sscanf (ml, "Matched line:  %[^#]# embed<%d>-", jnk, &num) == 2) {
		QM_return_embed_attrs(FD, rsock, num);
	}
}

#endif



/* ----------------------------------------------------------------- *
   QM_user_object -- send data about an object back to user.
   * ----------------------------------------------------------------- */
int QM_user_object(rsock, fd, opsize, opdata)
int rsock;
fd_t fd;
int opsize;
char *opdata[];
{
	reg_t *entry;
	int i;
	char buf[BUFSIZ], *s;
	int len;

	/* Be careful not to sprintf something too large! */

	if ((entry = RG_Get_Entry(fd)) == NULL)
		return FAIL;

	/* ----- Show URL ----- */
	sprintf(buf, "%03d - %s\n", BR_URL, entry->url);
	SWRITE(rsock, buf, strlen(buf));

	/* ----- Show Description ----- */
	if (QM_descflag == 1 && entry->desc != NULL && entry->descs > 0) {
		sprintf(buf, "%03d - %d\n", BR_DESCRIPTION, entry->descs);
		SWRITE(rsock, buf, strlen(buf));
		SWRITE(rsock, entry->desc, entry->descs);
	}
	/* ----- Pointer to Summary Object ----- */
	s = SM_Get_Obj_URL(fd);
	sprintf(buf, "%03d - %s\n", BR_SUMMARY_URL, s);
	xfree(s); s = NULL;
	SWRITE(rsock, buf, strlen(buf));

	/* ----- Opaque Data ----- */
	if (QM_opaqueflag == 1) { for (i = 0; i < opsize; i++) {
			if (opdata[i] && (len = strlen(opdata[i])) > 0) {
				if (len > (BUFSIZ-15))
					*(opdata[i]+BUFSIZ-15) = '\0';
				sprintf(buf, "%03d - %s\n",
					BR_OPAQUE_DATA,
					opdata[i]);
				SWRITE(rsock, buf, strlen(buf));
#ifdef QM_RET_EMBED_ATT
				if (QM_nattlist > 0)
				QM_find_embed_attrs (fd, rsock, opdata[i]);
#endif
			}
		}
	}
	/* ----- Attribute Requests ----- */
	QM_return_attributes(fd, rsock);

	/* ----- Object End ----- */
	sprintf (buf, "%d - object end\n", BR_END_OBJECT);
	SWRITE(rsock, buf, strlen(buf));

	LOGQUERY_R(entry);
	return SUCCESS;
}


/* ----------------------------------------------------------------- 
   QM_user_done -- send trailer information from user query.
   ----------------------------------------------------------------- */
int QM_user_done(rsock, count)
int rsock, count;
{
	char ret[BUFSIZ];
	extern char *BrkHomePage, *WebServer;

	QM_free_attributes();	/* do this first */
	if (BrkHomePage != NULL && WebServer != NULL) {
		sprintf(ret, "%03d - http://%s%s\n",
			BR_BROKER_URL, WebServer, BrkHomePage);
		SWRITE(rsock, ret, strlen(ret));
	}
	/* End the Query results with 103 */
	sprintf(ret, "%03d - %d Returned Object(s).\n",
		BR_END_RESULTS, count);
	Log("Query returned %d objects.\n", count);
	SWRITE(rsock, ret, strlen(ret));
	return SUCCESS;
}

/*
 *  QM_add_attribute() - Adds the given #attribute argument to the
 *  query.  Must normalize the attribute name.
 */
int QM_add_attribute(att)
char *att;
{
	QM_attlist[QM_nattlist] = xstrdup(att);
	(void)COL_Normalize_Name(QM_attlist[QM_nattlist]);
	Debug(75,5,("QM: Adding attribute: %s\n",QM_attlist[QM_nattlist]));
	QM_nattlist++;
	return SUCCESS;
}

int QM_free_attributes()
{
	int i;
	for (i = 0; i < QM_nattlist; i++) {
		if (QM_attlist[i] != NULL) {
			xfree(QM_attlist[i]);
			QM_attlist[i] = NULL;
		}
	}
	QM_nattlist = 0;
	return SUCCESS;
}

int QM_user_warning(rsock, message)
int rsock;
char *message;
{
	char ret[BUFSIZ];

	sprintf(ret, "%03d - %s\n", BR_USER_MSG, message);
	SWRITE(rsock, ret, strlen(ret));

	return SUCCESS;
}

int QM_send_bulk_err(sock)
int sock;
{
	Debug(75, 1, ("Error occurred in Broker-to-Broker transfer.\n"));
	SWRITE(sock, BULK_ERR, strlen(BULK_ERR));
	return SUCCESS;
}

static void admin_log(n,s)
int n;
char *s;
{
	char *p, *q, *w, *t, buf[BUFSIZ];

	/* remove password from broker.out */
	for (t = buf, w = s; *w; ) *t++ = tolower(*w++);
	if ((p = (char *) strstr(buf, "#password")) != NULL) {
		p += strlen("#password");
		q = strchr(p+1, '#');
		q = (q == NULL) ? t : q - 1;
		for (t = buf, w = s; *w; t++, w++) *t = (t < p || t > q) ? *w : 'X';
		*t = '\0';
		Log("Processing Admin Command %d: %s\n", n, buf);
	} else {
		Log("Processing Admin Command %d: %s\n", n, s);
	}
}
