fbmuck-6.05/auto/
fbmuck-6.05/contrib/jresolver/
fbmuck-6.05/contrib/jresolver/org/
fbmuck-6.05/contrib/jresolver/org/fuzzball/
fbmuck-6.05/docs/devel/
fbmuck-6.05/game/
fbmuck-6.05/game/logs/
fbmuck-6.05/game/muf/
fbmuck-6.05/scripts/
fbmuck-6.05/src_docs/
/* mcp.c: MUD Client Protocol.
   Part of the FuzzBall distribution. */

#include "config.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include "db.h"
#include "externs.h"
#include "mcp.h"
#include "mcppkg.h"
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif /* HAVE_MALLOC_H */


#define MCP_MESG_PREFIX		"#$#"
#define MCP_QUOTE_PREFIX	"#$\""
#define MCP_ARG_EMPTY		"\"\""
#define MCP_INIT_PKG		"mcp"
#define MCP_DATATAG			"_data-tag"
#define MCP_INIT_MESG		"mcp "
#define MCP_NEGOTIATE_PKG	"mcp-negotiate"





/* Defined elsewhere.  Used to send text to a connection */
void SendText(McpFrame * mfr, const char *mesg);
void FlushText(McpFrame * mfr);


McpPkg *mcp_PackageList = NULL;



int
strcmp_nocase(const char *s1, const char *s2)
{
	while (*s1 && tolower(*s1) == tolower(*s2))
		s1++, s2++;
	return (tolower(*s1) - tolower(*s2));
}



int
strncmp_nocase(const char *s1, const char *s2, int cnt)
{
	while (cnt && *s1 && tolower(*s1) == tolower(*s2))
		s1++, s2++, cnt--;
	if (!cnt) {
		return 0;
	} else {
		return (tolower(*s1) - tolower(*s2));
	}
}


/* Used internally to escape and quote argument values. */
int
msgarg_escape(char* buf, int bufsize, const char* in)
{
	char *out = buf;
	int len = 0;

	if (bufsize < 3) {
		return 0;
	}
	*out++ = '"';
	len++;
	while (*in && len < bufsize - 3) {
		if (*in == '"' || *in == '\\') {
			*out++ = '\\';
			len++;
		}
		*out++ = *in++;
		len++;
	}
	*out++ = '"';
	*out = '\0';
	len++;

	return len;
}


int mcp_internal_parse(McpFrame * mfr, const char *in);



/*****************************************************************/
/***                          ************************************/
/*** MCP PACKAGE REGISTRATION ************************************/
/***                          ************************************/
/*****************************************************************/



/*****************************************************************
 *
 * void mcp_package_register(
 *              const char* pkgname,
 *              McpVer minver,
 *              McpVer maxver,
 *              McpPkg_CB callback,
 *              void* context,
 *              ContextCleanup_CB cleanup
 *          );
 *
 *
 *****************************************************************/

void
mcp_package_register(const char *pkgname, McpVer minver, McpVer maxver, McpPkg_CB callback,
					 void *context, ContextCleanup_CB cleanup)
{
	McpPkg *nu = (McpPkg *) malloc(sizeof(McpPkg));

	nu->pkgname = (char *) malloc(strlen(pkgname) + 1);
	strcpy(nu->pkgname, pkgname);
	nu->minver = minver;
	nu->maxver = maxver;
	nu->callback = callback;
	nu->context = context;
	nu->cleanup = cleanup;

	mcp_package_deregister(pkgname);
	nu->next = mcp_PackageList;
	mcp_PackageList = nu;
	mcp_frame_package_renegotiate(pkgname);
}






/*****************************************************************
 *
 * void mcp_package_deregister(
 *              const char* pkgname,
 *          );
 *
 *
 *****************************************************************/

void
mcp_package_deregister(const char *pkgname)
{
	McpPkg *ptr = mcp_PackageList;
	McpPkg *prev = NULL;

	while (ptr && !strcmp_nocase(ptr->pkgname, pkgname)) {
		mcp_PackageList = ptr->next;
		if (ptr->cleanup)
			ptr->cleanup(ptr->context);
		if (ptr->pkgname)
			free(ptr->pkgname);
		free(ptr);
		ptr = mcp_PackageList;
	}

	prev = mcp_PackageList;
	if (ptr)
		ptr = ptr->next;

	while (ptr) {
		if (!strcmp_nocase(pkgname, ptr->pkgname)) {
			prev->next = ptr->next;
			if (ptr->cleanup)
				ptr->cleanup(ptr->context);
			if (ptr->pkgname)
				free(ptr->pkgname);
			free(ptr);
			ptr = prev->next;
		} else {
			prev = ptr;
			ptr = ptr->next;
		}
	}
	mcp_frame_package_renegotiate(pkgname);
}








/*****************************************************************/
/***                    ******************************************/
/*** MCP INITIALIZATION ******************************************/
/***                    ******************************************/
/*****************************************************************/


void mcp_basic_handler(McpFrame * mfr, McpMesg * mesg, void *dummy);
void mcp_negotiate_handler(McpFrame * mfr, McpMesg * mesg, McpVer version, void *dummy);
void mcppkg_simpleedit(McpFrame * mfr, McpMesg * mesg, McpVer ver, void *context);


/*****************************************************************
 *
 * void mcp_initialize();
 *
 *   Initializes MCP globally at startup time.
 *
 *****************************************************************/

void
mcp_initialize(void)
{
	McpVer oneoh = { 1, 0 };
	McpVer twooh = { 2, 0 };

	/* McpVer twoone = {2,1}; */

	mcp_package_register(MCP_NEGOTIATE_PKG, oneoh, twooh, mcp_negotiate_handler, NULL, NULL);
	mcp_package_register("org-fuzzball-help", oneoh, oneoh, mcppkg_help_request, NULL, NULL);
	mcp_package_register("org-fuzzball-notify", oneoh, oneoh, mcppkg_simpleedit, NULL, NULL);
	mcp_package_register("org-fuzzball-simpleedit", oneoh, oneoh, mcppkg_simpleedit, NULL, NULL);
	mcp_package_register("dns-org-mud-moo-simpleedit", oneoh, oneoh, mcppkg_simpleedit, NULL, NULL);
}




