/* 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; }