/* -*- C -*- */
/* [SRI-NIC]PS:<NICPROG>WHOIS.C.1145, 10-Dec-90 12:43:03, Edit by ZZZ
 * In find_temp() - use the temp flags, don't just OR them in (must be able
 * to do temp search without a flag that's already set).  Changed calls
 * to find_temp() to OR their own flags in where appropriate.
 * This fixes the mysterious extra matches in the subdomain display by
 * eliminating the partial match flag from that temp search.
 * (*** same as 1144, but without intervening netblock/asn changes ***)
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1134,  8-Jun-90 20:29:21, Edit by ZZZ
 * Added find_big() to handle searches with more than MAX_MATCH matches.
 * Added boolean return codes to sub_matches(), display_matches(), and
 * summary() to preserve listing continutity across search&display
 * iterations.  (ie. when a user says "no" to "see more?", it should stop!)
 * Added time_thru argument to same procs to allow them to tailor their
 * output to match whether it's the first display block or not.
 * Dropped MAX_MATCH back down to 2048.
 *
 * ***Note:  To make other procedures take advantage of this large-match
 * capability, they need to be changed to use the find_big() and to accept
 * the aforementioned return codes throughout their output path.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1133,  1-Jun-90 16:02:12, Edit by ZZZ
 * Bumped MAX_MATCH from 2048 to 3072 to reflect the db having more users
 * than that registered for some hosts.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1132, 31-May-90 15:20:43, Edit by ZZZ
 * In get_hostname(), update the xfstat() call code in the fashion that it
 * was earlier in initialize().  (see update blurb for version 1127)
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1131, 26-Feb-90 12:18:32, Edit by ZZZ
 * Modified log_entry() to show invocation mode (WHOIS/HOST) in usage log.
 * Changed checking of TacReq to ValidCard for Milnet TAC user status in
 * individuals' display.  And made namesake hosts show up (where they exist)
 * in the list of hosts under a given subdomain.  (eg. SRI.COM will show up
 * now in listing of SRI.COM's hosts...)
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1129, 20-Feb-90 16:55:28, Edit by ZZZ
 * In d_imp(), display the Autodin information whenever it's available.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.NEWC.1128,  7-Feb-90 18:41:33, Edit by ZZZ
 * In d_individual(), a person is a MILNET TAC user if the first character
 * in the TACREQ field is a Y, not if the record merely has a TACID defined.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.NEWC.1127, 30-Jan-90 13:37:50, Edit by ZZZ
 * Changed initialize() code to reflect changes in xfstat() return values.
 * Added record-last-updated to output of all record types.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.NEWC.1126, 11-Dec-89 09:34:05, Edit by ZZZ
 * Changes to support conversion to Ansi C compiler where needed: Added
 * external function declarations, removed unreferenced local vars, and
 * added type-casting to parameters of procedure calls.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.NEWC.1122, 25-Sep-89 23:29:24, Edit by ZZZ
 * Change header for domain servers in network listing.  A header argument
 * (char *) was added to d_dsrv() to provide the calling function the
 * freedom to choose its own header string.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.NEWC.1121, 15-Sep-89 13:47:31, Edit by ZZZ
 * Changes to support listing of domain servers for network records:
 * In db_items[] - added i_inaddrserver record.
 * In d_domain() - moved hunk of code to new routine d_dsrv(), and added
 *                 call to d_dsrv() to list i_servercontact field.
 * In d_network() - added call to d_dsrv() to list i_inaddrserver field.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1118, 15-Aug-89 08:14:20, Edit by ZZZ
 * Changed "Alternate POC" title to "Alternate Contact", and added it
 * to the contacts list for D, N, and O records.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1117, 10-Aug-89 06:01:51, Edit by ZZZ
 * Modifications to support changed LISTEN invocation for server mode:
 * In log_entry() - stdin is now .priin, so get host info from stdout.
 * In initialize() - set F_SERVER flag when invoked as SWHOIS.
 * In serve() - loop and suspend, to support LISTEN's continuing of forks.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1116,  4-Aug-89 15:51:57, Edit by ZZZ
 * Added the AlternatePOC field to output for H, S, T, and W records.  For
 * this, added AlternatePOC to contacts-lists for hosts and imps.  Made
 * d_tac() refer to the imp_contacts[] list, rather than creating a
 * duplicate with the name tac_contacts[]...
 * In get_htype(): don't show INVISIBLE_HTYPES records even if X_OK.
 * 
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1115, 12-Jun-89 06:10:43, Edit by ZZZ
 * Modified f_tac() to call do_host() only if the search for the tac
 * fails.  Conditionalized the part of do_host() that automatically
 * performs a partial-match search when it's not requested.  Both of
 * these were causing unacceptably-long searches for simple requests.
 * Changed the no-subdomains message in d_s_domain() to be more useful.
 * 
 * [SRI-NIC]PS:<NICPROG.TEST>WHOIS.C.1114, 12-May-89 06:10:40, Edit by ZZZ
 * Changed "in front" to "before the name" in the see_more() help strings,
 * v3[] and v4[], to make it clearer just where the '*' or '%' should go.
 * Made sure all calls to see_more() include the flags for the original
 * match block: Those calls that used freshly-initialized blocks without
 * merging the original flags were changed to use the original block (omb).
 * This ensures that see_more(), as a prompting function, will always know
 * whether the user's original command was EXPAND or SUBDISPLAY...
 *
 * [SRI-NIC]PS:<NICPROG.TEST>WHOIS.C.1113,  7-May-89 02:10:55, Edit by ZZZ
 * Added function strip_trailing_white() to remove the whitespace from the
 * end of the command line.  The command parser did the job for all of the
 * arguments except the last.  It's now the first thing done in whois().
 *
 * [SRI-NIC]PS:<NICPROG.TEST>WHOIS.C.1111,  5-Apr-89 05:56:21, Edit by ZZZ
 * Fixed do_mailbox() to try to find the exact string it's given before
 * trying to canonicalize the hostname.  Only do the latter if the former
 * yields no results.  In canonicalize_host(), don't print any errors
 * when the hostname lookup yields no matches - they don't do any good.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1089, 30-Nov-88 20:24:30, Edit by IAN
 * Fixed do_mailbox() to handle "@ host" case with leading whitespace
 * before before hostname part.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1082, 25-Nov-88 13:28:43, Edit by IAN
 * massive assorted internal re-writes; field-only searches now allowed
 * in conjunction with type-only ("TAC !foo"); new ARPANET and MILNET
 * flags for those-only net matching; much more tabelization of stuff;
 * fewer globasls - now flag-driven from global_flags instead of discreet
 * bools.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1061, 17-Aug-88 21:21:52, Edit by IAN
 * check abort flag before yes/no'ing users; check it in type_file too;
 * various reworkings of the interrupt scheme.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1059, 15-Aug-88 22:57:01, Edit by IAN
 * display_matches() and summary() now abortable; no output on ^E if no csb
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1058, 14-Aug-88 09:21:14, Edit by IAN
 * Added ^G abort and ^E status interrupt characters
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1057, 12-Aug-88 22:19:16, Edit by IAN
 * Fixed up s_mailbox() and other mailbox stuff, which were broken.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1056, 12-Aug-88 21:26:26, Edit by IAN
 * Changed "No such host as "xxx" to Host "xxx" not recognized.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1055,  9-Aug-88 11:39:49, Edit by IAN
 * For sub_matches calls, make ask_p be TRUE, e.g. show the first few
 * then ask if they want to see the rest.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1054,  1-Aug-88 15:47:10, Edit by IAN
 * Have "WHOIS" turn on _WHOIS_F which means do WHOIS-style lookup:
 * anything goes, normal output.  temporarily overrides host_mode
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1053,  1-Aug-88 14:59:12, Edit by IAN
 * If there are less than AUTO_SHOW members/users/hosts-on, etc, just
 * show them; only ask if there are more than that.  see _FIND_ASKFUL_F
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1051, 29-Jul-88 13:55:22, Edit by IAN
 * Don't show any of the htypes in the INVISIBLE_HTYPES string unless x_ok
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1048, 12-Jul-88 16:44:59, Edit by IAN
 * No more "Program error, ..." bogosity.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1047, 24-Jun-88 16:04:44, Edit by SKAHN
 * Changed #include <strung.h> to #include <nic/strung.h>
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1046,  6-Jun-88 22:41:55, Edit by IAN
 * Allow trailing '*' in place of trailing '.', for people who do "foo*"
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1039,  4-May-88 17:21:03, Edit by SKAHN
 * Enclosed TOPS-20 dependencies in #if SYS_T20
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1032, 25-Feb-88 13:54:27, Edit by IAN
 * Added new global flag, "interactive", which means not-server and
 * not-automated-service, e.g. there's a real human there who can
 * answer questions.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1029, 25-Feb-88 12:53:04, Edit by IAN
 * When running as a server, just dump out the raw help file instead
 * of siccing the HELP program on it
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1022, 19-Feb-88 13:07:08, Edit by IAN
 * Changed save_idstr() to now always make a key composed of two
 * concatenated fields, the key-field + handle.  That way we get
 * automatic 2nd-level sorting when names are identical.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.1016, 17-Feb-88 20:02:35, Edit by IAN
 * Have do_mailbox() show you the hostname it canonicalized out of your
 * given host specification.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.946, 12-Feb-88 19:46:25, Edit by IAN
 * Make '%' command show subdisplay of display, instead of having
 * specialized users-on-host meaning.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.882, 25-Jan-88 21:02:15, Edit by IAN
 * Rewrote parse_command() and new_parse() to flexibly allow mixed
 * order of new and old commands: "host *foo" and "*host foo" both work.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.864, 18-Jan-88 20:44:09, Edit by IAN
 * Fix English: "No match for partial name foo" not "No partial match
 * for name foo" in matches().
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.855, 31-Dec-87 11:07:33, Edit by IAN
 * Add "bye" as synonym to quit/exit/etc.  WHOIS now takes all the same
 * exit keywords as the EXEC.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.854, 23-Dec-87 15:05:21, Edit by IAN
 * In do_mailbox(), for "foo" check for "foo%..." also.
 *
 * [SRI-NIC]PS:<NICPROG>WHOIS.C.835, 14-Dec-87 21:24:35, Edit by IAN
 * In do_name(), check for <letter><comma><letter>, e.g. "smith,john"
 * and, when checking name, try "smith, john" instead.  also in do_name,
 * when the input ends with <comma><space><letter>, assume it is the
 * tail end of a last name and initial, e.g. "smith, j"; do the search
 * as a partial in that case, to catch all last names with a first name
 * starting with that letter.
 */

/*
 *  WHOIS -- User DB lookup, HOST program, NICNAM server
 *
 *	This is the main DB lookup program for the external world.  It
 *	looks up records in the main registration database and displays
 *	them (with different displays for different record types).
 *
 *  WHOIS also embodies these variations:
 *
 *	HOST: HOST is like WHOIS except the default lookup type is "host",
 *	and it does a completely different type of host record output.  See
 *	d_athost().  WHOIS tells that it's HOST by checking argv[0].
 *
 *	NICNAM: This is a network-server version of WHOIS.  A listening
 *	process (LISTEN in this case) does the dirty work, and redirects
 *	primary I/O to the net.  Execution is mostly the same as for the
 *	user version, with these exceptions:
 *
 *	    there are no yes/no type questions asked of the user.
 *	    instead, the user gets a fixed message (see v3[] et
 *	    al in the verbiage section) saying how to always get
 *	    subdisplay output.
 *
 *	    it's a one-query, one-response protocol now.  i'd like
 *	    to change this.  perhaps if we provided a modified unix
 *	    client to take advantage of a new multiple query
 *	    protocol, then it could happen.  the suggested protocol
 *	    change is to have a trailing '-' on the input string
 *	    mean that more queries will follow, so stick around.
 *
 *	WHOIS tells it's a server by checking to see if primary I/O has been
 *	redirected to a TCP: device.  See initialize().
 *
 *	XWHOIS: XWHOIS is the same as WHOIS except that it uses the x-user
 *	database, defined as X_DB_FILE.
 *
 *  Note that you must load this program with C:LIBCKX in order to make it
 *  extended addressing.  It needs the extra memory.  If it doesn't have it,
 *  it will fail at low levels, often silently, the symptoms being that
 *  subdisplays (like registered users for a host) are simply missing.
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	source:			NICPROG:WHOIS.C
 *	required libraries:	C:LIBCKX.REL, C:LIBNIC.REL
 *	executable:		PUB:WHOIS.EXE
 *
 *	documentation:		NICPROG:WHOIS-7935.*
 *	help file:		see OPTIONS_FILE, WHOIS_HELP_FILE
 *				and HOST_HELP_FILE
 *
 *	author:			Ian Macky
 *	written:		August 86
 */

#include <c-env.h>			/* this C environment, SYS_xxx, etc */
#include <ctype.h>			/* toupper, isspace, etc */
#include <netdb.h>			/* network stuff 4 gethostbyaddr etc */
#include <sgtty.h>			/* tchars stuff for TTY interrupts */
#include <signal.h>			/* for doing TTY interrupts */
#include <stdio.h>			/* standard I/O package */
#include <string.h>			/* strchr, etc */
#include <time.h>			/* for dealing with realtime */
#include <varargs.h>			/* variable calling args */
#include <sys/types.h>			/* stat() needs this... */
#include <sys/stat.h>			/* use xstat() to know if TCP */
#include <sys/ioctl.h>			/* for using ioctl()s */
#include <nic/libnx.h>			/* local support routines */
#include <nic/idb.h>			/* IDB database package */
#include <nic/strung.h>			/* strCMP, etc */
#if SYS_T20
#define EXEC_PARSE_COMMAND_LINE		/* EXEC-style command-line parsing */
#include <urtsud.h>			/* is turned on by these 2 lines */
#include <frkxec.h>			/* for T20-specific forkexec() */
#include <jsys.h>			/* for JSYS names, flags */
#endif /* SYS_T20 */

#define AUTO_EXP                0	/* don't do automatic matches */

#define HOST_HTYPES		"HSWTP"
#define INVISIBLE_HTYPES	"L"
#if SYS_T20
#define QUERY_USER		"ANONYMOUS.NICGUEST"
#define SERVICE_USER		"OPERATOR"
#define DB_FILE			"DB-ID:ID.ACCESS"
#define HOST_HELP_FILE		"HLP:HOST.HLP"
#define OPTIONS_FILE		"NICPROG:WHOIS.OPTIONS"
#define SERVER_LOG_FILE		"USE-STATS:USE-STATS.TCP-NICNAM"
#define WHOIS_HELP_FILE		"HLP:WHOIS.HLP"
#define WHOIS_LOG_FILE		"USE-STATS:USE-STATS.NICNAM"
#define X_DB_FILE		"DB-ID:XI.ACCESS"
#define HELP_KEYWORD		"WHOIS"
#define HELP_PROGRAM		"SYS:HELP.EXE"
#else
#define QUERY_USER		"ANONYMOUS.NICGUEST"
#define SERVICE_USER		"OPERATOR"
#define DB_FILE			"/fs1/a/id/id"
#define OPTIONS_FILE		"/fs1/a/nicprog/whois.options"
#define SERVER_LOG_FILE		"/fs1/a/nicprog/whois.stats-server"
#define WHOIS_LOG_FILE		"/fs1/a/nicprog/whois.stats-local"
#define X_DB_FILE		"/fs1/a/id/xi"
#define HELP_KEYWORD		"WHOIS"
#define HOST_HELP_FILE		"HLP:HOST.HLP"	/* Ugh */
#define WHOIS_HELP_FILE		"HLP:WHOIS.HLP"	/* more ugh */
#define HELP_PROGRAM		"SYS:HELP.EXE"	/* Super ugh */
#endif

#define CONTROL(char)		(char ^ 0100)
#define ABORT_CHAR		CONTROL('G')
#define STATUS_CHAR		CONTROL('E')

#define MAX_MATCH	2048		/* max # of matches we can handle */
#define AVERAGE_KEY	64		/* assume this many chars of a key */
#define KEY_BUF_SIZE	(MAX_MATCH * AVERAGE_KEY)

#define MAX_LINE	512		/* size of a generic char buffer */
#define MAX_ADDR	128		/* max # of addresses for a host */