/*****************************************************************
 *
 * void mcp_negotiation_start(McpFrame* mfr);
 *
 *   Starts MCP negotiations, if any are to be had.
 *
 *****************************************************************/

void
mcp_negotiation_start(McpFrame * mfr)
{
	McpMesg reply;

	mfr->enabled = 1;
	mcp_mesg_init(&reply, MCP_INIT_PKG, "");
	mcp_mesg_arg_append(&reply, "version", "2.1");
	mcp_mesg_arg_append(&reply, "to", "2.1");
	mcp_frame_output_mesg(mfr, &reply);
	mcp_mesg_clear(&reply);
	mfr->enabled = 0;
}







/*****************************************************************/
/***                       ***************************************/
/*** MCP CONNECTION FRAMES ***************************************/
/***                       ***************************************/
/*****************************************************************/

struct McpFrameList_t {
	McpFrame* mfr;
	struct McpFrameList_t* next;
};
typedef struct McpFrameList_t McpFrameList;
McpFrameList* mcp_frame_list;


/*****************************************************************
 *
 * void mcp_frame_init(McpFrame* mfr, connection_t con);
 *
 *   Initializes an McpFrame for a new connection.
 *   You MUST call this to initialize a new McpFrame.
 *   The caller is responsible for the allocation of the McpFrame.
 *
 *****************************************************************/

void
mcp_frame_init(McpFrame * mfr, connection_t con)
{
	McpFrameList* mfrl;

	mfr->descriptor = con;
	mfr->version.verminor = 0;
	mfr->version.vermajor = 0;
	mfr->authkey = NULL;
	mfr->packages = NULL;
	mfr->messages = NULL;
	mfr->enabled = 0;

	mfrl = (McpFrameList*)malloc(sizeof(McpFrameList));
	mfrl->mfr = mfr;
	mfrl->next = mcp_frame_list;
	mcp_frame_list = mfrl;
}





/*****************************************************************
 *
 * void mcp_frame_clear(McpFrame* mfr);
 *
 *   Cleans up an McpFrame for a closing connection.
 *   You MUST call this when you are done using an McpFrame.
 *
 *****************************************************************/

void
mcp_frame_clear(McpFrame * mfr)
{
	McpPkg *tmp = mfr->packages;
	McpMesg *tmp2 = mfr->messages;
	McpFrameList* mfrl = mcp_frame_list;
	McpFrameList* prev;

	if (mfr->authkey) {
	        free(mfr->authkey);
	        mfr->authkey = NULL;
	}
	while (tmp) {
		mfr->packages = tmp->next;
		if (tmp->pkgname)
			free(tmp->pkgname);
		free(tmp);
		tmp = mfr->packages;
	}
	while (tmp2) {
		mfr->messages = tmp2->next;
		mcp_mesg_clear(tmp2);
		free(tmp2);
		tmp2 = mfr->messages;
	}

	while (mfrl && mfrl->mfr == mfr) {
		mcp_frame_list = mfrl->next;
		free(mfrl);
		mfrl = mcp_frame_list;
	}
	if (!mcp_frame_list) {
		return;
	}
	prev = mcp_frame_list;
	mfrl = prev->next;
	while (mfrl) {
		if (mfrl->mfr == mfr) {
			prev->next = mfrl->next;
			free(mfrl);
			mfrl = prev->next;
		} else {
			prev = mfrl;
			mfrl = mfrl->next;
		}
	}
}





/*****************************************************************
 *
 * void mcp_frame_package_renegotiate(
 *              McpFrame* mfr,
 *              char* package
 *          );
 *
 *   Removes a package from the list of supported packages
 *   for all McpFrames, and initiates renegotiation of that
 *   package.
 *
 *****************************************************************/

void
mcp_frame_package_renegotiate(const char* package)
{
	McpVer nullver = { 0, 0 };
	McpFrameList* mfrl = mcp_frame_list;
	McpFrame* mfr;
	McpMesg cando;
	char verbuf[32];
	McpPkg *p;

	p = mcp_PackageList;
	while (p) {
		if (!strcmp_nocase(p->pkgname, package)) {
			break;
		}
		p = p->next;
	}

	if (!p) {
		mcp_mesg_init(&cando, MCP_NEGOTIATE_PKG, "can");
		mcp_mesg_arg_append(&cando, "package", package);
		mcp_mesg_arg_append(&cando, "min-version", "0.0");
		mcp_mesg_arg_append(&cando, "max-version", "0.0");
	} else {
		mcp_mesg_init(&cando, MCP_NEGOTIATE_PKG, "can");
		mcp_mesg_arg_append(&cando, "package", p->pkgname);
		snprintf(verbuf, sizeof(verbuf), "%d.%d", p->minver.vermajor, p->minver.verminor);
		mcp_mesg_arg_append(&cando, "min-version", verbuf);
		snprintf(verbuf, sizeof(verbuf), "%d.%d", p->maxver.vermajor, p->maxver.verminor);
		mcp_mesg_arg_append(&cando, "max-version", verbuf);
	}

	while (mfrl) {
		mfr = mfrl->mfr;
		if (mfr->enabled) {
			if (mcp_version_compare(mfr->version, nullver) > 0) {
				mcp_frame_package_remove(mfr, package);
				mcp_frame_output_mesg(mfr, &cando);
			}
		}
		mfrl = mfrl->next;
	}
	mcp_mesg_clear(&cando);

	/*
	mcp_mesg_init(&cando, MCP_NEGOTIATE_PKG, "end");
	mcp_frame_output_mesg(mfr, &cando);
	mcp_mesg_clear(&cando);
	*/
}






/*****************************************************************
 *
 * void mcp_frame_package_add(
 *              McpFrame* mfr,
 *              const char* package,
 *              McpVer minver,
 *              McpVer maxver
 *          );
 *
 *   Attempt to register a package for this connection.
 *   Returns EMCP_SUCCESS if the package was deemed supported.
 *   Returns EMCP_NOMCP if MCP is not supported on this connection.
 *   Returns EMCP_NOPACKAGE if the package versions didn't overlap.
 *
 *****************************************************************/

int
mcp_frame_package_add(McpFrame * mfr, const char *package, McpVer minver, McpVer maxver)
{
	McpPkg *nu;
	McpPkg *ptr;
	McpVer selver;

	if (!mfr->enabled) {
		return EMCP_NOMCP;
	}

	for (ptr = mcp_PackageList; ptr; ptr = ptr->next) {
		if (!strcmp_nocase(ptr->pkgname, package)) {
			break;
		}
	}
	if (!ptr) {
		return EMCP_NOPACKAGE;
	}

	mcp_frame_package_remove(mfr, package);

	selver = mcp_version_select(ptr->minver, ptr->maxver, minver, maxver);
	nu = (McpPkg *) malloc(sizeof(McpPkg));
	nu->pkgname = (char *) malloc(strlen(ptr->pkgname) + 1);
	strcpy(nu->pkgname, ptr->pkgname);
	nu->minver = selver;
	nu->maxver = selver;
	nu->callback = ptr->callback;
	nu->context = ptr->context;
	nu->next = NULL;

	if (!mfr->packages) {
		mfr->packages = nu;
	} else {
		McpPkg *lastpkg = mfr->packages;

		while (lastpkg->next)
			lastpkg = lastpkg->next;
		lastpkg->next = nu;
	}
	return EMCP_SUCCESS;
}





/*****************************************************************
 *
 * void mcp_frame_package_remove(
 *              McpFrame* mfr,
 *              char* package
 *          );
 *
 *   Removes a package from the list of supported packages
 *   for this McpFrame.
 *
 *****************************************************************/

void
mcp_frame_package_remove(McpFrame * mfr, const char *package)
{
	McpPkg *tmp;
	McpPkg *prev;

	while (mfr->packages && !strcmp_nocase(mfr->packages->pkgname, package)) {
		tmp = mfr->packages;
		mfr->packages = tmp->next;
		if (tmp->pkgname)
			free(tmp->pkgname);
		free(tmp);
	}

	prev = mfr->packages;
	while (prev && prev->next) {
		if (!strcmp_nocase(prev->next->pkgname, package)) {
			tmp = prev->next;
			prev->next = tmp->next;
			if (tmp->pkgname)
				free(tmp->pkgname);
			free(tmp);
		} else {
			prev = prev->next;
		}
	}
}





/*****************************************************************
 *
 * McpVer mcp_frame_package_supported(
 *              McpFrame* mfr,
 *              char* package
 *          );
 *
 *   Returns the supported version of the given package.
 *   Returns {0,0} if the package is not supported.
 *
 *****************************************************************/

McpVer
mcp_frame_package_supported(McpFrame * mfr, const char *package)
{
	McpPkg *ptr;
	McpVer errver = { 0, 0 };

	if (!mfr->enabled) {
		return errver;
	}

	for (ptr = mfr->packages; ptr; ptr = ptr->next) {
		if (!strcmp_nocase(ptr->pkgname, package)) {
			break;
		}
	}
	if (!ptr) {
		return errver;
	}

	return (ptr->minver);
}





/*****************************************************************
 *
 * void mcp_frame_package_docallback(
 *              McpFrame* mfr,
 *              McpMesg* msg
 *          );
 *
 *   Executes the callback function for the given message.
 *   Returns EMCP_SUCCESS if the call completed successfully.
 *   Returns EMCP_NOMCP if MCP is not supported for that connection.
 *   Returns EMCP_NOPACKAGE if the package is not supported.
 *
 *****************************************************************/

int
mcp_frame_package_docallback(McpFrame * mfr, McpMesg * msg)
{
	McpPkg *ptr = NULL;

	if (!strcmp_nocase(msg->package, MCP_INIT_PKG)) {
		mcp_basic_handler(mfr, msg, NULL);
	} else {
		if (!mfr->enabled) {
			return EMCP_NOMCP;
		}

		for (ptr = mfr->packages; ptr; ptr = ptr->next) {
			if (!strcmp_nocase(ptr->pkgname, msg->package)) {
				break;
			}
		}
		if (!ptr) {
			if (!strcmp_nocase(msg->package, MCP_NEGOTIATE_PKG)) {
				McpVer twooh = { 2, 0 };

				mcp_negotiate_handler(mfr, msg, twooh, NULL);
			} else {
				return EMCP_NOPACKAGE;
			}
		} else {
			ptr->callback(mfr, msg, ptr->maxver, ptr->context);
		}
	}
	return EMCP_SUCCESS;
}





/*****************************************************************
 *
 * int mcp_frame_process_input(
 *           McpFrame* mfr,
 *           const char* linein,
 *           char *outbuf,
 *           int bufsize
 *      );
 *
 *   Check a line of input for MCP commands.
 *   Returns 0 if the line was an out-of-band MCP message.
 *   Returns 1 if the line was in-band data.
 *     outbuf will contain the in-band data on return, if any.
 *
 *****************************************************************/

int
mcp_frame_process_input(McpFrame * mfr, const char *linein, char *outbuf, int bufsize)
{
	if (!strncmp_nocase(linein, MCP_MESG_PREFIX, 3)) {
		/* treat it as an out-of-band message, and parse it. */
		if (mfr->enabled || !strncmp_nocase(MCP_INIT_MESG, linein + 3, 4)) {
			if (!mcp_internal_parse(mfr, linein + 3)) {
				strncpy(outbuf, linein, bufsize);
				outbuf[bufsize - 1] = '\0';
				return 1;
			}
			return 0;
		} else {
			strncpy(outbuf, linein, bufsize);
			outbuf[bufsize - 1] = '\0';
			return 1;
		}
	} else if (mfr->enabled && !strncmp_nocase(linein, MCP_QUOTE_PREFIX, 3)) {
		/* It's quoted in-band data.  Strip the quoting. */
		strncpy(outbuf, linein + 3, bufsize);
	} else {
		/* It's in-band data.  Return it raw. */
		strncpy(outbuf, linein, bufsize);
	}
	outbuf[bufsize - 1] = '\0';
	return 1;
}