#define PAGE_LENGTH	24		/* these are the default page length */
#define LINE_WIDTH	80		/* and heights, if modes' are 0 */
#define ASK_AFTER	5		/* ask to continue after this many */
#define AUTO_SHOW	(ASK_AFTER * 2)	/* if this many or less, just show */
#define BREAK_EVERY	10		/* break summaries every n people */
#define MBX_COLUMN	33		/* for summary, mailbox starts here */
#define ADDR_COLUMN	33		/* for hosts on imp, addr start here */
#define SUB_DOM_COLUMN	21		/* sub-domain output name start here */

#define NET_ARPANET	10		/* network#s of these networks */
#define NET_MIT		18		/* MIT-TEMP-NET */
#define NET_MILNET	26		/* used by net_display() */

#define FALSE		0
#define TRUE		1

typedef	int		bool;		/* TRUE/FALSE only */

/*
 *	global flag (F) bits
 */

#define F_ABORT		    (1 << 0)	/* user wants to abort search? */
#define F_INT_ENABLED	    (1 << 1)	/* interrupts enabled? */
#define F_DB_LOADED	    (1 << 2)	/* db loaded & initialized yet? */
#define F_ONCE_THROUGH	    (1 << 3)	/* input from JCL?  if so, one pass. */
#define F_HOST_MODE	    (1 << 4)	/* running as the host program? */
#define F_X_WHOIS	    (1 << 5)	/* looking at DB of X stuff? */
#define F_NIC_USER	    (1 << 6)	/* are you?  if not, no frills. */
#define F_SERVER	    (1 << 7)	/* are you?  one query, one response */
#define F_INFERIOR	    (1 << 8)	/* running as inferior fork? */
#define F_X_OK		    (1 << 9)	/* allowed to see X records? */
#define F_INTERACTIVE	    (1 << 10)	/* interactive session?  user there? */
#define F_NLI		    (1 << 11)	/* Not Logged In user? */

/*
 *	global_flag-diddling macros
 */

#define SET(flag)		global_flags |= (flag)
#define CLEAR(flag)		global_flags &= ~(flag)
#define ON(flag)		(global_flags & (flag))
#define OFF(flag)		(!(global_flags & (flag)))

/*
 *	local flag (LF) bits
 */

#define LF_DUMP		    (1 << 0)	/* void-dump-style output */
#define LF_PARTIAL	    (1 << 1)	/* partial match on search */
#define LF_SUBSTRING	    (1 << 2)	/* substring match on search */
#define LF_NO_ABBREV	    (1 << 3)	/* keyword can't be abbreviated */
#define LF_NO_ARG	    (1 << 4)	/* keywords takes no arg */
#define LF_EXPAND	    (1 << 5)	/* do everything without asking */
#define LF_RECORDS_FULL	    (1 << 6)	/* out of room in match block */
#define LF_NO_SUB	    (1 << 7)	/* don't show subdisplay */
#define LF_FULL_OUTPUT	    (1 << 8)	/* no summary, full output on all */
#define LF_SUMMARY	    (1 << 9)	/* do a summary always. */
#define LF_FIND_FIRST	    (1 << 10)	/* return first match in find() */
#define LF_KEYS_FULL	    (1 << 11)	/* key buffer is full */
#define LF_SUBDISPLAY	    (1 << 12)	/* show subdisplay of display */
#define LF_MAILBOX_ONLY	    (1 << 13)	/* mailbox-only search */
#define LF_HANDLE_ONLY	    (1 << 14)	/* handle-only search */
#define LF_NAME_ONLY	    (1 << 15)	/* name-only search */
#define LF_COMMAND	    (1 << 16)	/* keyword is a WHOIS command */
#define LF_FIND_ASKFUL	    (1 << 17)	/* stop finding after ASK_AFTER */
#define LF_WHOIS	    (1 << 18)	/* do WHOIS-style output */
#define LF_ARPANET	    (1 << 19)	/* only want ARPANET hosts */
#define LF_MILNET	    (1 << 20)	/* only want MILNET hosts */

#define LF_BARE_COMMAND	    (LF_COMMAND | LF_NO_ABBREV | LF_NO_ARG)
#define LF_ONLY_FIELDS	    (LF_HANDLE_ONLY | LF_NAME_ONLY | LF_MAILBOX_ONLY)

/*
 *	this is the structure for storing search matches in.  we keep one
 *	lying around globally since that way there's no chance of not
 *	being able to allocate it (with get_match_block()), plus we want
 *	the parse flags kept in the flags word to be globally available.
 */

typedef struct MATCH_BLOCK {
    unsigned flags;			/* flag word */
    int cmd_index;			/* index into commands[] of cmd */
    int searches;			/* # of searches into this mb */
    char *htypes;			/* pointer to valid htypes or NULL */
    idfnd_t fndblk;			/* find-block for searching */
    char *target;			/* target string */
    iditm_t key_field;			/* field to use for sort key */
    int count;				/* number of matches */
    int records[MAX_MATCH];		/* offset into next two arrays */
    idrix_t recidx[MAX_MATCH];		/* record indicies of matches */
    char *keys[MAX_MATCH];		/* pointers to saved names */
    char *key_cp;			/* pointer into name_buf */
    int key_count;			/* # chars saved in key_buffer */
    char key_buffer[AVERAGE_KEY * MAX_MATCH];
} match_block;

/*
 *	database field number stuff.  i_foo gets the database's internal
 *	field# for field "foo".  load_database() goes over this array
 *	and looks up the field#s given the names and fills them into
 *	the i_foo locations.  i_handle is special since it is guaranteed
 *	to be 0.
 */

#if SYS_T20	/* Disambiguate variable names (1st 6 chars) on T20 */
#define i_alternatepoc	i_altpoc
#define i_hostname	i_hname
#define i_impname	i_iname
#define i_impnetnumber	i_inet
#define i_impnumber	i_inumber
#define i_netname	i_nname
#define i_netnumber	i_nnumber
#endif

#define i_handle		0		/* handle is ALWAYS item #0 */

iditm_t	i_address, i_admincontact, i_alternatepoc, i_autodin, i_comments,
	i_connectype, i_coordinator, i_cputype, i_domainame, i_groups,
	i_hadmin, i_host, i_hostname, i_htype, i_impname, i_impnetnumber,
	i_impnumber, i_inaddrserver, i_mailbox, i_name, i_netaddress,
	i_netname, i_netnumber, i_nicknames, i_opsys, i_parentdom, i_phone,
	i_protocols, i_servercontact, i_tacid, i_tacnumber, i_techcontact,
	i_updated, i_user, i_validcard, i_zonecontact;

struct item_record {
    iditm_t *item;			/* addr of iditm_t containing field# */
    char *name;				/* textual name of field, for lookup */
} db_items[] = {
    &i_address, "address",		&i_admincontact, "admincontact",
    &i_alternatepoc, "alternatepoc",	&i_autodin, "autodin",
    &i_comments, "comments",		&i_connectype, "connectype",
    &i_coordinator, "coordinator",
    &i_cputype, "cputype",		&i_domainame, "domainame",
    &i_groups, "groups",		&i_hadmin, "hostadmin",
    &i_host, "host",			&i_hostname, "hostname",
    &i_htype, "htype",			&i_impname, "impname",
    &i_impnetnumber, "impnetnumber",	&i_impnumber, "impnumber",
    &i_inaddrserver, "inaddrserver",
    &i_mailbox, "mailbox",		&i_name, "name",
    &i_netaddress, "netaddress",	&i_netname, "netname",
    &i_netnumber, "netnumber",		&i_nicknames, "nicknames",
    &i_opsys, "opsys",			&i_parentdom, "parentdom",
    &i_phone, "phone",			&i_protocols, "protocols",
    &i_servercontact, "servercontact",	&i_tacid, "tacid",
    &i_tacnumber, "tacnumber",		&i_techcontact, "techcontact",
    &i_updated, "updated",		&i_user, "user",
    &i_validcard, "validcard",		&i_zonecontact, "zonecontact" 
};

/*
 *	structure for displaying contacts for a record.  since the
 *	same person might fill more than one position, a structure with
 *	all the ident-containing-fields we want displayed is given to
 *	d_contacts(), which does the right thing, display-wise.
 *
 *	the record and handle members are set by d_contacts(), and can
 *	be initialized to anything.  if the label is NULL, the database's
 *	label for that field is used.
 */

struct contact_struct {
    iditm_t *field;			/* field# that ID is kept in */
    char *label;			/* label to be used */
    idrix_t record;			/* db record# */
    char *handle;			/* cp to handle */
};

struct contact_struct host_contacts[] = {
    &i_hadmin, "Host Administrator", 0, NULL,
    &i_coordinator, "Coordinator", 0, NULL,
    &i_alternatepoc, "Alternate Contact", 0, NULL
};

#define N_HOST_CONTACTS	(sizeof(host_contacts)/sizeof(struct contact_struct))

struct contact_struct imp_contacts[] = {
    &i_coordinator, "Coordinator", 0, NULL,
    &i_alternatepoc, "Alternate Contact", 0, NULL
};

#define N_IMP_CONTACTS (sizeof(imp_contacts)/sizeof(struct contact_struct))

struct contact_struct domain_contacts[] = {
    &i_admincontact, "Administrative Contact", 0, NULL,
    &i_techcontact, "Technical Contact", 0, NULL,
    &i_zonecontact, "Zone Contact", 0, NULL,
    &i_alternatepoc, "Alternate Contact", 0, NULL
};

#define N_DOMAIN_CONTACTS (sizeof(domain_contacts)/sizeof(struct contact_struct))

struct contact_struct coordinator[] = {
    &i_coordinator, NULL, 0, NULL,
    &i_alternatepoc, "Alternate Contact", 0, NULL
};

#define N_COORDINATOR	(sizeof(coordinator)/sizeof(struct contact_struct))

/*
 *	procedure declarations.  internal declarations are in the
 *	order in which the routines occur in this source.
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *                            externals
 */
extern int	atoi();		/* alpha-to-int converter */
extern int	exit();		/* terminate a process */
extern int	forkexec();	/* spawns inferior exec fork */
extern int	free();		/* de-allocate memory blocks */
extern int	getpid();	/* get process identification */
extern int	getpw();	/* get name from uid */
extern int	getuid();	/* return userid (e.g. user#) */
extern int	ioctl();	/* device control */
extern int	kill();		/* send a signal to a process */
extern char    *malloc();	/* ubiquitous, eh? */
extern int	qsort();	/* quicker sort */
extern int	write();	/* write output */
extern int	xfstat();	/* get extended status (huh?) */
/*
                          top level routines
*/
char *initialize();			/* do 1-time startup initialization */
bool serve();				/* do work as server */
bool child();				/* run as inferior fork */
bool whois();				/* do an actual lookup of a string */
bool suspend();				/* suspend pgm, return to superior */
/*
                     command parser plus support
*/
int parse_command();			/* parse command, ret cmds[] index */
char *get_word();			/* extract a "word" from string */
bool do_partial();			/* handle ... trailing partial */
char *do_magic();			/* old (magic character) parser */
void strip_trailing_white();		/* strip spaces from end of string */
int s_tbluk();				/* look up string in a command_table */
int c_tbluk();				/* look up a char in a command_table */
/*
                              help stuff
*/
bool help();				/* routine to handle "help" command */
bool options();				/* likewise for users' "?" */
bool type_file();			/* type out the given file */
bool run_program();			/* run a program given filespec&jcl */
/*
                      high level finder routines
*/
bool f_anything();			/* finder for generic lookup */
bool f_usual();				/* check the "usual" (handle, name) */
bool f_imp();				/* find an "imp" */
bool f_domain();			/* find a "domain" */
bool do_host();				/* factored out worker for hosts */
bool do_name();				/* factored out worked for names */
bool f_network();			/* find a "network" */
bool f_org();				/* find an "organization" */
bool f_tac();				/* find a "tac" */
bool do_mailbox();			/* factored out worker for mailbox */
void find_mailbox();			/* do_mailbox() subroutine */
bool s_mailbox();			/* find a suffix-mailbox */
/*
                      low-level finder routines
*/
idrix_t find();				/* the real core finding routine */
idrix_t find_temp();			/* find() with temp flags/target */
int find_big();				/* find() for large sets */
/*
                   record- and key-saving routines
*/
bool save_match();			/* save record/key in match block */
bool save_record();			/* save record keyed by name/handle */
char *save_idstrs();			/* save an idstr_t or C string */
char *save_string();			/* in permanent key-string buffer */
/*
                     high-level display routines
*/
bool display_matches();			/* display the results of a search */
void no_matches();			/* emits the "No matches..." line */
bool sub_matches();			/* like the above, for sub-matches */
void out_record();			/* display the given record */
void dump_record();			/* dump complete record */
bool summary();				/* summarize multiple matches */
int sum_line();				/* standard summary-line routine */
void s_rest();				/* do variable part of sum_line() */
void contact_summary();			/* special output for a contact */
void d_contacts();			/* multiple-contact typeout */
/*
                   per-record-type display routines
*/
void d_individual();			/* display a person's record */
void d_machine();			/* display some sort of machine */
void d_host();				/* display a host */
void d_s_host();			/* host subdisplay */
void d_hinfo();				/* display standard info for host */
void d_imp();				/* display an imp */
int out_netname();			/* support routine: print netname */
void d_domain();			/* show a domain & hosts on it */
void d_dsrv();				/* domain-servers display */
void d_s_domain();			/* domain subdisplay */
int sub_domain();			/* summary routine for hosts on dom */
void d_network();			/* show network and hosts on it */
void d_s_network();			/* network subdisplay */
int host_line();			/* summary routine for hosts on net */
void d_org();				/* display an organization */
void d_s_org();				/* organization subdisplay */
void d_tac();				/* display a tac */
void d_updated();			/* display last-updated field */
/*
                    special @HOST output routines
*/
void d_athost();			/* do @HOST-program-style d_host() */
void net_display();			/* address typout used by athost */
/*
                     middle-level output routines
*/
int name_and_handle();			/* output "name (handle)" */
void standard_header();			/* name + handle + address header */
void comments();			/* display comments if any */
int label();				/* output name of field */
void d_labeled();			/* output label then contents */
void d_indented();			/* display multi-line field indented */
/*
                      low-level output routines
*/
void putz();				/* Output item specified by item # */
bool yesno();				/* ask a yes-or-no question, int off */
void space();				/* space/tab over n spaces */
/*
                      low-level database support
*/
void load_database();			/* load and initialize database */
bool get_record();			/* make the given record current */
bool get_item();			/* get item from current record */
int get_htype();			/* verify and return htype from cur */
bool right_net();			/* checks if correct network */
bool x_record();			/* is this an X-record? */
char *id2c();				/* turn idstr_t into C string */
/*
                     log-file stuff, plus support
*/
void log_entry();			/* make a log-file entry */
void timestamp();			/* emit a timestamp to a stream */
void get_hostname();			/* get host on other side of stream */
void get_job_info();			/* do what it says */
/*
                        miscellaneous support
*/
bool canonicalize_host();		/* canonicalize hostname into buf */
void match_initialize();		/* initialize the match block */
void search_initialize();		/* initialize a fndblk */
int net_part();				/* extract net portion of A.B.C.D */
bool see_more();			/* factored out want-to-see-more? */
void sort_matches();			/* run qsort over a match block */
int qcmp();				/* qsort/strCMP/structure interface */
match_block *get_match_block();		/* allocate a match_block */
void free_match_block();		/* release an allocated match_block */
char *get_jcl();			/* read RSCAN JCL into buffer */
/*
                           interrupt stuff
*/
void int_clear();			/* reset interrupt flags */
void int_on();				/* turn on interrupts */
void int_off();				/* turn off interrupts */
void int_abort();			/* int: user wants to abort search */
void int_status();			/* int: user wants to see stats */

/*
 *	command table declarations.  the first table entry is for when no
 *	keywords have been specified at all -- the default action.
 */