/*****************************************************************
 *
 * void mcp_frame_output_inband(
 *             McpFrame* mfr,
 *             const char* lineout
 *         );
 *
 *   Sends a string to the given connection, using MCP escaping
 *     if needed and supported.
 *
 *****************************************************************/

void
mcp_frame_output_inband(McpFrame * mfr, const char *lineout)
{
	if (!mfr->enabled ||
		(strncmp(lineout, MCP_MESG_PREFIX, 3) && strncmp(lineout, MCP_QUOTE_PREFIX, 3))
			) {
		SendText(mfr, lineout);
	} else {
		SendText(mfr, MCP_QUOTE_PREFIX);
		SendText(mfr, lineout);
	}
	/* SendText(mfr, "\r\n"); */
}






/*****************************************************************
 *
 * int mcp_frame_output_mesg(
 *             McpFrame* mfr,
 *             McpMesg* msg
 *         );
 *
 *   Sends an MCP message to the given connection.
 *   Returns EMCP_SUCCESS if successful.
 *   Returns EMCP_NOMCP if MCP isn't supported on this connection.
 *   Returns EMCP_NOPACKAGE if this connection doesn't support the needed package.
 *
 *****************************************************************/

int
mcp_frame_output_mesg(McpFrame * mfr, McpMesg * msg)
{
	char outbuf[BUFFER_LEN * 2];
	int bufrem = sizeof(outbuf);
	char mesgname[128];
	char datatag[32];
	McpArg *anarg = NULL;
	int mlineflag = 0;
	char *p;
	char *out;
	int flushcount = 8;

	if (!mfr->enabled && strcmp_nocase(msg->package, MCP_INIT_PKG)) {
		return EMCP_NOMCP;
	}

	/* Create the message name from the package and message subnames */
	if (msg->mesgname && *msg->mesgname) {
		snprintf(mesgname, sizeof(mesgname), "%s-%s", msg->package, msg->mesgname);
	} else {
		snprintf(mesgname, sizeof(mesgname), "%s", msg->package);
	}

	strcpy(outbuf, MCP_MESG_PREFIX);
	strcatn(outbuf, sizeof(outbuf), mesgname);
	if (strcmp_nocase(mesgname, MCP_INIT_PKG)) {
		McpVer nullver = { 0, 0 };

		strcatn(outbuf, sizeof(outbuf), " ");
		strcatn(outbuf, sizeof(outbuf), mfr->authkey);
		if (strcmp_nocase(msg->package, MCP_NEGOTIATE_PKG)) {
			McpVer ver = mcp_frame_package_supported(mfr, msg->package);

			if (!mcp_version_compare(ver, nullver)) {
				return EMCP_NOPACKAGE;
			}
		}
	}

	/* If the argument lines contain newlines, split them into separate lines. */
	for (anarg = msg->args; anarg; anarg = anarg->next) {
		if (anarg->value) {
			McpArgPart *ap = anarg->value;

			while (ap) {
				p = ap->value;
				while (*p) {
					if (*p == '\n' || *p == '\r') {
						McpArgPart *nu = (McpArgPart *) malloc(sizeof(McpArgPart));

						nu->next = ap->next;
						ap->next = nu;
						*p++ = '\0';
						nu->value = (char *) malloc(strlen(p) + 1);
						strcpy(nu->value, p);
						ap->value = (char *) realloc(ap->value, strlen(ap->value) + 1);
						ap = nu;
						p = nu->value;
					} else {
						p++;
					}
				}
				ap = ap->next;
			}
		}
	}

	/* Build the initial message string */
	out = outbuf;
	bufrem = outbuf + sizeof(outbuf) - out;
	for (anarg = msg->args; anarg; anarg = anarg->next) {
		out += strlen(out);
		bufrem = outbuf + sizeof(outbuf) - out;
		if (!anarg->value) {
			anarg->was_shown = 1;
			snprintf(out, bufrem, " %s: %s", anarg->name, MCP_ARG_EMPTY);
			out += strlen(out);
			bufrem = outbuf + sizeof(outbuf) - out;
		} else {
			int totlen = strlen(anarg->value->value) + strlen(anarg->name) + 5;

			if (anarg->value->next || totlen > ((BUFFER_LEN - (out - outbuf)) / 2)) {
				/* Value is multi-line or too long.  Send on separate line(s). */
				mlineflag = 1;
				anarg->was_shown = 0;
				snprintf(out, bufrem, " %s*: %s", anarg->name, MCP_ARG_EMPTY);
			} else {
				anarg->was_shown = 1;
				snprintf(out, bufrem, " %s: ", anarg->name);
				out += strlen(out);
				bufrem = outbuf + sizeof(outbuf) - out;

				msgarg_escape(out, bufrem, anarg->value->value);
				out += strlen(out);
				bufrem = outbuf + sizeof(outbuf) - out;
			}
			out += strlen(out);
			bufrem = outbuf + sizeof(outbuf) - out;
		}
	}

	/* If the message is multi-line, make sure it has a _data-tag field. */
	if (mlineflag) {
		snprintf(datatag, sizeof(datatag), "%.8lX", (unsigned long)(RANDOM() ^ RANDOM()));
		snprintf(out, bufrem, " %s: %s", MCP_DATATAG, datatag);
		out += strlen(out);
		bufrem = outbuf + sizeof(outbuf) - out;
	}

	/* Send the initial line. */
	SendText(mfr, outbuf);
	SendText(mfr, "\r\n");

	if (mlineflag) {
		/* Start sending arguments whose values weren't already sent. */
		/* This is usually just multi-line argument values. */
		for (anarg = msg->args; anarg; anarg = anarg->next) {
			if (!anarg->was_shown) {
				McpArgPart *ap = anarg->value;

				while (ap) {
					*outbuf = '\0';
					snprintf(outbuf, sizeof(outbuf), "%s* %s %s: %s", MCP_MESG_PREFIX, datatag, anarg->name, ap->value);
					SendText(mfr, outbuf);
					SendText(mfr, "\r\n");
					if (!--flushcount) {
						FlushText(mfr);
						flushcount = 8;
					}
					ap = ap->next;
				}
			}
		}

		/* Let the other side know we're done sending multi-line arg vals. */
		snprintf(outbuf, sizeof(outbuf), "%s: %s", MCP_MESG_PREFIX, datatag);
		SendText(mfr, outbuf);
		SendText(mfr, "\r\n");
	}

	return EMCP_SUCCESS;
}