struct command_table {
    char *name;					/* keyword */
    char magic;					/* equivalent magic char */
    char *htypes;				/* valid htypes */
    int (*function)();				/* function it calls */
    unsigned flags;				/* flags it sets */
} commands[] = {
    NULL,	    0,	    NULL,	  f_anything,	LF_NO_ARG,
    "arpanet",	    0,	    HOST_HTYPES,  NULL,		LF_ARPANET,
    "bye",	    0,	    NULL,	  suspend,	LF_BARE_COMMAND,
    "domain",	    0,	    "D",	  f_domain,	0,
    "dump",	    '#',    NULL,	  NULL,		LF_DUMP,
    "exit",	    0,	    NULL,	  suspend,	LF_BARE_COMMAND,
    "expand",	    '*',    NULL,	  NULL,		LF_EXPAND,
    "full",	    '=',    NULL,	  NULL,		LF_FULL_OUTPUT,
    "gateway",	    0,	    "W",	  do_host,	0,
    "group",	    0,	    "GO",	  f_org,	0,
    "handle",	    '!',    NULL,	  NULL,		LF_HANDLE_ONLY,
    "help",	    0,	    NULL,	  help,		LF_BARE_COMMAND,
    "host",	    0,	    HOST_HTYPES,  do_host,	0,
/* --- This one catches "who is foo" and strips leading whitespace ------- */
    "is",	    ' ',    NULL,	  NULL,		0,
    "IMP",	    0,	    "S",	  f_imp,	0,
    "mailbox",	    0,	    NULL,	  NULL,		LF_MAILBOX_ONLY,
    "milnet",	    0,	    HOST_HTYPES,  NULL,		LF_MILNET,
    "name",	    '.',    NULL,	  NULL,		LF_NAME_ONLY,
    NULL,	    '~',    NULL,	  NULL,		LF_NO_SUB,
    "network",	    0,	    "N",	  f_network, 	0,
    "options",	    '?',    NULL,	  options,	LF_BARE_COMMAND,
    "organization", 0,	    "GO",	  f_org,	0,
    "partial",	    0,	    NULL,	  NULL,		LF_PARTIAL,
    "person",	    0,	    "I",	  NULL,		0,
    "PSN",	    0,	    "S",	  f_imp,	0,
    "q",	    0,	    NULL,	  suspend,	LF_BARE_COMMAND,
    "quit",	    0,	    NULL,	  suspend,	LF_BARE_COMMAND,
/* --- This is for dummies who type "RETURN" to exit --------------------- */
    "return",	    0,	    NULL,	  suspend,	LF_BARE_COMMAND,
    "subdisplay",   '%',    NULL,	  NULL,		LF_SUBDISPLAY,
    "summary",	    '$',    NULL,	  NULL,		LF_SUMMARY,
    "TAC",	    0,	    "T",	  f_tac,	0,
    "whois",	    0,	    NULL,	  NULL,		LF_WHOIS
};

#define N_COMMANDS	(sizeof(commands) / sizeof(struct command_table))

/*
 *	HTYPE tables
 */

struct htype_struct {
    char htype;				/* htypes in this class */
    char *name;				/* name of this record type */
    iditm_t *item1;			/* first item for summary line */
    iditm_t *item2;			/* second item for summary line */
    void (*main)();			/* main display procedure */
    void (*sub)();			/* subdisplay, if any */
} htypes[] = {
    'D', "domain",	 NULL,	     &i_domainame,    d_domain,     d_s_domain,
    'G', "group",	 &i_mailbox,  &i_phone,	     d_org,	   d_s_org,
    'H', "host",	 &i_hostname, &i_netaddress,   d_host,	   d_s_host,
    'I', "person",	 &i_mailbox,  &i_phone,	     d_individual, NULL,
    'N', "network",	 &i_netname,  &i_netnumber,    d_network,    d_s_network,
    'O', "organization", &i_mailbox,  &i_phone,	     d_org,	   d_s_org,
    'P', "TEP",		 &i_hostname, &i_netaddress,   d_host,	   d_s_host,
    'S', "PSN",		 &i_impnumber,&i_impnetnumber, d_imp,	   NULL,
    'T', "TAC",		 NULL,	     &i_phone,	     d_tac,	   NULL,
    'W', "gateway",	 &i_hostname, &i_netaddress,   d_host,	   d_s_host,
};

#define N_HTYPES	(sizeof(htypes) / sizeof(struct htype_struct))

/*
 *	globals
 */

unsigned global_flags;			/* global flags */

int width = LINE_WIDTH;			/* TTY line width */
int height = PAGE_LENGTH;		/* page height */
int column;				/* output column counter */
char *run_mode;				/* for differentiated logging */

/*
 *	the following are necessary to pass information to the sorting
 *	routine called by qsort in sort_matches(), and for displaying
 *	current match_block stats in int_status().
 */

match_block *cmb;			/* pointer to in-use match_block */
match_block *sorting_mb;		/* pointer to sorting-mb */

struct version w_version = {
    3, 5, 1090, 1			/* major, minor, edit, who */
};

/*
 *	verbiage
 */

char indent[] = "   ";			/* indent sub-text this much */
#define INDENT	(sizeof(indent) - 1)	/* length less trailing null */

char divider[] = "\n----------\n\n";	/* summary divider */
char more[] = "--- More? ";		/* want to see more? */

char w_prompt[] = "Whois: ";
char h_prompt[] = "Host: ";

char w_intro[] = "\
  Enter a handle, name, mailbox, or other field, optionally preceded\n\
  by a keyword, like \"host sri-nic\".  Type \"?\" for short, 2-page\n\
  details, \"HELP\" for full documentation, or hit RETURN to exit.\n\
---> Do ^E to show search progress, ^G to abort a search or output <---";

char h_intro[] = "\
  Enter a handle, name, hostname, address, or other field, \"?\" for a\n\
  short options list, \"HELP\" for full help, \"WHOIS xxx\" for WHOIS-type\n\
  output, or hit RETURN to exit.";

char single_out[] = "\n\
To single out one record, look it up with \"!xxx\", where xxx is the\n\
handle, shown in parenthesis following the name, which comes first.";

char v3[] = "\n\
To see this host record with registered users, repeat the command with\n\
a star ('*') before the name; or, use '%' to show JUST the registered users.";

char v4[] = "\n\
To see this organization record with registered members, repeat the command\n\
with a star ('*') before the name; or, use '%' to show JUST the registered\n\
members.";