/*****************************************************************/
/***                   *******************************************/
/***  MESSAGE METHODS  *******************************************/
/***                   *******************************************/
/*****************************************************************/



/*****************************************************************
 *
 * void mcp_mesg_init(
 *         McpMesg*    msg,
 *         const char* package,
 *         const char* mesgname
 *     );
 *
 *   Initializes an MCP message.
 *   You MUST initialize a message before using it.
 *   You MUST also mcp_mesg_clear() a mesg once you are done using it.
 *
 *****************************************************************/

void
mcp_mesg_init(McpMesg * msg, const char *package, const char *mesgname)
{
	msg->package = (char *) malloc(strlen(package) + 1);
	strcpy(msg->package, package);
	msg->mesgname = (char *) malloc(strlen(mesgname) + 1);
	strcpy(msg->mesgname, mesgname);
	msg->datatag = NULL;
	msg->args = NULL;
	msg->incomplete = 0;
	msg->bytes = 0;
	msg->next = NULL;
}




/*****************************************************************
 *
 * void mcp_mesg_clear(
 *              McpMesg* msg
 *          );
 *
 *   Clear the given MCP message.
 *   You MUST clear every message after you are done using it, to
 *     free the memory used by the message.
 *
 *****************************************************************/

void
mcp_mesg_clear(McpMesg * msg)
{
	if (msg->package)
		free(msg->package);
	if (msg->mesgname)
		free(msg->mesgname);
	if (msg->datatag)
		free(msg->datatag);

	while (msg->args) {
		McpArg *tmp = msg->args;

		msg->args = tmp->next;
		if (tmp->name)
			free(tmp->name);
		while (tmp->value) {
			McpArgPart *ptr2 = tmp->value;

			tmp->value = tmp->value->next;
			if (ptr2->value)
				free(ptr2->value);
			free(ptr2);
		}
		free(tmp);
	}
	msg->bytes = 0;
}









/*****************************************************************/
/***                  ********************************************/
/*** ARGUMENT METHODS ********************************************/
/***                  ********************************************/
/*****************************************************************/



/*****************************************************************
 *
 * int mcp_mesg_arg_linecount(
 *         McpMesg* msg,
 *         const char* name
 *     );
 *
 *   Returns the count of the number of lines in the given arg of
 *   the given message.
 *
 *****************************************************************/

int
mcp_mesg_arg_linecount(McpMesg * msg, const char *name)
{
	McpArg *ptr = msg->args;
	int cnt = 0;

	while (ptr && strcmp_nocase(ptr->name, name)) {
		ptr = ptr->next;
	}
	if (ptr) {
		McpArgPart *ptr2 = ptr->value;

		while (ptr2) {
			ptr2 = ptr2->next;
			cnt++;
		}
	}
	return cnt;
}




/*****************************************************************
 *
 * char* mcp_mesg_arg_getline(
 *         McpMesg* msg,
 *         const char* argname
 *         int linenum;
 *     );
 *
 *   Gets the value of a named argument in the given message.
 *
 *****************************************************************/

char *
mcp_mesg_arg_getline(McpMesg * msg, const char *argname, int linenum)
{
	McpArg *ptr = msg->args;

	while (ptr && strcmp_nocase(ptr->name, argname)) {
		ptr = ptr->next;
	}
	if (ptr) {
		McpArgPart *ptr2 = ptr->value;

		while (linenum > 0 && ptr2) {
			ptr2 = ptr2->next;
			linenum--;
		}
		if (ptr2) {
			return (ptr2->value);
		}
		return NULL;
	}
	return NULL;
}




/*****************************************************************
 *
 * int mcp_mesg_arg_append(
 *         McpMesg* msg,
 *         const char* argname,
 *         const char* argval
 *     );
 *
 *   Appends to the list value of the named arg in the given mesg.
 *   If that named argument doesn't exist yet, it will be created.
 *   This is used to construct arguments that have lists as values.
 *   Returns the success state of the call.  EMCP_SUCCESS if the
 *   call was successful.  EMCP_ARGCOUNT if this would make too
 *   many arguments in the message.  EMCP_ARGLENGTH is this would
 *   cause an argument to exceed the max allowed number of lines.
 *
 *****************************************************************/

int
mcp_mesg_arg_append(McpMesg * msg, const char *argname, const char *argval)
{
	McpArg *ptr = msg->args;
	int namelen = strlen(argname);
	int vallen = argval? strlen(argval) : 0;

	if (namelen > MAX_MCP_ARGNAME_LEN) {
		return EMCP_ARGNAMELEN;
	}
	if (vallen + msg->bytes > MAX_MCP_MESG_SIZE) {
		return EMCP_MESGSIZE;
	}
	while (ptr && strcmp_nocase(ptr->name, argname)) {
		ptr = ptr->next;
	}
	if (!ptr) {
		if (namelen + vallen + msg->bytes > MAX_MCP_MESG_SIZE) {
			return EMCP_MESGSIZE;
		}
		ptr = (McpArg *) malloc(sizeof(McpArg));
		ptr->name = (char *) malloc(namelen + 1);
		strcpy(ptr->name, argname);
		ptr->value = NULL;
		ptr->last = NULL;
		ptr->next = NULL;
		if (!msg->args) {
			msg->args = ptr;
		} else {
			int limit = MAX_MCP_MESG_ARGS;
			McpArg *lastarg = msg->args;

			while (lastarg->next) {
				if (limit-- <= 0) {
					free(ptr->name);
					free(ptr);
					return EMCP_ARGCOUNT;
				}
				lastarg = lastarg->next;
			}
			lastarg->next = ptr;
		}
		msg->bytes += sizeof(McpArg) + namelen + 1;
	}

	if (argval) {
		McpArgPart *nu = (McpArgPart *) malloc(sizeof(McpArgPart));

		nu->value = (char *) malloc(vallen + 1);
		strcpy(nu->value, argval);
		nu->next = NULL;

		if (!ptr->last) {
			ptr->value = ptr->last = nu;
		} else {
			ptr->last->next = nu;
			ptr->last = nu;
		}
		msg->bytes += sizeof(McpArgPart) + vallen + 1;
	}
	ptr->was_shown = 0;
	return EMCP_SUCCESS;
}




/*****************************************************************
 *
 * void mcp_mesg_arg_remove(
 *         McpMesg* msg,
 *         const char* argname
 *     );
 *
 *   Removes the named argument from the given message.
 *
 *****************************************************************/

void
mcp_mesg_arg_remove(McpMesg * msg, const char *argname)
{
	McpArg *ptr = msg->args;
	McpArg *prev = NULL;

	while (ptr && !strcmp_nocase(ptr->name, argname)) {
		msg->args = ptr->next;
		msg->bytes -= sizeof(McpArg);
		if (ptr->name) {
			free(ptr->name);
			msg->bytes -= strlen(ptr->name) + 1;
		}
		while (ptr->value) {
			McpArgPart *ptr2 = ptr->value;

			ptr->value = ptr->value->next;
			msg->bytes -= sizeof(McpArgPart);
			if (ptr2->value) {
				msg->bytes -= strlen(ptr2->value) + 1;
				free(ptr2->value);
			}
			free(ptr2);
		}
		free(ptr);
		ptr = msg->args;
	}

	prev = msg->args;
	if (ptr)
		ptr = ptr->next;

	while (ptr) {
		if (!strcmp_nocase(argname, ptr->name)) {
			prev->next = ptr->next;
			msg->bytes -= sizeof(McpArg);
			if (ptr->name) {
				free(ptr->name);
				msg->bytes -= strlen(ptr->name) + 1;
			}
			while (ptr->value) {
				McpArgPart *ptr2 = ptr->value;

				ptr->value = ptr->value->next;
				msg->bytes -= sizeof(McpArgPart);
				if (ptr2->value) {
					msg->bytes -= strlen(ptr2->value) + 1;
					free(ptr2->value);
				}
				free(ptr2);
			}
			free(ptr);
			ptr = prev->next;
		} else {
			prev = ptr;
			ptr = ptr->next;
		}
	}
}




/*****************************************************************/
/***                 *********************************************/
/*** VERSION METHODS *********************************************/
/***                 *********************************************/
/*****************************************************************/




/*****************************************************************
 *
 * int mcp_version_compare(McpVer v1, McpVer v2);
 *
 *   Compares two McpVer structs.
 *   Results are similar to strcmp():
 *     Returns negative if v1 <  v2
 *     Returns 0 (zero) if v1 == v2
 *     Returns positive if v1 >  v2
 *
 *****************************************************************/

int
mcp_version_compare(McpVer v1, McpVer v2)
{
	if (v1.vermajor != v2.vermajor) {
		return (v1.vermajor - v2.vermajor);
	}
	return (v1.verminor - v2.verminor);
}




/*****************************************************************
 *
 * McpVer mcp_version_select(
 *                McpVer min1,
 *                McpVer max1,
 *                McpVer min2,
 *                McpVer max2
 *            );
 *
 *   Given the min and max package versions supported by a client
 *     and server, this will return the highest version that is
 *     supported by both.
 *   Returns a McpVer of {0, 0} if there is no version overlap.
 *
 *****************************************************************/

McpVer
mcp_version_select(McpVer min1, McpVer max1, McpVer min2, McpVer max2)
{
	McpVer result = { 0, 0 };

	if (mcp_version_compare(min1, max1) > 0) {
		return result;
	}
	if (mcp_version_compare(min2, max2) > 0) {
		return result;
	}
	if (mcp_version_compare(min1, max2) > 0) {
		return result;
	}
	if (mcp_version_compare(min2, max1) > 0) {
		return result;
	}
	if (mcp_version_compare(max1, max2) > 0) {
		return max2;
	} else {
		return max1;
	}
}





/*****************************************************************/
/***                       ***************************************/
/***  MCP PACKAGE HANDLER  ***************************************/
/***                       ***************************************/
/*****************************************************************/