char *months[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

/*
 *	top level routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	optionally given an arg, look up the givetn string.  if we're a
 *	server (determined by initialize()) then do one-query one-response.
 *	else for a user, if they gave us the string in JCL just look it up
 *	and stop, otherwise type an intro prompt and loop asking for input,
 *	punting when they hit <CR> or give generic QUIT command.
 */

int main(argc, argv)
int argc;
char **argv;
{
    char *arg, *target, *intro, *prompt, buf[MAX_LINE];

    arg = initialize(argc, argv);	/* do 1-time initialization */

    if (ON(F_SERVER))			/* if running as a server */
	return serve();			/* then do the work & done. */
    if (ON(F_INFERIOR))			/* if running as inferior */
	return child();			/* play the role of child. */
    if (ON(F_HOST_MODE)) {
	intro = h_intro;
	prompt = h_prompt;		/* set up the verbiage pointers */
    } else {				/* depending on what mode we're */
	intro = w_intro;		/* running in. */
	prompt = w_prompt;
    }
/*
 *	loop; if no arg yet, ask for it.  to exit, we do our own HALTF%
 *	and then continue the for (;;) loop, since the C runtime itself
 *	isn't continuable.
 */
    for (;;) {					/* loop forever */
	if (arg) {
	    whois(arg);				/* if there's an arg, whois */
	    if (ON(F_NLI)) putchar('\n');
	}
	if (!arg || ON(F_NLI)) {		/* no arg, so prompt & get */
	    nx_nicbanner("WHOIS", &w_version);	/* announce ourself */
	    if (OFF(F_NIC_USER))		/* NIC staff already know */
		puts(intro);			/* how to use WHOIS. */
	    while ((target = getline(buf, MAX_LINE, prompt)) && *target)
		whois(target);			/* loop doing lookups */
	}
	fflush(stdout);				/* make sure all gets out. */
	suspend();				/* halt now, continuable. */
	arg = get_jcl(buf);			/* see if there's new JCL */
    }
}

/*
 *	do one-time initialization.
 */

char *initialize(argc, argv)
int argc;
char **argv;
{
    int n, user_no, ablock[5];
    bool service_user;
    struct tchars tch;
    char *arg;				/* user's JCL-line arg */
    char user[100];

    global_flags = 0;			/* reset all global flags */
    user_no = getuid();			/* our user number */
    user[0] = 0;
    getpw(user_no, user);		/* Find user name */
#if SYS_T20
    if (!user_no)
	SET(F_NLI);			/* if 0, Not Logged In. */
#endif
    service_user = (strCMP(user,SERVICE_USER)==0);	/* SERVICE user? */
    if (user_no && (strCMP(user,QUERY_USER)!=0) && !service_user)
	SET(F_NIC_USER);
#if SYS_T20
    {
    struct xstat xs;
    unsigned int dev;

    /* xfstat() calls DVCHR, so LH(st_dev) has input device type. */
    /*   If it's a TCP device, we're running in server mode. */
    if (!xfstat(fileno(stdin),&xs)) {
        dev = ((unsigned int) xs.st.st_dev) >> 18;
	if (dev == (0600000 + ST_DEV_TCP))	/* 600000 is 20X device code */
	    SET(F_SERVER);
	}
    }
#endif
    if (ON(F_NIC_USER) && OFF(F_SERVER))	/* allowed to see X records? */
	SET(F_X_OK);
    run_mode = "WHOIS";			/* default run mode */
    if (argc > 0) {				/* command name's 1st in JCL */
	if (!strCMP(argv[0], "HOST")) {		/* host program is one of us */
	    SET(F_HOST_MODE);
	    run_mode = "HOST";
	}
	else if (ON(F_X_OK) && !strCMP(argv[0], "XWHOIS")) {
	    SET(F_X_WHOIS);			/* XWHOIS looks at X DB */
	    run_mode = "XWHOIS";
	}
	else if (!strCMP(argv[0], "INFERIOR"))
	    SET(F_INFERIOR);			/* we're to run as inferior */
    }
    if (OFF(F_SERVER | F_INFERIOR) && !service_user)
	SET(F_INTERACTIVE);
    if (argc > 1) {				/* if runing from JCL, the */
	arg = argv[1];				/*   arg is the one and only */
	if (ON(F_NIC_USER))
	    SET(F_ONCE_THROUGH);		/*   argv arg, ala urtsud. */
    } else
	arg = NULL;				/* else no input yet; so ask */
#if SYS_T20
    if (ON(F_INTERACTIVE)) {
	ablock[1] = _PRIOU;			/* where output goes */
	if (jsys(RFMOD, ablock)) {		/* get mode word */
	    if (n = (ablock[2] & TT_WID) >> TT_WID_S)
		width = n;			/* get width from mode word */
	    if (n = (ablock[2] & TT_LEN) >> TT_LEN_S)
		height = n;			/* get page length */
	}
    }
#endif
    if (ON(F_INTERACTIVE)) {
	ioctl(0, TIOCGETC, &tch);	/* get current stuff */
	tch.t_quitc = ABORT_CHAR;	/* make quit char ^G */
	tch.t_intrc = STATUS_CHAR;	/* make int char ^E */
	ioctl(0, TIOCSETC, &tch);	/* set it back now */
	signal(SIGQUIT, int_abort);	/* go here on abort */
	signal(SIGINT, int_status);	/* here for status */
    }
    return arg;				/* return arg or NULL if none */
}

/*
 *	when running as a server, get a single query and return a single
 *	response, then done.  a blank line from the user returns the
 *	help file.
 */

bool serve()
{
    char *target, buf[MAX_LINE];

    setlinebuf(stdin);				/* line-at-a-time input, pls */
    setvbuf(stdout, NULL, _IOFBF, 0);		/* full buffering on output */
    target = getline(buf, MAX_LINE, 0);		/* get the arg line */
    return whois(target);
}

/*
 *	handle running as inferior fork.  we get input from pipe, line at
 *	a time, and send out output to stdout like normal.  after each
 *	lookup we return control to superior by stopping (SIGTSTP), and
 *	are continued when needed again with a SIGCONT.
 */

bool child()
{
    char *target, buf[MAX_LINE];
    static int our_pid = 0;
    bool result = FALSE;

    setlinebuf(stdin);			/* line-at-a-time from pipe. */
    if (!our_pid)			/* if don't known our own */
	our_pid = getpid();		/* process ID, figure it now. */
    while (target = getline(buf, MAX_LINE, 0)) {    /* while there's input */
	result = whois(target);		/* do the lookup then */
	kill(our_pid, SIGTSTP);		/* halt back to superior. */
    }					/* do it again when continued */
    return result;			/* return final result. */
}

/*
 *	Suspend program and return to superior.  Permits continuation.
 */
bool
suspend()
{
    nx_halt();
}

/*
 *	given a string, do the lookup.  returns truth value.
 */

bool whois(s)
char *s;
{
    char *arg, *htypes;
    bool result = FALSE;
    match_block mb;
    unsigned flags;
    int cmd;

    strip_trailing_white(s);			/* make sure it's usable */
    log_entry(s);				/* make a log entry */
    if (!*s && ON(F_SERVER))			/* a blank line to the */
	return help();				/* server gives help */
    cmd = parse_command(s, &arg, &flags);	/* cmd=0 for default action */
    if (flags & LF_COMMAND)			/* if it's a command, */
	result = ((*commands[cmd].function)());	/* just do it */
    else if (commands[cmd].function || (flags & LF_ONLY_FIELDS)) {
	if (!*arg) {
	    puts("Sorry, you can't search for the null string.");
	    return FALSE;
	}
	if (OFF(F_DB_LOADED))			/* if it hasn't been loaded */
	    load_database();			/* yet, load up the db now. */
/* --- Unless overriden, host-mode and ARPA/MIL modes are for hosts ----- */
	if (!(htypes = commands[cmd].htypes))
	    if (ON(F_HOST_MODE) || (flags & (LF_ARPANET | LF_MILNET)))
		htypes = HOST_HTYPES;
	match_initialize(&mb, arg, i_name, flags, htypes);
	mb.cmd_index = cmd;			/* command used in search */
	int_on();				/* turn on interrupts */
	f_usual(&mb);				/* first try the usual */
/* --- Only not field-only and has additional finder, use it ------------- */
	if (!(flags & LF_ONLY_FIELDS))
	    (*commands[cmd].function)(&mb);
	int_off();
	cmb = NULL;				/* not searching anymore */
	display_matches(&mb, sum_line, TRUE, FALSE);	/* display results! */
	result = (mb.count > 0);		/* won if found anything */
    } else					/* do-nothing keyword???? */
	printf("OK, %s!\n", commands[cmd].name);
    return result;
}

/*
 *	command parser plus support
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	command parser.
 */

int parse_command(s, rest, flags)
char *s, **rest;
unsigned *flags;
{
    char *cp, *next, word[MAX_LINE];
    int i, cmd = 0;
    int (*function)() = NULL;

    *flags = 0;					/* initialize flag word */
    if (do_partial(cp = s))			/* handle ... partial thing */
	*flags |= LF_PARTIAL;			/* were dots, so set flag */
    if (strchr(cp, '@'))			/* if contains an at */
	*flags |= LF_MAILBOX_ONLY;		/* then a mbx-only search */
    do {
	if (!(cp = do_magic(cp, &cmd, &function, flags)))
	    break;				/* handle/strip magic chars */
	next = get_word(cp, word);		/* then isolate this word */
	if (((i = s_tbluk(word)) < 0) ||	/* if not a keyword or */
	    (!(commands[i].flags & LF_NO_ARG) && !*next))    /* arg-starved */
	    break;				/* then do default action */
	if (commands[i].function) {		/* run a function? if so, */
	    if (function)			/* and already have one, */
 		break;				/* then must be text. */
	    function = commands[cmd = i].function;	/* this is the funct */
	}
	*flags |= commands[i].flags;		/* set flags for keyword */
    } while (i && (cp = next));
    *rest = cp;					/* set users pointer to it */
    return cmd;
}

/*
 *	given a string s, strip off any trailing partial-match characters,
 *	e.g. dots.  returns TRUE if found any, else FALSE;  allow '*' in
 *	place of '.' for people who do "foo*"
 */

bool do_partial(s)
char *s;
{
    int i, c;
    char *cp;

    cp = s + (i = strlen(s)) - 1;		/* point to last char */
    if ((c = *cp) == '.' || c == '*') {		/* trailing dot means */
	while (--i > 0 && *--cp == c) ;		/* step back over 'em all */
	if (i > 0) {				/* if "foo..." then */
	    *++cp = '\0';			/* remove the dots and */
	    return TRUE;			/* flag partial match */
	}
    }
    return FALSE;
}

/*
 *	Old-style parse wants command of the form "<char><char>text"
 *	where char is "!" for handle-only, "#" for dump record, etc.
 */

char *do_magic(s, cmd, routine, flags)
char *s;
int *cmd, *flags;
int (**routine)();
{
    char *cp;
    int c, i;

    for (cp = s; (c = *cp) && (i = c_tbluk(c)) >= 0; cp++) {
	if (commands[i].function) {		/* if a function char but */
	    if (*routine) break;		/* already have function!! */
	    *routine = commands[*cmd = i].function;	/* type char */
	}
	*flags |= commands[i].flags;		/* maybe set some flags */
    }
    return cp;
}

/*
 *	given a cp to a string, write a copy of the next "word" to the given
 *	buffer, skipping leading whitespace.  a "word" is some positive
 *	number of contiguous non-whitespace characters.  next_cp is set
 *	to point to after the word, and the function returns TRUE if
 *	a word was found, else FALSE (with the dest buffer unchanged).
 */

char *get_word(cp, buf)
char *cp, *buf;
{
    char *original_buf = buf;

    while (isspace(*cp)) cp++;			/* skip leading whitespace */
    while (*cp && !isspace(*cp))		/* copy "word" to dest */
	*buf++ = *cp++;				/* buffer... */
    if (buf != original_buf) {			/* if found&wrote any chars */
	*buf = '\0';				/* then tie off dest */
	while (isspace(*cp)) cp++;		/* remove trailing white */
	return cp;				/* pt rest of string & win */
    } else
	return NULL;				/* else take lose return */
}

/*
 *	strip_trailing_white(s)
 *
 *	given a string pointer, make sure there are no whitespace characters
 *	immediately behind the terminating null.
 *
 */
void strip_trailing_white(s)
char *s;
{
  int i;
  char c;

  for (i = (strlen(s) - 1); i >= 0; i--)
    if (((c = s[i]) != ' ') && (c != '\t'))
      break;
  s[++i] = '\0';
 }

/*
 *	s(tring)_tbluk
 *
 *	given a keyword and pointer to a command table and the size of that
 *	table, see if the keyword is in that table.  checks all keywords in
 *	the table to test for multiple matches (ambiguity); for a single match
 *	returns the index into that command table, 0 for no match, -1 for
 *	ambiguous.
 *
 *	TBLUK% is the TOPS-20 JSYS which does this sort of table-lookup.
 */

int s_tbluk(key)
char *key;
{
    int i, length, cmd = 0;			/* 0+1 is default action */
    char *cp;

    length = strlen(key);
    for (cmd = i = 0; i < N_COMMANDS; i++) {
	if (!(cp = commands[i].name))		/* point to command */
	    continue;				/* might not be one, tho */
	if (!strnCMP(key, cp, length)) {	/* case insensitive compare */
	    if ((commands[i].flags & LF_NO_ABBREV) && length < strlen(cp))
		continue;			/* can't be abbreviated */
	    if (cmd) return -1;			/* ambiguous, multiple match */
	    else cmd = i;			/* save cmd */
	}
    }
    return cmd;
}

/*
 *	c(har)_tbluk
 *
 *	like s_tbluk, but looks for a single-char match with the magic
 *	char section of a command table.
 */

int c_tbluk(c)
char c;
{
    int i;

    for (i = 0; i < N_COMMANDS; i++)
	if (c == commands[i].magic)
	    return i;			/* 1-based, not 0-based */
    return -1;
}

/*
 *	help file stuff plus support
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	type out a mini command summary
 */

bool options(mb)
match_block *mb;
{
    if (!type_file(OPTIONS_FILE, mb->flags)) {
	printf("Sorry, can't find options file %s\n", OPTIONS_FILE);
	puts("Try the help file perhaps?  Do \"HELP\"");
	return FALSE;
    }
    return TRUE;
}

/*
 *	type out the help file
 */

bool help(mb)
match_block *mb;
{
    char *help_file = NULL;

    if (ON(F_HOST_MODE))
	help_file = HOST_HELP_FILE;
    else if (OFF(F_INTERACTIVE))
	help_file = WHOIS_HELP_FILE;

    if (help_file) {
	if (!type_file(help_file, mb->flags)) {
	    printf("Sorry, can't find help file %s -- %s\n",
		help_file, strerror(-1));
	    return FALSE;
	}
    } else if (!run_program(HELP_PROGRAM, HELP_KEYWORD)) {
	printf("Sorry, couldn't run help program %s\n", HELP_PROGRAM);
	return FALSE;
    }
    return TRUE;
}

/*
 *	given the filespec of an .EXE file to load and run, and the text
 *	which is to be argv[1] (where arvg[0] gets the exe filespec),
 *	runs the program.  returns TRUE/FALSE for success.
 */

bool run_program(prog, text)
char *prog, *text;
{
#if SYS_T20
    struct frkxec frkb;
    char rscan_buf[MAX_LINE];

    frkb.fx_name = prog;		/* filespec of .exe */
    frkb.fx_flags = FX_WAIT | FX_T20_RSCAN; /* wait for fork to terminate */
    sprintf(frkb.fx_blkadr = rscan_buf, "HELP %s\n", text);
    frkb.fx_envp = NULL;		/* environment vector */
    frkb.fx_fdin = frkb.fx_fdout = -1;	/* no new stdin out stdout */
    return (forkexec(&frkb) == -1) ? FALSE : TRUE;
#else
    return FALSE;
#endif
}

/*
 *	type out the given file, returning truth as to success
 */

bool type_file(file, flags)
char *file;
unsigned flags;
{
    FILE *f;
    char line[MAX_LINE];
    int c, line_count;

    if (!(f = fopen(file, "r")))		/* open file to be typed */
	return FALSE;				/* oops, can't... */
    if (OFF(F_INTERACTIVE) || (flags & LF_EXPAND)) /* if you said *help or */
	while ((c = getc(f)) != EOF)		/* we're a server, then */
	    putchar(c);				/* just blast it all out! */
    else {					/* else do it with -more-. */
	for (line_count = 0; OFF(F_ABORT) && fgets(line, MAX_LINE, f);) {
	    if (height && ++line_count == height - 1) {
		if (ON(F_ABORT) || !yesno(more))
		    break;			/* aborting or no more, pls */
		line_count = 1;			/* new screen, first line */
	    }
	    fputs(line, stdout);		/* blast out the line */
	}					/* while there is input */
    }
    fclose(f);					/* all done with file */
    return TRUE;
}

/*
 *	high-level finder routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	this is the default-action case for when the user does not specify
 *	a specific type of record.  try to match on absolutely everything,
 *	since we have no idea what they are looking for, but stick to fields
 *	which have sort-tables!
 */

bool f_anything(mb)
match_block *mb;
{
    if (ON(F_HOST_MODE) && !(mb->flags & LF_WHOIS)) /* running as HOST? */
	do_host(mb);				/* only look for host stuff */
    else {
	do_mailbox(mb);
	do_host(mb);
	find(mb, i_impname);		/* bonus checks!! */
	find(mb, i_netname);
	find(mb, i_netnumber);
	find(mb, i_impnetnumber);
    }
    return TRUE;		/* Always true for now */
}

/*
 *	check for the "usual" things.  this finder is called by most of
 *	the other to do searches that are in common in (almost) all cases.
 */

bool f_usual(mb)
match_block *mb;
{
    if (!(mb->flags & LF_ONLY_FIELDS)) {
	find(mb, i_handle);		/* "usual" consists of handle */
	do_name(mb);			/* and name... */
	do_mailbox(mb);
    }
    if (mb->flags & LF_HANDLE_ONLY)
	find(mb, i_handle);		/* "usual" consists of handle */
    if (mb->flags & LF_NAME_ONLY)
	do_name(mb);			/* and name... */
    if (mb->flags & LF_MAILBOX_ONLY)
	do_mailbox(mb);
    return TRUE;		/* Always true for now */
}

/*
 *	name matching is special.  if they give a name with no comma, e.g.
 *	"Smith", and they don't want partial match, then what we will
 *	check for is any name starting with text+"," e.g. "Smith," since
 *	names are store in the database as "last, first".  Also, we check
 *	for their text then a space, to catch the first word of a multi-
 *	word name, plus their text and a dot, for initial matching.
 */

bool do_name(mb)
match_block *mb;
{
    char *at, *tp, strbuf[MAX_LINE];
    int n;

/*
 *	if we're given <non-space><comma><non-space>, insert a space
 *	after the comma, assuming they just messed up last,first.
 */
    if ((at = strchr(mb->target, ',')) && at != mb->target &&
	isalpha(*(at-1)) && isalpha(*++at)) {
	strncpy(strbuf, mb->target, n = at - mb->target);
	strcpy(strbuf+n, " ");	/* copy old string up to the comma, then */
	strcat(strbuf, at);	/* add a space and the rest of the original */
	at = mb->target;	/* save old target; at is used as a flag! */
	mb->target = strbuf;	/* strbuf contains the new target */
    } else
	at = NULL;	/* flag that we didn't clobber original target */
/*
 *	if they gave us something with a trailing initial (the last char
 *	is a letter and the char before is a space), then do a partial
 *	match so we find everyone with a first name starting with that.
 *	otherwise, just do the lookup normally.
 */
    if ((n = strlen(mb->target)) > 3 &&
	*(tp = mb->target + n - 3) == ',' &&
	*++tp == ' ' && isalpha(*++tp))
	find_temp(mb, i_name, mb->flags | LF_PARTIAL, NULL);
    else
	find(mb, i_name);			/* ---DO IT--- */
/*
 *	first see if there's an exact (or partial, if requested) match
 *	for name.  if partial, then that's it!  we're done.  else...
 */
    if (!(mb->flags & LF_PARTIAL)) {
/*
 *	if a full match, then do some additional searches.  check for the
 *	text with a comma after it (like for a lastname), a dot after it
 *	like for an initial, or a space after it (for some words in a multi-
 *	word name).  all of these searches should look at the same parts
 *	of the name sort table, which only has to be loaded once, so it
 *	should be pretty fast.
 */
	if (mb->target != strbuf)		/* if using original, */
	    strcpy(strbuf, mb->target);		/* copy it now to strbuf. */
	tp = strbuf + strlen(strbuf);		/* point after the text */
	*tp = ' ';				/* add a trailing space */
	*(tp + 1) = '\0';			/* & a null after the space */
	find_temp(mb, i_name, mb->flags | LF_PARTIAL, strbuf);
	*tp = ',';				/* now try a comma */
	find_temp(mb, i_name, mb->flags | LF_PARTIAL, strbuf);
	*tp = '.';				/* now try a dot */
	find_temp(mb, i_name, mb->flags | LF_PARTIAL, strbuf);
    }
    if (at) mb->target = at;			/* restore original target */
    return TRUE;	/* Always true for now */
}

bool do_mailbox(mb)
match_block *mb;
{
    char *cp, *from, *to, *atsign, *host;
    char mailbox[MAX_LINE], new_host[MAX_LINE];


/*
 *	if they just said "foo", then we want to look for "foo@anything",
 *	so gen up "foo@" in a buffer and do a partial search.  if they
 *	did "foo.", then just look it up.
 */
    if (!strchr(mb->target, '@')) {		/* explicit mailbox? */
	if (mb->flags & LF_PARTIAL) {		/* no.  partial match? */
	    find(mb, i_mailbox);
	    return;
	}
	strcpy(mailbox, mb->target);		/* copy the original word */
	strcat(mailbox, "@");			/* then add an atsign */
	find_temp(mb, i_mailbox, mb->flags | LF_PARTIAL, mailbox);
	*strchr(mailbox, '@') = '%';     	/* try with a '%' now */
	find_temp(mb, i_mailbox, mb->flags | LF_PARTIAL, mailbox);
	return;
    }


/* --- find start of hostname, after the at and any whitespace ------------ */
    for (atsign = host = strrchr(mb->target, '@'); isspace(*++host);) ;
/* --- back up over space after username before atsign -------------------- */
    for (cp = atsign - 1; cp >= mb->target && isspace(*cp); cp--) ;
/* --- construct final mailbox -------------------------------------------- */
    for (from = mb->target, to = mailbox; from <= cp; )
	*to++ = *from++;			/* copy username part */
    *to++ = '@';				/* add atsign */
    strcpy(to, host);				/* then add official mailbox */

    find_mailbox(mb,mailbox);		/* try to find exact match */

    if (!mb->count)			/* any matches? */
      {					/* nope. */
	/* ---- now canonicalize and use offical name if different */
	if (!(mb->flags & LF_PARTIAL)		/* Skip this if partial */
	   && canonicalize_host(host, new_host, mb->flags))
	  {
	    /* First output the result of our first failed search. */
	    display_matches(mb, sum_line, TRUE, FALSE);
	    printf("[Looking for official hostname %s instead of \"%s\"]\n",
	           new_host, host);
	    host = new_host;
	  }
	/* --- construct final mailbox -------------------------------- */
	for (to = from = mb->target; from <= cp; )
	  *to++ = *from++;		/* copy username part */
	*to++ = '@';			/* add atsign */
	strcpy(to, host);		/* then add official mailbox */

	find_mailbox(mb,mb->target);
      }
    return(TRUE);
}

/*
 *	if starts with @, "@host", then call the suffix mailbox checker,
 *	else if ends is @, "ian@" do a simple partial search, else do the
 *	full lookup on the cleaned up mailbox.
 */

void find_mailbox(mb, mailbox)
match_block *mb;
char *mailbox;
{
    char *cp;	

    if (*mailbox == '@')
	s_mailbox(mb, mailbox);
    else {
	/* Use partial match if arg is "user@" or "user@h..." */
	cp = mailbox + strlen(mailbox) - 1;	/* point to last char */
	find_temp(mb, i_mailbox, mb->flags | ((*cp == '@') ? LF_PARTIAL : 0),
		mailbox);
    }
}

/*
 *	suffix mailbox, "@bar", which is a bitch.  we let FNDSBX do the
 *	work of finding potential matches (potential only since "@foo"
 *	could match "@foonix"), then grab the records and verify that
 *	it's really the suffix we want.
 */

bool s_mailbox(mb, target)
match_block *mb;
char *target;
{
    idstr_t key;
    idrix_t record;
    int length;
    char *cp;

    if (ON(F_INTERACTIVE))
	puts("[Scanning database... this will take a little while!]");
    search_initialize(&mb->fndblk, ID_FNDT_SUBS|ID_FNDF_NOREC,
			i_mailbox, target);
    length = strlen(target);			/* length of target */
    while (id_recfind(&mb->fndblk)) {		/* potential match */
	record = mb->fndblk.fnd_rix;
	if (!(mb->flags & LF_PARTIAL)) {
	    /* Get ptr to place where '@' will be if "@foo" matches "x@foo" */
	    key = mb->fndblk.fnd_str;		/* Get entry string */
	    cp = ID_SCP(key) + ID_SLN(key) - length;
	    if (*cp != '@') continue;		/* key must END with target */
	}
	if (!get_htype(record)) continue;	/* Ignore if invisible htype */
	if (!save_record(mb, record)) break;	/* save this record */
    }
    return TRUE;	/* Always true for now */
}

bool f_imp(mb)
match_block *mb;
{
    find(mb, i_nicknames);			/* nickname will do */
    find(mb, i_impname);			/* impname or */
    find(mb, i_impnumber);			/* impnumber or */
    return TRUE;		/* Always true for now */
}

bool f_domain(mb)
match_block *mb;
{
    char strbuf[MAX_LINE];

    find(mb, i_domainame);				/* find domains */
    strcpy(strbuf, mb->target);
    /* If given FOO look for FOO. also */
    strcat(strbuf, ".");
    find_temp(mb, i_domainame, mb->flags | LF_PARTIAL, strbuf);
    return TRUE;		/* Always true for now */
}

bool do_host(mb)
match_block *mb;
{
    find(mb, i_hostname);
    find(mb, i_nicknames);
    find(mb, i_netaddress);
    find(mb, i_domainame);

#if AUTO_EXP
    /* Automatically expand the target for a partial-match */
    /*   search, even though the user didn't ask for it... */
    if (!(mb->flags & LF_PARTIAL))
    {
      strcpy(strbuf, mb->target);
      tp = strbuf + strlen(strbuf);
      *tp = '.';			/* let's try tacking on a dot */
      *(tp + 1) = '\0';			/* make into real string */
      find_temp(mb, i_hostname, mb->flags | LF_PARTIAL, strbuf);
      find_temp(mb, i_nicknames, mb->flags | LF_PARTIAL, strbuf);
      find_temp(mb, i_domainame, mb->flags | LF_PARTIAL, strbuf);
      find_temp(mb, i_netaddress, mb->flags | LF_PARTIAL, strbuf);
      *tp = '-';			/* try adding a hyphen */
      find_temp(mb, i_hostname, mb->flags | LF_PARTIAL, strbuf);
      find_temp(mb, i_nicknames, mb->flags | LF_PARTIAL, strbuf);
      find_temp(mb, i_domainame, mb->flags | LF_PARTIAL, strbuf);
    }
#endif
    return TRUE;		/* Always true for now */
}

bool f_network(mb)
match_block *mb;
{
    char strbuf[MAX_LINE];

    find(mb, i_netname);				/* network name */
    find(mb, i_netnumber);			/* or number will do */
    if (!(mb->flags & LF_PARTIAL)) {		/* allow "net 10" */
	strcpy(strbuf, mb->target);		/* make a copy */
	strcat(strbuf, ".");			/* then try it with a dot */
	find_temp(mb, i_netnumber, LF_PARTIAL, strbuf);
    }
    return TRUE;		/* Always true for now */
}

bool f_org(mb)
match_block *mb;
{
    find(mb, i_nicknames);
    return TRUE;		/* Always true for now */
}

bool f_tac(mb)
match_block *mb;
{
    if (!find(mb, i_tacnumber))
      do_host(mb);
    return TRUE;		/* Always true for now */
}

/*
 *	low-level finder routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	do the real work of finding.  does an ID_FNDT_FULL or ID_FNDT_PREF
 *	depending on LF_PARTIAL.  throws out X-records when you're not
 *	allowed to see them, and records which don't have one of the
 *	given htypes.   matches are saved via save_match() and returns 0.
 */

idrix_t find(mb, field)
match_block *mb;
iditm_t field;
{
    idrix_t record;
    int c, old_count, ftyp;

    if (ON(F_ABORT))				/* don't do any more */
	return 0;				/* searches if aborting. */

    /* Continue previous search, or start new one? */
    if (!(mb->flags & LF_FIND_ASKFUL) || mb->count != AUTO_SHOW) {
	switch (mb->flags & (LF_PARTIAL | LF_SUBSTRING)) {
	    case LF_PARTIAL:	ftyp = ID_FNDT_PREF; break;
	    case LF_SUBSTRING:	ftyp = ID_FNDT_SUBS; break;
	    default:		ftyp = ID_FNDT_FULL;
	}
	search_initialize(&mb->fndblk, ftyp, field, mb->target);
    }

    mb->searches++;				/* doing another search */
    for (old_count = mb->count; OFF(F_ABORT) && id_recfind(&mb->fndblk);) {
	record = mb->fndblk.fnd_rix;			/* recidx for match */
	if (!(c = get_htype(record)) || (mb->htypes && !strchr(mb->htypes, c)))
	    continue;					/* doesn't match! */
	if ((mb->flags & (LF_ARPANET | LF_MILNET)) && !right_net(mb))
	    continue;
	if (!save_record(mb, record) || (mb->flags & LF_FIND_FIRST) ||
		    ((mb->flags & LF_FIND_ASKFUL) && mb->count == AUTO_SHOW))
	    break;
    }
    return (mb->count > old_count) ? record : 0;
}

/*
 *	does a find() with temporary flags and/or target
 */

idrix_t find_temp(mb, field, flags, target)
match_block *mb;
iditm_t field;
unsigned flags;
char *target;
{
    unsigned original_flags;
    char *original_target;
    idrix_t result;

    original_target = mb->target;		/* save original target */
    if (target)
	mb->target = target;			/* substitute alternate */
    original_flags = mb->flags;			/* save original flags */
    mb->flags = flags;				/* use temporary flags */
    result = find(mb, field);			/* do the real finding. */
    mb->flags = original_flags;			/* restore original flags */
    mb->target = original_target;		/* restore original target */
    return result;
}

/*
 * does find() function, but for targets that might have more than
 *   MAX_MATCHES matches.  Instead of forbidding more searches when
 *   called with a full match block, it clears the match block and
 *   iterates for the next batch.  To accomplish this, it sets the
 *   full-block flag when it fills up, not on the subsequent call
 *   the way the other save_match() does for find().
 * When LF_RECORDS_FULL is set on return, the calling routine is
 *   responsible for dealing with the contents before calling this
 *   routine again.
 * Note also that the running count of matches is maintained in the
 *   count argument.  The new count the return value, too.
 */

int find_big(mb, field, count)
match_block *mb;
iditm_t field;
int count;
{
    idrix_t record;
    int c, old_count, ftyp;

    if (ON(F_ABORT))
       return(0);

    /* Continue previous search, or start new one? */
    if (count == 0) {
	switch (mb->flags & (LF_PARTIAL | LF_SUBSTRING)) {
	    case LF_PARTIAL:	ftyp = ID_FNDT_PREF; break;
	    case LF_SUBSTRING:	ftyp = ID_FNDT_SUBS; break;
	    default:		ftyp = ID_FNDT_FULL;
	}
	search_initialize(&mb->fndblk, ftyp, field, mb->target);
    }

    /* See if we need to wrap the search this time */
    if (mb->count == MAX_MATCH) {
	mb->count = mb->key_count = 0;
	mb->flags &= ~LF_RECORDS_FULL;
	mb->key_cp = mb->key_buffer;
    }

    mb->searches++;				/* doing another search */
    for (old_count = count; OFF(F_ABORT) && id_recfind(&mb->fndblk);) {
	record = mb->fndblk.fnd_rix;			/* recidx for match */
	if (!(c = get_htype(record)) || (mb->htypes && !strchr(mb->htypes, c)))
	    continue;					/* doesn't match! */
	if ((mb->flags & (LF_ARPANET | LF_MILNET)) && !right_net(mb))
	    continue;
	if (!save_record(mb, record))
	    break;
	++count;
	if ((mb->flags & LF_FIND_FIRST) ||
	   ((mb->flags & LF_FIND_ASKFUL) && count == AUTO_SHOW))
	    break;
	if (mb->count >= MAX_MATCH) {
	    mb->flags |= LF_RECORDS_FULL;
	    break;
	}
    }
    return(count - old_count);
}



/*
 *	record and key saving routines.  save records in the match block,
 *	and keys in the key buffer in the given match block.
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	save a match in the match structures with the given text for
 *	a sorting key.
 */

bool save_match(mb, record, key)
match_block *mb;
idrix_t record;
char *key;
{
    int i;

    if (mb->flags & LF_RECORDS_FULL)		/* already full? */
	return FALSE;
    if (mb->count >= MAX_MATCH) {		/* don't think so.  room? */
	printf("Can't save more than %d matches.\n", MAX_MATCH);
	mb->flags |= LF_RECORDS_FULL;		/* flag that we know this */
	return FALSE;
    }
    for (i = 0; i < mb->count; i++)		/* check for duplicates */
	if (record == mb->recidx[i])
	    return TRUE;			/* already have this one */
    if (record != id_recidx && !get_record(record))
	return FALSE;
    mb->records[mb->count] = mb->count;		/* not sorted yet. */
    mb->recidx[mb->count] = record;		/* save unique recidx */
    mb->keys[mb->count++] = key;		/* save the sorting key */
    return TRUE;
}

/*
 *	save a record keyed by its name, or handle if no name
 */

bool save_record(mb, record)
match_block *mb;
idrix_t record;
{
    char *key;

    key = save_idstrs(mb, mb->key_field, i_handle);
    if (!save_match(mb, record, key))
	return FALSE;				/* failed to save */
    return TRUE;
}
 
/*
 *	save the contents of the given field in the key-string buffer
 */

char *save_idstrs(mb, fld1, fld2)
match_block *mb;
iditm_t fld1, fld2;
{
    char *p, *key;
    idstr_t itm1, itm2;

    if (mb->flags & LF_KEYS_FULL)
	return NULL;				/* no room at the inn */
    if (get_item(fld1, &itm1))			/* get the first field */
	mb->key_count += ID_SLN(itm1) + 1;	/* store it, account 4 null */
    else fld1 = -1;				/* oops, isn't there */
    if (fld2 != -1) {				/* get second field */
	if (!get_item(fld2, &itm2)) fld2 = -1;
	else mb->key_count += ID_SLN(itm2);
    }
    if (mb->key_count > KEY_BUF_SIZE) {
	fputs("Ran out of room in key_buffer[]!\n", stderr);
	mb->flags |= LF_KEYS_FULL;
	return NULL;
    }
    key = p = mb->key_cp;			/* start of key */
    if (fld1 != -1) {
	strncpy(p, ID_SCP(itm1), ID_SLN(itm1)); /* save first field */
	p += ID_SLN(itm1);			/* point to after it */
    }
    if (fld2 != -1) {				/* save 2nd field if any */
	strncpy(p, ID_SCP(itm2), ID_SLN(itm2));
	p += ID_SLN(itm2);
    }
    *p++ = '\0';				/* tie off key string */
    mb->key_cp = p;				/* update pointer to next */
    return key;					/* free and return cp 2 key */
}

/*
 *	save the given string in the key-string buffer
 */

char *save_string(mb, s)
match_block *mb;
char *s;
{
    char *old_cp;
    int n;

    if (mb->flags & LF_KEYS_FULL) return NULL;
    n = strlen(s) + 1;				/* # of chars written */
    mb->key_count += n;				/* update count */
    if (mb->key_count > KEY_BUF_SIZE) {
	fputs("Ran out of room in key_buffer[]!\n", stderr);
	mb->flags |= LF_KEYS_FULL;
	return NULL;
    }
    strcpy(mb->key_cp, s);			/* copy to perm buffer */
    old_cp = mb->key_cp;			/* save where we wrote */
    mb->key_cp += n;				/* advance key_cp */
    return old_cp;
}

/*
 *	high-level display routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	display routines.  call this after doing a high-level f_something;
 *	if there were no matches, it generates the "No [partial ] match
 *	for %s" message.  it returns the number of matches.
 */

void no_matches(mb)
match_block *mb;
{
    char *p;
    int c;

    if (mb->count) return;		/* this is for NO matches?? */
    fputs("No match for ", stdout);
    if (mb->flags & LF_PARTIAL)
	fputs("partial ", stdout);
    if (mb->flags & LF_ARPANET) {
	fputs("ARPANET ", stdout);
	if (mb->flags & LF_MILNET)
	    fputs("or ", stdout);
    }
    if (mb->flags & LF_MILNET)
	fputs("MILNET ", stdout);
    if (commands[mb->cmd_index].name)
	printf("%s ", commands[mb->cmd_index].name);
    if (mb->flags & LF_ONLY_FIELDS) {
	if (mb->flags & LF_HANDLE_ONLY)
	    fputs("handle ", stdout);
	if (mb->flags & LF_NAME_ONLY) {
	    if (mb->flags & LF_HANDLE_ONLY)
		fputs("or ", stdout);
	    fputs("name ", stdout);
	}
	if (mb->flags & LF_MAILBOX_ONLY) {
	    if (mb->flags & (LF_HANDLE_ONLY | LF_NAME_ONLY))
		fputs("or ", stdout);
	    fputs("mailbox ", stdout);
	}
    }
    putchar('"');				/* show funny characters */
    for (p = mb->target; c = *p; p++)
	if (isprint(c))
	    putchar(c);
	else {
	    putchar('^');
	    putchar(CONTROL(c));
	}
    puts("\".");
}

/*
 *	display the results of the last search.  cmb is expected to
 *	point to the match_block of the results to be displayed.  if there's
 *	more than one record, sort them by name and do a 1-line summary of
 *	each.  if there's just one record, do the appropriate display,
 *	depending on the record type.
 */

bool display_matches(mb, function, ask_flag, divider_flag)
match_block *mb;
int (*function)();
bool ask_flag, divider_flag;
{
    int i;
    bool keep_going = TRUE;

    int_on();					/* turn on interrupts */
    if (!mb->count)
	no_matches(mb);
    else if (mb->count == 1 && !(mb->flags & LF_SUMMARY))
	out_record(mb, mb->recidx[0]);		/* single match or always */
    else {
	sort_matches(mb);			/* sort multiple matches */
	if (!(mb->flags & LF_FULL_OUTPUT)) {
	    keep_going = summary(mb, function, ask_flag, divider_flag);
	    if (OFF(F_NIC_USER) && (ON(F_ONCE_THROUGH) || OFF(F_INTERACTIVE)))
		puts(single_out);
	} else {
	    for (i = 0; OFF(F_ABORT) && i < mb->count; i++) {
		if (i) fputs(divider, stdout);
		out_record(mb, mb->recidx[mb->records[i]]);
	    }
	}
    }
    int_off();
    return(keep_going);
}

/*
 *	this is the standard output routine for displaying the results
 *	of a subdisplay, like users of host, hosts on net, etc.
 */

bool sub_matches(mb, what, summary_function, ask_flag, divider_flag, time_thru)
match_block *mb;
char *what;
int (*summary_function)();
bool ask_flag, divider_flag;
int time_thru;
{
    bool maxed_out = FALSE;
    bool keep_going = FALSE;


    if (!mb->count)
	printf("Program error, couldn't find any %ss.\n", what);
    else {
	if (mb->count == 1)
	    printf("%s%sThere is one %s%s:\n\n", time_thru ? "\n" : "",
		   indent, time_thru ? "more " : "", what);
	else {
	    maxed_out = (mb->count == MAX_MATCH);
	    printf("%s%sThere are %s%d %s%ss:\n", time_thru ? "\n" : "",
		   indent, maxed_out ? "at least " : "",
		   mb->count, time_thru ? "more " : "", what);
	    if (maxed_out && !time_thru)
		printf("%s(Each block of %d will be sorted separately)\n",
		       indent, MAX_MATCH);
	    printf("\n");
	    sort_matches(mb);
	}
	mb->flags &= ~LF_SUBDISPLAY;		/* don't do sub-sub-display */
	mb->flags |= LF_SUMMARY;		/* and always summarize */
	keep_going = display_matches(mb, summary_function,
				     ask_flag, divider_flag);
    }
    return(keep_going);
}

/*
 *	output the given single record.  if they want it dumped, then
 *	use dump_record(), else use the appropriate display routine
 *	depending on the htype.
 */

void out_record(mb, record)
match_block *mb;
idrix_t record;
{
    int c, i;
    void (*main)() = standard_header, (*sub)() = NULL;

    if (!get_record(record)) return;		/* get the single record. */
    if (ON(F_NIC_USER) && (mb->flags & LF_DUMP)) {	/* want a raw dump? */
	dump_record();					/* yep */
	return;						/* and we're done! */
    }
    if (!(c = get_htype(NULL))) {		/* use current record */
	puts("Sorry, you shouldn't see this record.");
	return;
    }
    if (strchr(HOST_HTYPES, c) && ON(F_HOST_MODE) && !(mb->flags & LF_WHOIS))
	main = d_athost;
    else for (i = 0; i < N_HTYPES; i++)
	if (htypes[i].htype == c) {
	    main = htypes[i].main;
	    sub = htypes[i].sub;
	    break;
	}
    if (mb->flags & LF_SUBDISPLAY)	/* show the single match first */
	summary(mb, sum_line, FALSE, FALSE);
    else
	((*main)());			/* call the main display */
    if (sub) {				/* is there a sub-display? */
	if (!(mb->flags & LF_NO_SUB)) {
	    putchar('\n');
	    ((*sub)(mb));		/* then show it... */
	}
    } else					/* no sub-display. */
	if (mb->flags & LF_SUBDISPLAY) {	/* did they expect one? */
	    if (i < N_HTYPES)
		printf("There is no subdisplay for a %s record!\n",
			htypes[i].name);
	    else
		puts("There is no subdisplay for that record type!");
	}
}

/*
 *	dump the current record as "(n) label: text", where n is the internal
 *	field# (not the field name)
 */

void dump_record()
{
    iditm_t item;
    idstr_t itmbuf;

    for (item = -1; OFF(F_ABORT) && id_itmnext(&item, &itmbuf);) {
	printf("(%d) ", item);
	id_fputs(id_idfname(item), stdout);
	fputs(": ", stdout);
	id_fputs(itmbuf, stdout);
	putchar('\n');
    }
}

/*
 *	summary gives a 1-line summary of each person in the block.
 *
 *	if (ask_p) then the user is asked, after ASK_AFTER records
 *	have been displayed, if he wants to continue.  If the total
 *	number of records is <= AUTO_SHOW, then they're all just
 *	showed, with no asking.
 *
 *	if (divide_p), then after every BREAK_EVERY lines a divider
 *	is output, to break it up for easier reading.
 */

bool summary(mb, routine, ask_p, divide_p)
match_block *mb;
int (*routine)();
bool ask_p, divide_p;
{
    int i;

    int_clear();
    for (i = 0; OFF(F_ABORT) && i < mb->count; i++) {
	if (ask_p && ON(F_INTERACTIVE) && (i == ASK_AFTER) &&
		!(mb->flags & LF_EXPAND) && mb->count > AUTO_SHOW &&
		!yesno("There are %d more matches.  Show them? ",
		    mb->count - ASK_AFTER))
	    return(FALSE);
	if (divide_p && i && !(i % BREAK_EVERY))
	    fputs(divider, stdout);		/* output divider */
	if (!(*routine)(mb->recidx[mb->records[i]]))
	    return(FALSE);		/* output routine bombed! */
    }
    return(TRUE);
}

/*
 *	this is the standard routine given to summary() to call for each
 *	match.  it does "name (handle) x y" where x and y are different
 *	fields, depending on the record type.
 */

int sum_line(record)
idrix_t record;
{
    int i, c;

    if (get_record(record)) {
	column = name_and_handle();		/* returns length written */
	c = get_htype(NULL);
	for (i = 0; i < N_HTYPES; i++)
	    if (c == htypes[i].htype) {
		s_rest(htypes[i].item1, htypes[i].item2);
		return 1;
	    }
	putchar('\n');
	return 1;
    }
    printf("Error - could not retrieve record %d.\n", record);
    return 0;
}

/*
 *	this is the routine which outputs the variable fields x and y.
 *	the extra cruft for dealing with field2 is because phone fields
 *	are often weird, with leading CRLFs and so on.
 */

void s_rest(f1, f2)
iditm_t *f1, *f2;
{
    idstr_t buf1, buf2;
    char *p, *start;
    int len, body;

    if (f1 && get_item(*f1, &buf1) && id_siget(*f1, &buf1, &buf2)) {
	space(MBX_COLUMN - 1 - column);
	id_fputs(buf2, stdout);
	column += ID_SLN(buf2);
    }
    if (get_item(*f2, &buf1)) {
	for (len = ID_SLN(buf1), start = ID_SCP(buf1);
	     len > 0 && isspace(*start);
	     len--, start++) ;			/* skip leading cruft */
	for (body = 0, p = start;
	     len > 0 && *p != '\r' && *p != '\n';
	     p++, len--, body++) ;		/* only use the first line! */
	if ((column + body) >= width - 1) {
	    putchar('\n');		/* if won't fit on this line, */
	    column = 0;			/* go to the next */
	}
	space(width - 2 - column - body);
	while (--body >= 0)		/* output the field */
	    putchar(*start++);
    }
    putchar('\n');
}

/*
 *	this is the special display used for host records and such when
 *	displaying the liaison etc.  it's a quicky 2-line summary with
 *	no columnation: "name  (handle)  mailbox" and phone on the next line.
 */

void contact_summary()
{
    idstr_t itmbuf, mbxbuf;

    printf("%s%s", indent, indent);		/* double-indent for this */
    if (x_record()) fputs("X:", stdout);
    if (get_item(i_name, &itmbuf))
	id_fputs(itmbuf, stdout);
    else
	fputs("[No name]", stdout);
    fputs("  (", stdout);
    if (get_item(i_handle, &itmbuf))
	id_fputs(itmbuf, stdout);
    else
	fputs("ID!?", stdout);
    fputs(")  ", stdout);
    if (get_item(i_mailbox, &itmbuf) && id_siget(i_mailbox, &itmbuf, &mbxbuf))
	id_fputs(mbxbuf, stdout);
    else
	fputs("[No mailbox]", stdout);
    printf("\n%s%s", indent, indent);		/* double-indent for fone */
    if (get_item(i_phone, &itmbuf))
	id_fputs(itmbuf, stdout);
    else
	fputs("[No phone]", stdout);
    putchar('\n');				/* get to a new line */
}

/*
 *	special hack for displaying multiple contacts for a record.  in
 *	case a single person holds more than one title, it does
 *
 *	Foo, Bar:
 *	  person's information
 *
 *	instead of
 *
 *	Foo:
 *	  person's information
 *	Bar:
 *	  same person's information again
 */

void d_contacts(cb, n)
struct contact_struct cb[];
int n;
{
    iditm_t current;
    idstr_t itmbuf;
    char *cp, strbuf[MAX_LINE];
    int i, j, count;

    current = id_recidx;			/* save the current record */
    cp = strbuf;				/* use this buffer */
/*
 *	first grab and save all the handle-strings from the fields, since
 *	this record is only current NOW, and will cease to be as soon as
 *	we start to look up any of those people.
 */
    for (i = 0; i < n; i++) {
	if (get_item(*cb[i].field, &itmbuf)) {	/* if field exists */
	    cb[i].handle = cp;			/* save cp to buf */
	    id2c(cp, itmbuf);			/* copy it here. */
	    cp += ID_SLN(itmbuf) + 1;		/* move to next pos in buf */
	} else
	    cb[i].handle = NULL;		/* didn't find it. */
    }
/*
 *	now look up those handles we just got, and turn them into
 *	record indicies.  -1 means not found or x-user or whatever.
 */
    for (i = 0; i < n; i++) {
	if (cb[i].handle
	  && id_recgethdl(id_strfs(cb[i].handle))
	  && get_htype(NULL))
	    cb[i].record = id_recidx;
	else cb[i].record = -1;
    }
/*
 *	now do the display
 */
    count = 0;
    for (i = 0; i < n; i++) {
	if (cb[i].record >= 0) {
	    if (!count++) putchar('\n');	/* blank line b4 display */
	    fputs(indent, stdout);
	    if (cb[i].label) fputs(cb[i].label, stdout);
	    else label(*cb[i].field);
	    for (j = i + 1; j < n; j++)
		if (cb[i].record == cb[j].record) {
		    fputs(", ", stdout);
		    if (cb[j].label) fputs(cb[j].label, stdout);
		    else label(*cb[j].field);
		    cb[j].record = -1;		/* -1 so no re-found! */
		}
	    puts(":");
	    get_record(cb[i].record);		/* make record current */
	    contact_summary();			/* show that person */
	}
    }
    get_record(current);			/* re-get the old record */
}

/*
 *	per-record-type display routines.  these are the high-level
 *	routines which do the large-scale nice outputs for each different
 *	htype.
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	display routine for an individual, htype = "I".
 */

void d_individual()
{
    idstr_t itmstr;
    char *itmchar;

    standard_header();
    if (get_item(i_validcard, &itmstr)) {
        itmchar = ID_SCP(itmstr);
	if ((itmchar[0] == 'y') || (itmchar[0] == 'Y'))
	    printf("%sMILNET TAC user\n", indent);
    }
    comments();				/* display comments if any */
    d_updated();			/* show record-update date */
}

void d_host()
{
    standard_header();
    putchar('\n');
    d_hinfo();				/* display host info */
    printf("%sSystem: ", indent);
    putz(i_cputype);
    printf(" running ");
    putz(i_opsys);
    putchar('\n');
    d_contacts(host_contacts, N_HOST_CONTACTS);
    comments();				/* show comments in any */
    d_updated();			/* show record-update date */
}

/*
 *	if there are any records out there that have this host for
 *	their host field, then: if they didn't request auto-expansion
 *	of users, ask if they want to see them.  if so, show 'em.
 */

void d_s_host(omb)
match_block *omb;
{
    char strbuf[MAX_LINE];
    idstr_t itmbuf;
    match_block *mb;
    int count;
    int time_thru = 0;

    get_item(i_handle, &itmbuf);
    id2c(strbuf, itmbuf);			/* save host's ident here */
    if (!(mb = get_match_block()))		/* get a match block */
	return;					/* can't play ball without! */
    match_initialize(mb, strbuf, i_name, omb->flags | LF_FIND_ASKFUL, NULL);
    if (!find(mb, i_host))		/* find first AUTO_SHOWful */
	printf("%sNo registered users.\n", indent);	/* oops, none */
    else
        if (((count = mb->count) != AUTO_SHOW) ||
	    (see_more(mb, v3, "registered users of this host")))
	{
	    do {
		count += find_big(mb, i_host, count);
		if (!sub_matches(mb, "registered user", sum_line, 
				 (count <= MAX_MATCH), TRUE, time_thru++))
		    break;
		} while (mb->count == MAX_MATCH);
	    if (count >= MAX_MATCH)
		printf("\n%sTotal of %d records.\n\n", indent, count);
	}
    free_match_block(mb);			/* done with match block */
}

void d_hinfo()
{
    idstr_t nicbuf, dombuf;

    d_labeled(i_hostname, NULL, 0);
    get_item(i_nicknames, &nicbuf);
    get_item(i_domainame, &dombuf);
    if (ID_SLN(nicbuf) > 0 || ID_SLN(dombuf) > 0) {
	printf("%sNicknames: ", indent);
	if (ID_SLN(dombuf) > 0) {
	    id_fputs(dombuf, stdout);
	    if (ID_SLN(nicbuf) > 0) fputs(", ", stdout);
	}
	if (ID_SLN(nicbuf) > 0)
	    id_fputs(nicbuf, stdout);
	putchar('\n');
    }
    d_labeled(i_netaddress, "Address", ',');
}

void d_imp()
{
    idstr_t itmbuf;
    idfnd_t fndblk;
    char *cp, address[MAX_LINE], strbuf[MAX_LINE];
    int host, net, third;
    bool first;

    standard_header();
    putchar('\n');
    if (get_item(i_autodin, &itmbuf)) {
        fputs(indent, stdout);
	printf("Autodin: %s\n\n",id2c(strbuf,itmbuf));
    }
    fputs(indent, stdout);
    putz(i_impname);
    printf(" is PSN/IMP ");
    putz(i_impnumber);
    printf(" on network ");
    net = out_netname(i_impnetnumber);		/* display network as #/name */
    putchar('\n');				/* should only be class A */
    d_contacts(imp_contacts, N_IMP_CONTACTS);
    comments();
    d_updated();				/* show record-update date */
    get_item(i_impnumber, &itmbuf);		/* get the imp# */
    cp = id2c(strbuf, itmbuf);			/* copy it to buffer */
    first = FALSE;				/* first host not found yet */
    for (host = 0; host < 128; host++) {
	sprintf(address, "%d.%d.0.%s", net, host, cp);
	search_initialize(&fndblk, ID_FNDT_FULL, i_netaddress, address);
	while (id_recfind(&fndblk)) {		/* Find and get record */
	    if (!get_htype(NULL)) continue;
	    if (!first) {			/* if this's the 1st */
		printf("\n%sHosts on this PSN:\n\n", indent);
		first = TRUE;			/* this was it */
	    }
	    host_line(NULL);			/* host summary line */

	    /* Look for others behind this one, with nonzero third octets */
	    for (third = 1; third < 128; third++)
	    {
	        sprintf(address,"%d.%d.%d.%s",net,host,third,cp);
	        search_initialize(&fndblk,ID_FNDT_FULL,i_netaddress,address);
	        while (id_recfind(&fndblk) && get_htype(NULL))
	            host_line(NULL);
	    }
	}
    }
    if (!first) printf("\n%sNo hosts found on this PSN.\n", indent);
}

void d_updated()
{
    idstr_t itmbuf;
    char strbuf[MAX_LINE];
    char upyr[3],upmo[3],updy[3];

    get_item(i_updated, &itmbuf);
    id2c(strbuf, itmbuf);
    strncpy(upyr, &strbuf[0], 2);
    strncpy(upmo, &strbuf[2], 2);
    strncpy(updy, &strbuf[4], 2);
    upyr[2] = upmo[2] = updy[2] = '\0';

    printf("\n   Record last updated on %s-%s-%s.\n",
	   updy,months[atoi(upmo)-1],upyr);
}

/*
 *	given the # of a field containing a net#, type out that number,
 *	then the name of the network.  returns the first octet of the net#
 */

int out_netname(field)
iditm_t field;
{
    idstr_t netbuf;
    idrix_t current;
    char strbuf[MAX_LINE];
    int net;

    current = id_recidx;			/* save current recidx */
    if (!get_item(field, &netbuf)) {		/* get the network number */
	puts("?");				/* into idstr_t netbuf. */
	return 0;
    }
    net = net_part(strbuf, netbuf);		/* get important part */
    fputs(strbuf, stdout);			/* display it. */
    if (id_recgetitm(i_netnumber, netbuf)) {	/* now get the name of this */
	printf(" (");				/* network and display it */
	putz(i_netname);
	printf(")");
	get_record(current);			/* re-get old record */
    }
    return net;					/* return the first octet */
}

void d_domain()
{
    standard_header();
    putchar('\n');
    d_labeled(i_domainame, "Domain Name", 0);
    d_contacts(domain_contacts, N_DOMAIN_CONTACTS);
    d_updated();				/* show record-update date */
    d_dsrv(i_servercontact,"Domain servers in listed order");
    comments();
}

void d_dsrv(item, header)
iditm_t item;
char *header;
{
    idstr_t buf1, buf2;
    idrix_t record;
/*    idfnd_t fndblk; */
    int i, n_handles, n_hosts;
    char *cp, *handles[MAX_ADDR], strbuf[MAX_LINE];
/*    match_block *mb; */

    if (get_item(item, &buf1)) {
	record = id_recidx;				/* save current rec */
	n_handles = 0;					/* # of host handles */
	cp = strbuf;					/* where to store em */
	while (id_siget(item,&buf1,&buf2) && n_handles < MAX_ADDR) {
	    handles[n_handles++] = id2c(cp, buf2);	/* save handle */
	    cp += ID_SLN(buf2) + 1;			/* advance cp */
	}
	n_hosts = 0;					/* # we really see */
	for (i = 0; i < n_handles; i++) {		/* loop over handles */
	    if (!id_recgethdl(id_strfs(handles[i])) || !get_htype(NULL))
		continue;
	    if (!n_hosts++)
		printf("\n%s%s:\n\n", indent, header);
	    host_line(NULL);				/* output da facts */
	}
	if (!n_hosts) printf("\n%sNo known domain servers.\n", indent);
	get_record(record);				/* re-get org record */
    }
}

void d_s_domain(omb)
match_block *omb;
{
    idstr_t buf1;
    char strbuf[MAX_LINE];
    match_block *mb;

    if (!(mb = get_match_block()))			/* get a match block */
	return;						/* oops! */
    get_item(i_domainame, &buf1);			/* domain name */
    id2c(strbuf, buf1);					/* save it here */
    if (!strchr(strbuf, '.')) {				/* top-level? */
/*
 *	if the domainame doesn't have a dot, then it must be a top-level
 *	domain, so ask the user if they want to see all the domains under
 *	that top-level domain.
 */
	match_initialize(mb,strbuf,i_domainame,omb->flags|LF_FIND_ASKFUL,"D");
	if (!find(mb, i_parentdom))		/* find first AUTO_SHOWful */
	{
	  printf("%sFor information concerning this domain, please consult\n",
	         indent);
	  printf("%sthe Administrative Contact listed above.\n",indent);
	}
	else
	    switch (mb->count) {		/* some matches. */
		case AUTO_SHOW:			/* maybe lots more, so ask */
		    if (!see_more(mb, v3,
			    "known domains under this top-level domain"))
			break;			/* don't want to see em */
		    find(mb, i_parentdom);	/* do, so find all the rest */
		default:
		    sub_matches(mb, "known sub-domain", sub_domain,
			TRUE, FALSE, 0);
	    }
	free_match_block(mb);				/* done with this */
    } else {
/*
 *	the domainame DID have a dot, so this is not a top-level domain,
 *	but a secondary domain.  we ask the user if they want to see all
 *	known hosts under that domain.
 */
	*strbuf = '.';					/* we want .dom */
	id2c(strbuf + 1, buf1);				/* reput it after . */
	match_initialize(mb, strbuf, i_hostname, LF_SUBSTRING | LF_FIND_FIRST,
								HOST_HTYPES);
	if (find(mb,i_domainame) || find(mb,i_hostname) || find(mb,i_nicknames)) {
	    if (see_more(omb, v3, "known hosts under this secondary domain")) {
		match_initialize(mb, strbuf, i_hostname, LF_SUBSTRING,
								HOST_HTYPES);
		find_temp(mb,i_hostname, 0, strbuf+1);
		find(mb, i_domainame);
		find(mb, i_hostname);
		find(mb, i_nicknames);
		sub_matches(mb, "known host", host_line, TRUE, FALSE, 0);
	    }
	} else
	    printf("%sNo known hosts under this secondary domain.\n", indent);
	free_match_block(mb);				/* done with this */
    }
}

int sub_domain(record)
idrix_t record;
{
     idstr_t itmbuf;

    if (record && !get_record(record)) {
	printf("Error - could not retrieve record %d.\n");
	return 0;
    }
    if (x_record()) printf("%sX:", indent + 2);
    else fputs(indent, stdout);
    column = INDENT;
    if (!get_item(i_domainame, &itmbuf)) {
	putchar('!');			/* flag that typeout is handle */
	get_item(i_handle, &itmbuf);	/* this HAS to work! */
	column++;
    }
    id_fputs(itmbuf, stdout);
    column += ID_SLN(itmbuf);
    space(SUB_DOM_COLUMN - 1 - column);
    putz(i_name);
    putchar('\n');
    return 1;
}

void d_network()
{
    standard_header();
    putchar('\n');
    d_labeled(i_netname, NULL, 0);
    d_labeled(i_netnumber, NULL, 0);
    d_contacts(coordinator, N_COORDINATOR);
    d_dsrv(i_inaddrserver,"Domain System inverse mapping provided by");
    comments();
    d_updated();			/* show record-update date */
}

void d_s_network(omb)
match_block *omb;
{
    idstr_t itmbuf;
    idfnd_t fndblk;
    idrix_t record;
    char *key, strbuf[MAX_LINE], buf[MAX_LINE];
    int c, b1, b2, b3, b4;
    match_block *mb;
    bool ignored;

    get_item(i_netnumber, &itmbuf);
    net_part(strbuf, itmbuf);		/* get important part */
    strcat(strbuf, ".");		/* add a dot to the end */
    itmbuf = id_strfs(strbuf);		/* convert to idstr_t */
    if (!(mb = get_match_block()))
	return;
    match_initialize(mb, strbuf, i_hostname, omb->flags, HOST_HTYPES);
    search_initialize(&fndblk, ID_FNDT_PREF, i_netaddress, strbuf);
    for (ignored = FALSE; id_recfind(&fndblk);) {
	record = fndblk.fnd_rix;
	if (!(c = get_htype(record)) || !strchr(HOST_HTYPES, c))
	    continue;
	if (!mb->count && !see_more(omb, v3, "hosts on this network")) {
	    ignored = TRUE;
	    break;
	}
	id2c(buf, fndblk.fnd_str);		/* turn entry into C string */
	if (sscanf(buf, "%d.%d.%d.%d", &b1, &b2, &b3, &b4) != 4)
	    continue;
	if (b1 == NET_ARPANET || b1 == NET_MILNET)
	    sprintf(buf, "%3d%3d%3d%3d", b1, b4, b2, b3);
	else sprintf(buf, "%3d%3d%3d%3d", b1, b2, b3, b4);
	if (!(key = save_string(mb, buf)) || !save_match(mb, record, key))
	    break;
    }
    if (!ignored) {
	if (!mb->count)
	    printf("%sNo hosts found on this network.\n", indent);
	else
	    sub_matches(mb, "known host", host_line, TRUE, FALSE, 0);
    }
    free_match_block(mb);			/* done with match block */
}

int host_line(record)
idrix_t record;
{
    idstr_t itmbuf;

    if (record && !get_record(record)) {
	printf("Error - could not retrieve record %d.\n", record);
	return 0;
    }
    if (x_record()) printf("%sX:", indent + 2);
    else fputs(indent, stdout);
    column = INDENT;
    if (!get_item(i_hostname, &itmbuf)) {
	putchar('!');			/* flag that typeout is handle */
	get_item(i_handle, &itmbuf);	/* this HAS to work! */
	column++;
    }
    column += ID_SLN(itmbuf);
    id_fputs(itmbuf, stdout);
    space(ADDR_COLUMN - 1 - column);
    putz(i_netaddress);
    putchar('\n');
    return 1;
}

void d_org()
{
    standard_header();
    d_contacts(coordinator, N_COORDINATOR);
    comments();
    d_updated();			/* show record-update date */
}

/*
 *	now let's see about the members of this org...
 */

void d_s_org(omb)
match_block *omb;
{
    char *org_id, strbuf[MAX_LINE];
    idstr_t itmbuf;
    match_block *mb;

    get_item(i_handle, &itmbuf);			/* handle of this record */
    org_id = id2c(strbuf, itmbuf);		/* save host's ident here */
    if (!(mb = get_match_block()))
	return;
    match_initialize(mb, org_id, i_name, omb->flags | LF_FIND_ASKFUL, NULL);
    if (!find(mb, i_groups))			/* find first AUTO_SHOWful */
	printf("%sNo known members of this organization.\n", indent);
    else
	switch (mb->count) {			/* some matches. */
	    case AUTO_SHOW:			/* maybe lots more, so ask */
		if (!see_more(omb, v4,
			"registered members of this organization"))
		    break;			/* don't want to see em */
		find(mb, i_groups);		/* do, so find all the rest */
	    default:
		sub_matches(mb, "known member", sum_line, TRUE, TRUE, 0);
	}
    free_match_block(mb);			/* done with this */
}

void d_tac()
{
    standard_header();
    putchar('\n');
    d_hinfo();
    d_labeled(i_tacnumber, "TAC number", 0);
    d_labeled(i_cputype, "Hardware", 0);
    d_contacts(imp_contacts, N_IMP_CONTACTS);
    comments();
    d_updated();			/* show record-update date */
}

/*
 *	this is the special display routine used when running as the HOST
 *	program, plus support routines.
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	this is the display routine you get always when this program is
 *	invoked as "host".  it outputs the record in a radically different
 *	format; try it yourself to see.
 */

void d_athost()
{
    idstr_t str1, str2;
    idrix_t record;
    char *cp, *addresses[MAX_ADDR], strbuf[MAX_LINE];
    match_block *mb;
    int i, j;

    if (x_record()) fputs("X:", stdout);
    if (get_htype(NULL) == 'S') {
	putz(i_impname);
	printf(" is PSN ");
	putz(i_impnumber);
	printf(" on network ");
	out_netname(i_impnetnumber);
    } else {
	putz(i_hostname);
	get_item(i_domainame, &str1);
	get_item(i_nicknames, &str2);
	if (ID_SLN(str1) || ID_SLN(str2)) {
	    fputs(" (nicknames ", stdout);
	    if (ID_SLN(str1))
		id_fputs(str1, stdout);
	    if (ID_SLN(str2)) {
		if (ID_SLN(str1)) fputs(", ", stdout);
		id_siget(i_nicknames, &str2, &str1);
		id_fputs(str1, stdout);
		while (id_siget(i_nicknames, &str2, &str1)) {
		    fputs(", ", stdout);
		    id_fputs(str1, stdout);
		}
	    }
	    putchar(')');
	}
	printf("\n%sA ", indent);
	putz(i_cputype);
	printf(" running ");
	putz(i_opsys);
	putchar('.');
    }
    putchar('\n');
    if (get_item(i_netaddress, &str1)) {
	record = id_recidx;			/* save current record */
	cp = strbuf;				/* use this buffer */
	i = 0;					/* count of # of addresses */
	while (id_siget(i_netaddress, &str1, &str2)) {
	    addresses[i++] = id2c(cp, str2);	/* copy it to a buffer */
	    cp += ID_SLN(str2) + 1;		/* advance pointer */
	}
	if (i) {
	    if (!(mb = get_match_block())) return;
	    for (j = 0; j < i; j++)
		net_display(mb, addresses[j]);
	    free_match_block(mb);		/* done with this */
	    get_record(record);			/* re-get current record */
	}
    }
    d_labeled(i_connectype, "Connect Type", 0);
    d_labeled(i_protocols, NULL, ',');		/* comma is separator */
}

/*
 *	given an A.B.C.D type address string, output that address then
 *	the network it's on following, plus some extra info.
 */

#define CLASS_A		1		/* class A network starts here */
#define CLASS_B		128		/* class B starts here; + 2nd byte */
#define CLASS_C		192		/* class C starts here; + 3rd byte */

void net_display(mb, address)
match_block *mb;
char *address;
{
    int network, host, b3, imp, value;
    char netnum[MAX_LINE];

    if (sscanf(address, "%d.%d.%d.%d", &network, &host, &b3, &imp) != 4)
	return;
    if (network < CLASS_B)
	sprintf(netnum, "%d.0.0.0", network);
    else if (network < CLASS_C)
	sprintf(netnum, "%d.%d.0.0", network, host);
    else sprintf(netnum, "%d.%d.%d.0", network, host, b3);
    printf("%s%s", indent, address);
    match_initialize(mb, netnum, i_netnumber, 0, "N");
    find(mb, i_netnumber);
    if (mb->count == 1 && get_record(mb->recidx[0])) {
	printf(" (");
	putz(i_netname);
	switch (network) {
	    case NET_ARPANET:
	    case NET_MILNET:
		printf(", host %d, IMP %d", host, imp); break;
	    case NET_MIT:
		printf(", %o/%o", host, imp); break;
	}
	putchar(')');
    }
    if (ON(F_NIC_USER)) {
	value = imp + (b3 << 8) + (host << 2*8) + (network << 3*8);
	printf("\t\t[%o,,%o]", (value >> 18), (value & 0777777));
    }
    putchar('\n');
}

/*
 *	middle-level record output routines.  this routines do oft-used
 *	outputs from the current record.
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	output the name and handle of the current record as "name (handle)",
 *	returning the total number of characters written.
 */

int name_and_handle()
{
    idstr_t itmbuf;
    int n = 0;

    if (x_record()) {
	fputs("X:", stdout);
	n += 2;
    }
    if (get_item(i_name, &itmbuf)) {
	id_fputs(itmbuf, stdout);
	n += ID_SLN(itmbuf);
    } else {
	fputs("[No name]", stdout);
	n += 9;
    }
    if (get_item(i_handle, &itmbuf)) {
	fputs(" (", stdout);
	id_fputs(itmbuf, stdout);
	putc(')', stdout);
	n += ID_SLN(itmbuf) + 3;
    } else {
	fputs(" (ID?) ", stdout);
	n += 7;
    }
    return n;				/* # of chars written */
}

/*
 *
 *	this is the standard header for record display.  name, handle,
 *	plus mailbox if an individual, then the address indented starting
 *	at the next line.
 */

void standard_header()
{
    idstr_t itmbuf, mbxbuf;

    name_and_handle();
    if (get_item(i_mailbox, &itmbuf)) {
	id_siget(i_mailbox, &itmbuf, &mbxbuf);
	fputs("\t\t", stdout);
	id_fputs(mbxbuf, stdout);
    } else if (get_htype(NULL) == 'I')
	fputs("\t\t[No mailbox]", stdout);
    putchar('\n');
    if (get_item(i_address, &itmbuf))
	d_indented(itmbuf);
    if (get_item(i_phone, &itmbuf)) {
	if (strchr("\n\r", *ID_SCP(itmbuf)))
	    d_indented(itmbuf);		/* if phone starts with blank line, */
	else {				/* is multi-line phone... */
	    fputs(indent, stdout);
	    id_fputs(itmbuf, stdout);
	    putchar('\n');
	}
    }
}

/*
 *	if comments exist, output a blank line then the indented comments
 */

void comments()
{
    idstr_t itmstr;

    if (get_item(i_comments, &itmstr)) {
	putchar('\n');
	d_indented(itmstr);
    }
}

/*
 *	output the db's label for the given field, or <Item #n> if there's
 *	no label, which shouldn't ever happen.
 */

int label(field)
iditm_t field;
{
    idstr_t itmstr;

    itmstr = id_idfname(field);		/* look up name */
    if (ID_SLN(itmstr)) {		/* if it has a name */
	id_fputs(itmstr, stdout);	/* then let's see it */
	return ID_SLN(itmstr);		/* return length of string */
    } else {
	printf("%03d", field);		/* else do this, something weird! */
	return 3;			/* wrote 3 digits */
    }
}

/*
 *	display the given field indented with "label: " before it.  if
 *	label is NULL, then use the db's label.  if divider exists, then
 *	the text is broken on dividers such that it doesn't wrap across
 *	the right margin.
 */

void d_labeled(field, cp, divider)
iditm_t field;
char *cp, divider;
{
    idstr_t itmbuf;
    char *word_cp;
    int i, len, word_len;

    if (!get_item(field, &itmbuf)) return;	/* no contents. */
    fputs(indent, stdout);			/* indent */
    if (cp) {
	fputs(cp, stdout);			/* if they gave a label, use */
	i = strlen(cp);
    } else i = label(field);			/* it, else use the db's */
    fputs(": ", stdout);
    i += INDENT + 2;				/* account for extra text */
    if (!divider) {				/* if no divider, */
	id_fputs(itmbuf, stdout);		/* assume it's one line */
	putchar('\n');
	return;
    }
    len = ID_SLN(itmbuf);			/* length of text */
    cp = ID_SCP(itmbuf);			/* re-use cp as ptr to field */
    column = i;					/* we're starting here. */
    do {
	word_len = 0;				/* length of portion */
	word_cp = cp;				/* cp to start of portion */
	while (--len >= 0) {
	    word_len++;
	    if (*cp++ == divider) break;
	}
	if (column + word_len > width) {	/* too far? */
	    putchar('\n');			/* yes, get to a new line. */
	    column = 0;				/* reflect that */
	    space(i);				/* indent out again */
	}
	column += word_len;			/* column after writing */
	while (--word_len >= 0)
	    putchar(*word_cp++);		/* output the portion */
    } while (len > 0);				/* while there's source text */
    putchar('\n');				/* get to a new line */
}

/*
 *	display the given text (assumed to be multiple lines) indented
 */

void d_indented(itmbuf)
idstr_t itmbuf;
{
    char c, *cp;
    int len;

    for (cp = ID_SCP(itmbuf), len = ID_SLN(itmbuf);
	 len > 0 && isspace(*cp);
	 cp++, len--)			/* skip leading whitespace */
	;
    while (len > 0) {			/* while there's more to type out */
	fputs(indent, stdout);		/* indent this line */
	while (len-- > 0 && (c = *cp++) != '\r')
	    putchar(c);			/* print line until EOL or no more */
	len--; cp++;			/* skip the LF in the data */
	putchar('\n');
    }
}

/*
 *	low-level output support routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	ask a yes-or-no questions, with interrupts off, so that interaction
 *	with the user resets abortness.
 */

bool yesno(format, va_alist)
char *format;
va_dcl						/* note no semi! */
{
    char buf[MAX_LINE];
    bool result, int_state;

    if (int_state = ON(F_INT_ENABLED))		/* save interrupt status */
	int_off();				/* interrupts of for this */
    sprintf(buf, format, va_alist);		/* make the prompt */
    result = nx_yesno(buf);			/* ask the question */
    if (int_state)				/* if wanted interrupts, */
	int_on();				/* turn them back on now */
    return result;				/* return true life data */
}

/*
 *	this is the routine that was formerly bound to %z in printf()
 */

void putz(item)
iditm_t item;
{
    idstr_t string;

    if (get_item(item, &string))
	id_fputs(string, stdout);
    else
	putc('?', stdout);
}

/*
 *
 *	move over n spaces, using tabs if possible.  if we've already
 *	gone too far (and so passed a negative arg), then do a single
 *	space, just for a break.
 */

#define TAB_WIDTH	8		/* width of a standard tab stop */

void space(n)
int n;
{
    int old_column, pre, body;

    if (n < 0) n = 1;				/* too far, just one then */
    old_column = column;			/* save this */
    column += n;				/* we'll always move */
    pre = TAB_WIDTH - (old_column % TAB_WIDTH);	/* # of spaces to next stop */
    if (pre <= n) {				/* don't if that's too far */
	putchar('\t');				/* get to a starting stop */
	n -= pre;				/* eqv to this many spaces */
	body = n / TAB_WIDTH;			/* need some body tabs? */
	if (body > 0) {
	    n -= (body * TAB_WIDTH);		/* account for them */
	    while (--body >= 0)
		putchar('\t');			/* now splat them out */
	}
    }
    while (--n >= 0) putchar(' ');		/* now space over the rest */
}

/*
 *	low-level database support routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	load the database and define the items in the db_items[] array,
 *	which sets up all the _xxx field locations with the internal
 *	field numbers from the database.
 */

void load_database()
{
    int i;

    if (!id_pkginit(NULL)
      || !id_dbget(ON(F_X_WHOIS) ? X_DB_FILE : DB_FILE)) {
	fprintf(stderr, "Failed to load database: %s", id_errcp);
	exit(0);
    }
    for (i = 0; i < (sizeof(db_items) / sizeof(struct item_record)); i++) {
	if ((*db_items[i].item = id_idfnum(id_strfs(db_items[i].name))) < 0) {
	    fprintf(stderr, "Database doesn't know what a '%s' is!",
		    db_items[i].name);
	    exit(0);
	}
    }
    SET(F_DB_LOADED);
}

/*
 *
 *	make the given record current.  if you want this error to be
 *	continuable, change the exit(0) to a return FALSE;
 */

bool get_record(record)
idrix_t record;
{
    if (record != id_recidx && !id_recget(record)) {
	fprintf(stderr, "Failed to make record current - %s\n", id_errcp);
	exit(0);
    }
    return TRUE;
}

/*
 *	get the given item from the current record, returning TRUE if
 *	it exists and is non-empty.
 */

bool get_item(item, itmbuf)
iditm_t item;
idstr_t *itmbuf;
{
    if (!id_itmget(item, itmbuf) || ID_SLN(*itmbuf) <= 0) {
	*itmbuf = ID_STRLIT("");
	return FALSE;				/* say we didn't get it */
    }
    return TRUE;
}

/*
 *	check to see if a given record is permissable (not an X record,
 *	or an X with X-records are allowed.  returns the real htype if
 *	so, else 0.
 */

int get_htype(record)
idrix_t record;
{
    idstr_t itmstr;
    char *cp;
    int c;

    if (record && record != id_recidx && !get_record(record))
	return 0;
    if (!get_item(i_htype, &itmstr))
	return 0;
    cp = ID_SCP(itmstr);			/* point to htype */
    c = islower(*cp) ? toupper(*cp) : *cp;	/* Damn broken BSD toupper */
    if (c == 'X') {				/* if an X-record */
	if (OFF(F_X_OK)) return 0;		/* sorry, can't see it */
	c = *++cp;				/* get the real htype */
	c = islower(c) ? toupper(c) : c;	/* Damn broken BSD toupper */
    } else if (strchr(INVISIBLE_HTYPES, c))
	return 0;				/* don't see these htypes */
    return c;					/* return the htype. */
}

/*
 *	if the user wants an ARPA- or MIL-only search, check the current
 *	record to see if it's one of those.  returns TRUE if not host-type
 *	record, or host-type and on specified net(s), else FALSE.
 */

bool right_net(mb)
match_block *mb;
{
    idstr_t itmbuf, addrbuf;
    int netnum;

    if (!id_itmget(i_netaddress, &itmbuf) && !id_itmget(i_impnetnumber, &itmbuf))
	return TRUE;				/* then test doesn't apply. */
    while (id_siget(i_netaddress, &itmbuf, &addrbuf)) {
	if (sscanf(ID_SCP(addrbuf), "%d.", &netnum) == 1) {
	    if ((mb->flags & LF_ARPANET) && netnum == NET_ARPANET)
		return TRUE;
	    if ((mb->flags & LF_MILNET) && netnum == NET_MILNET)
		return TRUE;
	}
    }
    return FALSE;
}

/*
 *	return true if the current record is an X-record
 */

bool x_record()
{
    idstr_t itmbuf;

    return get_item(i_htype, &itmbuf)
	&& ( *ID_SCP(itmbuf) == 'X'
	  || *ID_SCP(itmbuf) == 'x') ;
}

/*
 *	given a pointer to a buffer (if NULL, use a single static buffer)
 *	and an idstr_t, turn that idstr_t into a real C string in the buffer,
 *	returning a char* pointer to it
 */

char *id2c(cp, itmbuf)
char *cp;
idstr_t itmbuf;
{
    strncpy(cp, ID_SCP(itmbuf), ID_SLN(itmbuf));
    *(cp + ID_SLN(itmbuf)) = '\0';	/* tie off */
    return cp;				/* return pointer to buf */
}

/*
 *	log file stuff, with support
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	make a log entry, showing what arg was given us
 */

void log_entry(s)
char *s;
{
    FILE *f;
    char *log_file, hostname[MAX_LINE];
    static char tty[MAX_LINE], username[MAX_LINE];
    static int jobnum = -1;

    log_file = ON(F_SERVER) ? SERVER_LOG_FILE : WHOIS_LOG_FILE;
    if (f = fopen(log_file, "a")) {
	timestamp(f);
	if (ON(F_SERVER)) {
	    get_hostname(stdin, hostname);
	    fprintf(f, " [%s](%s)\tArg: \"%s\"\n", hostname, run_mode, s);
	} else {
	    if (jobnum == -1)			/* if don't know it, */
		get_job_info(&jobnum, tty, username);
	    fprintf(f, "  %d %s %s (%s)\tArg: \"%s\"\n",
		    jobnum, tty, username, run_mode, s);
	}
	fclose(f);
    }
}

/*
 *	get the hostname of whose on the other side of stream f and put
 *	it in buf as an official hostname, or b1.b2.b3.b4 if we can't get
 *	the name.
 */

void get_hostname(f, buf)
FILE *f;
char *buf;
{
    int b1, b2, b3, b4;
    unsigned host;
    char *cp;
    struct hostent *p;
#if SYS_T20
    struct xstat xs;
    unsigned int dev;

    if (!xfstat(fileno(f), &xs)) {
      dev = ((unsigned int) xs.st.st_dev) >> 18;
      if (dev == (0600000 + ST_DEV_TCP)) {	/* 6000000 is 20X dev code */
        host = xs.xst_fhost;			/* foreign host# */
	cp = buf;
	*cp = (b1 = (host >> 3*8) & 0377);	/* turn into crufty char */
	*++cp = (b2 = (host >> 2*8) & 0377);	/* array which is how the */
	*++cp = (b3 = (host >> 8) & 0377);	/* gethost stuff likes it */
	*++cp = (b4 = host & 0377);
	*++cp = '\0';
	if (p = gethostbyaddr(buf, 4, AF_INET))	/* if got an official name */
	    strcpy(buf, p->h_name);		/* then copy it to dest */
	else					/* else put the numeric addr */
	    sprintf(buf, "%d.%d.%d.%d", b1, b2, b3, b4);
      }
    }
    else
#endif /* SYS_T20 */
	strcpy(buf, "0.0.0.0");		/* lost big, use this. */
}

/*
 *	Write a standard T20 timestamp to the given stream using ODTIM%.
 *	Returns return value from actual jsys() call.
 */

void timestamp(f)
FILE *f;
{
    time_t now;
    struct tm *ts;

    now = time(0);				/* current time */
    ts = localtime(&now);			/* get now in pieces */
    fprintf(f, "%2d-%s-%02d %02d:%02d:%02d", ts->tm_mday, months[ts->tm_mon],
	ts->tm_year % 100, ts->tm_hour, ts->tm_min, ts->tm_sec);
}

/*
 *	get your job information
 */

void get_job_info(jobnum, tty, username)
int *jobnum;
char *tty, *username;
{
    extern char *ttyname(), *getlogin();
    char *cp;

    *jobnum = getpid()
#if SYS_T20
			& 0777
#endif
				;
    if ((cp = ttyname(0)) == NULL)
	cp = "tty??";
    strcpy(tty, cp);
#if SYS_T20
    cp = tty + strlen(cp) - 1;	/* Get ptr to last char of string */
    if (*cp == ':') *cp = '\0';	/* Remove colon from device name, if any */
#endif

    if (cp = getlogin())
	strcpy(username, cp);
    else sprintf(username, "UID-%d", getuid());
}

/*
 *	miscellaneous support routines
 *
 * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
 *
 *	given cp a pointer to a hostname string, find the host record
 *	which matches that hostname/nickname/address.  Returns TRUE if
 *	the hostname was recognized, and canonicalized to something
 *	other than we what we were given.
 */

bool canonicalize_host(cp, buf, flags)
char *cp, *buf;
unsigned flags;
{
    bool canonicalized = FALSE;
    idstr_t itmbuf;					/* desired field */
    idrix_t old;					/* current record */
    match_block *mb;					/* large */

    if (mb = get_match_block()) {			/* couldnt make one? */
	match_initialize(mb, cp, i_handle, 0, HOST_HTYPES);
	mb->flags = flags;				/* search flags */
	do_host(mb);					/* find 'em! */
	if (mb->count > 1) {
	    printf("The host \"%s\" could be any of these %d:\n\n",
		cp, mb->count);
	    summary(mb, sum_line, TRUE, FALSE);		/* standard stuff */
	    putchar('\n');
	} else if (mb->count == 1) {
	    old = id_recidx;				/* current record */
	    if (get_record(mb->recidx[0]) &&
		    get_item(i_hostname, &itmbuf) &&
		    strnCMP(cp, ID_SCP(itmbuf), ID_SLN(itmbuf))) {
		id2c(buf, itmbuf);			/* get & sequester */
		canonicalized = TRUE;
	    }						/* re-get original */
	    if (old) get_record(old);			/* record. */
	}
	free_match_block(mb);				/* done with this */
    }
    return canonicalized;
}

/*
 *	initialize a match block.
 */

void match_initialize(mb, target, key_field, flags, htypes)
match_block *mb;
char *target, *htypes;
iditm_t key_field;
unsigned flags;
{
    mb->flags = flags;
    mb->htypes = htypes;
    mb->searches = mb->count = mb->key_count = 0;
    mb->key_cp = mb->key_buffer;	/* point cp to start of buffer */
    mb->target = target;		/* set new target string */
    mb->key_field = key_field;		/* set which field to key on */
    cmb = mb;				/* globalize current match_block */
}

/*
 *	initialize a search block.
 */

void search_initialize(fndblk, type, field, string)
idfnd_t *fndblk;
iditm_t field;
char *string;
{
    fndblk->fnd_init = type;
    fndblk->fnd_itm = field;
    fndblk->fnd_str = id_strfs(string);
}

/*
 *	given an idstr_t which is a netaddress, extract the portions of
 *	it which comprise just the netnumber, and write them to the
 *	given buf.  That is, for a class A network, just write A.  For
 *	a class B, write A.B, for a class C, write A.B.C
 */

int net_part(buf, itmstr)
char *buf;
idstr_t itmstr;
{
    int b1, b2, b3;

    id2c(buf, itmstr);				/* copy it to a C buf. */
    if (sscanf(buf, "%d.%d.%d.", &b1, &b2, &b3) != 3)
	return -1;
    if (b1 >= CLASS_C) sprintf(buf, "%d.%d.%d", b1, b2, b3);
    else if (b1 >= CLASS_B) sprintf(buf, "%d.%d", b1, b2);
    else sprintf(buf, "%d", b1);
    return b1;
}

/*
 *	ask a "want to see more?" type question.   this constucts a prompt,
 *	and asks the user a yes/no question, returning the truth of their
 *	answer.
 */

bool see_more(mb, server_msg, user_msg)
match_block *mb;
char *server_msg, *user_msg;
{
    if (!(mb->flags & (LF_SUBDISPLAY | LF_EXPAND))) {
	if (OFF(F_INTERACTIVE)) {
	    puts(server_msg);				/* how to use '*' */
	    return FALSE;
	} else {
	    if (!yesno("Would you like to see the %s? ", (int) user_msg))
		return FALSE;				/* no! */
	    putchar('\n');
	}
    }
    return TRUE;						/* yes! */
}

/*
 *	sort the given match_block by the strings pointed to by keys[]
 */

void sort_matches(mb)
match_block *mb;
{
    sorting_mb = mb;			/* make sure this is globalized */
    qsort((char *) mb->records, mb->count, sizeof(int), qcmp);
}

/*
 *
 *	routine called by qsort to compare two entries.  s1 and s2 are
 *	pointers into the records[] array, which contains indicies into
 *	recidx[] and keys[].
 */

int qcmp(s1, s2)
char *s1, *s2;
{
    return strCMP(sorting_mb->keys[*((int *) s1)],
		  sorting_mb->keys[*((int *) s2)]);
}

/*
 *	allocate a match block
 */

match_block *get_match_block()
{
    match_block *mb;

    if (!(mb = (match_block *) malloc(sizeof(match_block)))) {
	fputs("Couldn't allocate enough memory for match block!\n", stderr);
	return NULL;
    }
    return mb;
}

/*
 *	de-allocate a match-block
 */

void free_match_block(mb)
match_block *mb;
{
    if (cmb == mb)	/* if this used to be the current match */
	cmb = NULL;	/* block, then it's going away now.... */
    free((char *) mb);
}

/*
 *	see if there's more JCL.  if there is, read it into the given
 *	buffer.
 */

char *get_jcl(buf)
char *buf;
{
#if SYS_T20
    int n, ablock[5];
    char *p;

    ablock[1] = _RSINI;			/* make data available as priin */
    if (!jsys(RSCAN, ablock))
	return NULL;
    ablock[1] = _RSCNT;			/* return # of chars in RSCAN buf */
    if (!jsys(RSCAN, ablock) | ((n = ablock[1]) < 1))
	return NULL;			/* failed or no chars */
    for (p = buf; --n >= 0; p++)
	*p = getchar();			/* read from priin and stuff in buf */
    *--p = '\0';			/* remove trailing LF & tie off buf. */
    for (p = buf; *p; p++)
	if (*p == ' ')			/* if has a space, */
	    return ++p;			/* arg comes next. */
#endif
    return NULL;
}

/*
 *	Interrupt stuff
 */

void int_on()
{
    int_clear();
    SET(F_INT_ENABLED);			/* interrupts in actions now */
}

void int_off()
{
    CLEAR(F_INT_ENABLED);
    int_clear();
}

void int_clear()
{
    id_abortf = FALSE;			/* reset abortness flags */
    CLEAR(F_ABORT);
}

#define ABORT_MESSAGE	" *Aborting*\n"

void int_abort()
{
    if (ON(F_INT_ENABLED) && OFF(F_ABORT)) {
	id_abortf = TRUE;		/* set that user wants to abort */
	SET(F_ABORT);
	write(fileno(stderr), ABORT_MESSAGE, sizeof(ABORT_MESSAGE) - 1);
    }
}

#define FORMAT_PREFIX		
#define plural(n)		n, (n == 1) ? "" : "es"

void int_status()
{
    if (ON(F_INT_ENABLED) && cmb) {
	char *p, buf[MAX_LINE];
	int length1, length2 = 0;
	time_t now;

	now = time(NULL);		/* get current time, turn into str */
	sprintf(buf, " %.8s -- ", ctime(&now) + 11);    /* HH:MM:SS */
	p = buf + (length1 = strlen(buf));	/* point to tail to string */
	if (!cmb->count)
	    strcpy(p, "No matches yet");
	else
	    sprintf(p, "%d match%s so far", plural(cmb->count));
	sprintf(p += (length2 = strlen(p)), " in %d search%s.\n",
	    plural(cmb->searches));
	write(fileno(stderr), buf, length1 + length2 + strlen(p));
    }
}