void
mcp_basic_handler(McpFrame * mfr, McpMesg * mesg, void *dummy)
{
	McpVer myminver = { 2, 1 };
	McpVer mymaxver = { 2, 1 };
	McpVer minver = { 0, 0 };
	McpVer maxver = { 0, 0 };
	McpVer nullver = { 0, 0 };
	const char *ptr;
	const char *auth;

	if (!*mesg->mesgname) {
		auth = mcp_mesg_arg_getline(mesg, "authentication-key", 0);
		if (auth) {
			mfr->authkey = (char *) malloc(strlen(auth) + 1);
			strcpy(mfr->authkey, auth);
		} else {
			McpMesg reply;
			char authval[128];

			mcp_mesg_init(&reply, MCP_INIT_PKG, "");
			mcp_mesg_arg_append(&reply, "version", "2.1");
			mcp_mesg_arg_append(&reply, "to", "2.1");
			snprintf(authval, sizeof(authval), "%.8lX", (unsigned long)(RANDOM() ^ RANDOM()));
			mcp_mesg_arg_append(&reply, "authentication-key", authval);
			mfr->authkey = (char *) malloc(strlen(authval) + 1);
			strcpy(mfr->authkey, authval);
			mcp_frame_output_mesg(mfr, &reply);
			mcp_mesg_clear(&reply);
		}

		ptr = mcp_mesg_arg_getline(mesg, "version", 0);
		if (!ptr)
			return;
		while (isdigit(*ptr))
			minver.vermajor = (minver.vermajor * 10) + (*ptr++ - '0');
		if (*ptr++ != '.')
			return;
		while (isdigit(*ptr))
			minver.verminor = (minver.verminor * 10) + (*ptr++ - '0');

		ptr = mcp_mesg_arg_getline(mesg, "to", 0);
		if (!ptr) {
			maxver = minver;
		} else {
			while (isdigit(*ptr))
				maxver.vermajor = (maxver.vermajor * 10) + (*ptr++ - '0');
			if (*ptr++ != '.')
				return;
			while (isdigit(*ptr))
				maxver.verminor = (maxver.verminor * 10) + (*ptr++ - '0');
		}

		mfr->version = mcp_version_select(myminver, mymaxver, minver, maxver);
		if (mcp_version_compare(mfr->version, nullver)) {
			McpMesg cando;
			char verbuf[32];
			McpPkg *p = mcp_PackageList;

			mfr->enabled = 1;
			while (p) {
				if (strcmp_nocase(p->pkgname, MCP_INIT_PKG)) {
					mcp_mesg_init(&cando, MCP_NEGOTIATE_PKG, "can");
					mcp_mesg_arg_append(&cando, "package", p->pkgname);
					snprintf(verbuf, sizeof(verbuf), "%d.%d", p->minver.vermajor, p->minver.verminor);
					mcp_mesg_arg_append(&cando, "min-version", verbuf);
					snprintf(verbuf, sizeof(verbuf), "%d.%d", p->maxver.vermajor, p->maxver.verminor);
					mcp_mesg_arg_append(&cando, "max-version", verbuf);
					mcp_frame_output_mesg(mfr, &cando);
					mcp_mesg_clear(&cando);
				}
				p = p->next;
			}
			mcp_mesg_init(&cando, MCP_NEGOTIATE_PKG, "end");
			mcp_frame_output_mesg(mfr, &cando);
			mcp_mesg_clear(&cando);
		}
	}
}





/*****************************************************************/
/***                                 *****************************/
/***  MCP-NEGOTIATE PACKAGE HANDLER  *****************************/
/***                                 *****************************/
/*****************************************************************/

void
mcp_negotiate_handler(McpFrame * mfr, McpMesg * mesg, McpVer version, void *dummy)
{
	McpVer minver = { 0, 0 };
	McpVer maxver = { 0, 0 };
	const char *ptr;
	const char *pkg;

	if (!strcmp(mesg->mesgname, "can")) {
		pkg = mcp_mesg_arg_getline(mesg, "package", 0);
		if (!pkg)
			return;
		ptr = mcp_mesg_arg_getline(mesg, "min-version", 0);
		if (!ptr)
			return;
		while (isdigit(*ptr))
			minver.vermajor = (minver.vermajor * 10) + (*ptr++ - '0');
		if (*ptr++ != '.')
			return;
		while (isdigit(*ptr))
			minver.verminor = (minver.verminor * 10) + (*ptr++ - '0');

		ptr = mcp_mesg_arg_getline(mesg, "max-version", 0);
		if (!ptr) {
			maxver = minver;
		} else {
			while (isdigit(*ptr))
				maxver.vermajor = (maxver.vermajor * 10) + (*ptr++ - '0');
			if (*ptr++ != '.')
				return;
			while (isdigit(*ptr))
				maxver.verminor = (maxver.verminor * 10) + (*ptr++ - '0');
		}

		mcp_frame_package_add(mfr, pkg, minver, maxver);
	} else if (!strcmp(mesg->mesgname, "end")) {
		/* nothing to do for end of package negotiations. */
	}
}





/*****************************************************************/
/****************                *********************************/
/**************** INTERNAL STUFF *********************************/
/****************                *********************************/
/*****************************************************************/

int
mcp_intern_is_ident(const char **in, char *buf, int buflen)
{
	int origbuflen = buflen;

	if (!isalpha(**in) && **in != '_')
		return 0;
	while (isalpha(**in) || **in == '_' || isdigit(**in) || **in == '-') {
		if (--buflen > 0) {
			*buf++ = **in;
		}
		(*in)++;
	}
	if (origbuflen > 0)
		*buf = '\0';
	return 1;
}


int
mcp_intern_is_simplechar(char in)
{
	if (in == '*' || in == ':' || in == '\\' || in == '"')
		return 0;
	if (!isprint(in) || in == ' ')
		return 0;
	return 1;
}


int
mcp_intern_is_unquoted(const char **in, char *buf, int buflen)
{
	int origbuflen = buflen;

	if (!mcp_intern_is_simplechar(**in))
		return 0;
	while (mcp_intern_is_simplechar(**in)) {
		if (--buflen > 0) {
			*buf++ = **in;
		}
		(*in)++;
	}
	if (origbuflen > 0)
		*buf = '\0';
	return 1;
}


int
mcp_intern_is_quoted(const char **in, char *buf, int buflen)
{
	int origbuflen = buflen;
	const char *old = *in;

	if (**in != '"')
		return 0;
	(*in)++;
	while (**in) {
		if (**in == '\\') {
			(*in)++;
			if (**in && --buflen > 0) {
				*buf++ = **in;
			}
		} else if (**in == '"') {
			break;
		} else {
			if (--buflen > 0) {
				*buf++ = **in;
			}
		}
		(*in)++;
	}
	if (**in != '"') {
		*in = old;
		return 0;
	}
	(*in)++;
	if (origbuflen > 0) {
		*buf = '\0';
	}
	return 1;
}


int
mcp_intern_is_keyval(McpMesg * msg, const char **in)
{
	char keyname[128];
	char value[BUFFER_LEN];
	const char *old = *in;
	int deferred = 0;

	if (!isspace(**in))
		return 0;
	while (isspace(**in))
		(*in)++;
	if (!mcp_intern_is_ident(in, keyname, sizeof(keyname))) {
		*in = old;
		return 0;
	}
	if (**in == '*') {
		msg->incomplete = 1;
		deferred = 1;
		(*in)++;
	}
	if (**in != ':') {
		*in = old;
		return 0;
	}
	(*in)++;
	if (!isspace(**in)) {
		*in = old;
		return 0;
	}
	while (isspace(**in))
		(*in)++;
	if (!mcp_intern_is_unquoted(in, value, sizeof(value)) &&
		!mcp_intern_is_quoted(in, value, sizeof(value))
			) {
		*in = old;
		return 0;
	}

	if (deferred) {
		mcp_mesg_arg_append(msg, keyname, NULL);
	} else {
		mcp_mesg_arg_append(msg, keyname, value);
	}
	return 1;
}



int
mcp_intern_is_mesg_start(McpFrame * mfr, const char *in)
{
	char mesgname[128];
	char authkey[128];
	char *subname = NULL;
	McpMesg *newmsg = NULL;
	McpPkg *pkg = NULL;
	int longlen = 0;

	if (!mcp_intern_is_ident(&in, mesgname, sizeof(mesgname)))
		return 0;
	if (strcmp_nocase(mesgname, MCP_INIT_PKG)) {
		if (!isspace(*in))
			return 0;
		while (isspace(*in))
			in++;
		if (!mcp_intern_is_unquoted(&in, authkey, sizeof(authkey)))
			return 0;
		if (strcmp(authkey, mfr->authkey))
			return 0;
	}

	if (strncmp_nocase(mesgname, MCP_INIT_PKG, 3)) {
		for (pkg = mfr->packages; pkg; pkg = pkg->next) {
			int pkgnamelen = strlen(pkg->pkgname);

			if (!strncmp_nocase(pkg->pkgname, mesgname, pkgnamelen)) {
				if (mesgname[pkgnamelen] == '\0' || mesgname[pkgnamelen] == '-') {
					if (pkgnamelen > longlen) {
						longlen = pkgnamelen;
					}
				}
			}
		}
	}
	if (!longlen) {
		int neglen = strlen(MCP_NEGOTIATE_PKG);

		if (!strncmp_nocase(mesgname, MCP_NEGOTIATE_PKG, neglen)) {
			longlen = neglen;
		} else if (!strcmp_nocase(mesgname, MCP_INIT_PKG)) {
			longlen = strlen(mesgname);
		} else {
			return 0;
		}
	}
	subname = mesgname + longlen;
	if (*subname) {
		*subname++ = '\0';
	}

	newmsg = (McpMesg *) malloc(sizeof(McpMesg));
	mcp_mesg_init(newmsg, mesgname, subname);
	while (*in) {
		if (!mcp_intern_is_keyval(newmsg, &in)) {
			mcp_mesg_clear(newmsg);
			free(newmsg);
			return 0;
		}
	}

	/* Okay, we've recieved a valid message. */
	if (newmsg->incomplete) {
		/* It's incomplete.  Remember it to finish later. */
		const char *msgdt = mcp_mesg_arg_getline(newmsg, MCP_DATATAG, 0);

		newmsg->datatag = (char *) malloc(strlen(msgdt) + 1);
		strcpy(newmsg->datatag, msgdt);
		mcp_mesg_arg_remove(newmsg, MCP_DATATAG);
		newmsg->next = mfr->messages;
		mfr->messages = newmsg;
	} else {
		/* It's complete.  Execute the callback function for this package. */
		mcp_frame_package_docallback(mfr, newmsg);
		mcp_mesg_clear(newmsg);
		free(newmsg);
	}
	return 1;
}


int
mcp_intern_is_mesg_cont(McpFrame * mfr, const char *in)
{
	char datatag[128];
	char keyname[128];
	McpMesg *ptr;

	if (*in != '*') {
		return 0;
	}
	in++;
	if (!isspace(*in))
		return 0;
	while (isspace(*in))
		in++;
	if (!mcp_intern_is_unquoted(&in, datatag, sizeof(datatag)))
		return 0;
	if (!isspace(*in))
		return 0;
	while (isspace(*in))
		in++;
	if (!mcp_intern_is_ident(&in, keyname, sizeof(keyname)))
		return 0;
	if (*in != ':')
		return 0;
	in++;
	if (!isspace(*in))
		return 0;
	in++;

	for (ptr = mfr->messages; ptr; ptr = ptr->next) {
		if (!strcmp(datatag, ptr->datatag)) {
			mcp_mesg_arg_append(ptr, keyname, in);
			break;
		}
	}
	if (!ptr) {
		return 0;
	}
	return 1;
}


int
mcp_intern_is_mesg_end(McpFrame * mfr, const char *in)
{
	char datatag[128];
	McpMesg *ptr, **prev;

	if (*in != ':') {
		return 0;
	}
	in++;
	if (!isspace(*in))
		return 0;
	while (isspace(*in))
		in++;
	if (!mcp_intern_is_unquoted(&in, datatag, sizeof(datatag)))
		return 0;
	if (*in)
		return 0;
	prev = &mfr->messages;
	for (ptr = mfr->messages; ptr; ptr = ptr->next, prev = &ptr->next) {
		if (!strcmp(datatag, ptr->datatag)) {
			*prev = ptr->next;
			break;
		}
	}
	if (!ptr) {
		return 0;
	}
	ptr->incomplete = 0;
	mcp_frame_package_docallback(mfr, ptr);
	mcp_mesg_clear(ptr);
	free(ptr);

	return 1;
}


int
mcp_internal_parse(McpFrame * mfr, const char *in)
{
	if (mcp_intern_is_mesg_cont(mfr, in))
		return 1;
	if (mcp_intern_is_mesg_end(mfr, in))
		return 1;
	if (mcp_intern_is_mesg_start(mfr, in))
		return 1;
	return 0;
}