diff --git a/plugins/avplug/avdecode.c b/plugins/avplug/avdecode.c index 5497ff0f..8504c817 100644 --- a/plugins/avplug/avdecode.c +++ b/plugins/avplug/avdecode.c @@ -18,8 +18,8 @@ #define avio_alloc_context av_alloc_put_byte */ -#define ARGNAMES ,sourceid, data, speed, samples, channels, width -BUILTIN(void, S_RawAudio, (int sourceid, void *data, int speed, int samples, int channels, int width)); +#define ARGNAMES ,sourceid, data, speed, samples, channels, width, volume +BUILTIN(void, S_RawAudio, (int sourceid, void *data, int speed, int samples, int channels, int width, float volume)); #undef ARGNAMES /*should probably try threading this*/ diff --git a/plugins/avplug/avencode.c b/plugins/avplug/avencode.c index d3adcc50..2b67c0b3 100644 --- a/plugins/avplug/avencode.c +++ b/plugins/avplug/avencode.c @@ -412,6 +412,30 @@ static media_encoder_funcs_t encoderfuncs = AVEnc_End }; +/* +qintptr_t AVEnc_ExecuteCommand(qintptr_t *args) +{ + char cmd[256]; + Cmd_Argv(0, cmd, sizeof(cmd)); + if (!strcmp(cmd, "avcapture")) + { +menuclear +menualias menucallback + +menubox 0 0 320 8 +menutext 0 0 "GO GO GO!!!" "radio21" +menutext 0 8 "Fall back" "radio22" +menutext 0 8 "Stick together" "radio23" +menutext 0 16 "Get in position" "radio24" +menutext 0 24 "Storm the front" "radio25" +menutext 0 24 "Report in" "radio26" +menutext 0 24 "Cancel" + return true; + } + return false; +} +*/ + qboolean AVEnc_Init(void) { pCvar_Register("avplug_format", "", 0, "avplug"); @@ -430,6 +454,9 @@ qboolean AVEnc_Init(void) return false; } +// if (Plug_Export("ExecuteCommand", AVEnc_ExecuteCommand)) +// Cmd_AddCommand("avcapture"); + return true; } diff --git a/plugins/berkelium/berkelium.vcproj b/plugins/berkelium/berkelium.vcproj index 0069b2d5..a93bd687 100644 --- a/plugins/berkelium/berkelium.vcproj +++ b/plugins/berkelium/berkelium.vcproj @@ -54,7 +54,7 @@ width = 1024; - ctx->height = 1024; + ctx->paintedwidth = ctx->width = 1024; + ctx->paintedheight = ctx->height = 1024; ctx->repainted = false; ctx->buffer = (unsigned int*)malloc(ctx->width * ctx->height * 4); ctx->wnd = Berkelium::Window::create(context); @@ -226,6 +229,8 @@ static void *Dec_DisplayFrame(void *vctx, qboolean nosound, enum uploadfmt_e *fm if (!ctx->repainted) return NULL; + ctx->paintedwidth = ctx->width; + ctx->paintedheight = ctx->height; ctx->repainted = false; return ctx->buffer; } @@ -238,8 +243,16 @@ static void Dec_Destroy(void *vctx) static void Dec_GetSize (void *vctx, int *width, int *height) { decctx *ctx = (decctx*)vctx; - *width = ctx->width; - *height = ctx->height; + if (ctx->repainted) + { + *width = ctx->width; + *height = ctx->height; + } + else + { + *width = ctx->paintedwidth; + *height = ctx->paintedheight; + } } static qboolean Dec_SetSize (void *vctx, int width, int height) { diff --git a/plugins/jabber/jabberclient.c b/plugins/jabber/jabberclient.c index 254032c2..054d9b31 100644 --- a/plugins/jabber/jabberclient.c +++ b/plugins/jabber/jabberclient.c @@ -2,16 +2,35 @@ #include "../plugin.h" #include +#include "../../engine/common/netinc.h" +#include "xml.h" + +#define NOICE + +#define QUAKEMEDIATYPE "quake" +#define QUAKEMEDIAXMLNS "fteqw.com:netmedia" + +struct icestate_s *(QDECL *pICE_Create)(void *module, char *conname, char *peername, enum icemode_e mode); //doesn't start pinging anything. +struct icestate_s *(QDECL *pICE_Find)(void *module, char *conname); +void (QDECL *pICE_Begin)(struct icestate_s *con, char *stunip, int stunport); //begins sending stun packets and stuff as required. +struct icecandidate_s *(QDECL *pICE_GetLCandidateInfo)(struct icestate_s *con); //stuff that needs reporting to the peer. +void (QDECL *pICE_AddRCandidateInfo)(struct icestate_s *con, struct icecandidate_s *cand); //stuff that came from the peer. +void (QDECL *pICE_Close)(struct icestate_s *con); //bye then. + #define Q_strncpyz(o, i, l) do {strncpy(o, i, l-1);o[l-1]='\0';}while(0) -#define JCL_BUILD "2" +#define JCL_BUILD "3" #define ARGNAMES ,sock,certhostname BUILTINR(int, Net_SetTLSClient, (qhandle_t sock, const char *certhostname)); #undef ARGNAMES +#define ARGNAMES ,funcname +BUILTINR(void *, Plug_GetNativePointer, (const char *funcname)); +#undef ARGNAMES + void (*Con_TrySubPrint)(const char *conname, const char *message); void Fallback_ConPrint(const char *conname, const char *message) { @@ -42,22 +61,21 @@ void Con_SubPrintf(char *subname, char *format, ...) #define COLOURRED "^1" #define COLOURYELLOW "^3" #define COLOURPURPLE "^5" - #define COMMANDPREFIX "jabbercl" + #define COMMANDPREFIX "xmpp" + #define COMMANDPREFIX2 "jab" + #define COMMANDPREFIX3 "jabbercl" #define playsound(s) #define TL_NETGETPACKETERROR "NET_GetPacket Error %s\n" - #define TOKENSIZE 1024 - char com_token[TOKENSIZE]; - - char *COM_Parse (char *data) //this is taken out of quake + char *COM_ParseOut (char *data, char *buf, int bufsize) //this is taken out of quake { int c; int len; len = 0; - com_token[0] = 0; + buf[0] = 0; if (!data) return NULL; @@ -71,34 +89,22 @@ void Con_SubPrintf(char *subname, char *format, ...) data++; } - // skip // comments - if (c=='/') - { - if (data[1] == '/') - { - while (*data && *data != '\n') - data++; - goto skipwhite; - } - } - - // handle quoted strings specially if (c == '\"') { data++; while (1) { - if (len >= TOKENSIZE-1) + if (len >= bufsize-1) return data; c = *data++; if (c=='\"' || !c) { - com_token[len] = 0; + buf[len] = 0; return data; } - com_token[len] = c; + buf[len] = c; len++; } } @@ -106,16 +112,16 @@ void Con_SubPrintf(char *subname, char *format, ...) // parse a regular word do { - if (len >= TOKENSIZE-1) + if (len >= bufsize-1) return data; - com_token[len] = c; + buf[len] = c; data++; len++; c = *data; } while (c>32); - com_token[len] = 0; + buf[len] = 0; return data; } @@ -123,21 +129,25 @@ void Con_SubPrintf(char *subname, char *format, ...) - -void JCL_Command(void); +void RenameConsole(char *totrim); +void JCL_Command(char *consolename); +void JCL_LoadConfig(void); +void JCL_WriteConfig(void); qintptr_t JCL_ExecuteCommand(qintptr_t *args) { char cmd[256]; Cmd_Argv(0, cmd, sizeof(cmd)); - if (!strcmp(cmd, COMMANDPREFIX)) + if (!strcmp(cmd, COMMANDPREFIX) || !strcmp(cmd, COMMANDPREFIX2) || !strcmp(cmd, COMMANDPREFIX3)) { - JCL_Command(); + if (!args[0]) + JCL_Command(""); return true; } return false; } +qintptr_t JCL_ConsoleLink(qintptr_t *args); qintptr_t JCL_ConExecuteCommand(qintptr_t *args); qintptr_t JCL_Frame(qintptr_t *args); @@ -149,19 +159,38 @@ qintptr_t Plug_Init(qintptr_t *args) { CHECKBUILTIN(Net_SetTLSClient); if (!BUILTINISVALID(Net_SetTLSClient)) - Con_Print("Jabber Client Plugin Loaded ^1without^7 TLS\n"); + Con_Print("XMPP Plugin Loaded ^1without^7 TLS\n"); else - Con_Print("Jabber Client Plugin Loaded with TLS\n"); + Con_Print("XMPP Plugin Loaded. For help, use: ^[/"COMMANDPREFIX" /help^]\n"); + + Plug_Export("ConsoleLink", JCL_ConsoleLink); if (!Plug_Export("ConExecuteCommand", JCL_ConExecuteCommand)) { - Con_Printf("Jabber client plugin in single-console mode\n"); + Con_Printf("XMPP plugin in single-console mode\n"); Con_TrySubPrint = Fallback_ConPrint; } else Con_TrySubPrint = Con_SubPrint; Cmd_AddCommand(COMMANDPREFIX); + Cmd_AddCommand(COMMANDPREFIX2); + Cmd_AddCommand(COMMANDPREFIX3); + + + CHECKBUILTIN(Plug_GetNativePointer); + if (BUILTINISVALID(Plug_GetNativePointer)) + { + pICE_Create = Plug_GetNativePointer("ICE_Create"); + pICE_Find = Plug_GetNativePointer("ICE_Find"); + pICE_Begin = Plug_GetNativePointer("ICE_Begin"); + pICE_GetLCandidateInfo = Plug_GetNativePointer("ICE_GetLCandidateInfo"); + pICE_AddRCandidateInfo = Plug_GetNativePointer("ICE_AddRCandidateInfo"); + pICE_Close = Plug_GetNativePointer("ICE_Close"); + } + + + JCL_LoadConfig(); return 1; } else @@ -185,15 +214,40 @@ qintptr_t Plug_Init(qintptr_t *args) #define JCL_MAXMSGLEN 10000 +typedef struct bresource_s +{ + char bstatus[128]; //basic status + char fstatus[128]; //full status + char server[256]; + int servertype; //0=none, 1=already a client, 2=joinable -typedef struct { + struct bresource_s *next; + + char resource[1]; +} bresource_t; +typedef struct buddy_s +{ + bresource_t *resources; + bresource_t *defaultresource; //this is the one that last replied + int defaulttimestamp; + qboolean friended; + + char name[256]; + + struct buddy_s *next; + char accountdomain[1]; //no resource on there +} buddy_t; +typedef struct jclient_s +{ char server[64]; int port; qhandle_t socket; - qhandle_t inlog; - qhandle_t outlog; + char *outbuf; + int outbufpos; + int outbuflen; + int outbufmax; char bufferedinmessage[JCL_MAXMSGLEN+1]; //servers are required to be able to handle messages no shorter than a specific size. //which means we need to be able to handle messages when they get to us. //servers can still handle larger messages if they choose, so this might not be enough. @@ -205,22 +259,450 @@ typedef struct { char username[256]; char password[256]; char resource[256]; + char jid[256]; //this is our full username@domain/resource string int tagdepth; int openbracket; int instreampos; + qboolean tlsconnect; //the old tls method on port 5223. qboolean connected; //fully on server and authed and everything. - qboolean noplain; //block plain-text password exposure qboolean issecure; //tls enabled + qboolean streamdebug; //echo the stream to subconsoles + + qboolean preapproval; char curquakeserver[2048]; char defaultnamespace[2048]; //should be 'jabber:client' or blank (and spammy with all the extra xmlns attribs) + + struct iq_s { + struct iq_s *next; + char id[64]; + int timeout; + qboolean (*callback) (struct jclient_s *jcl, struct subtree_s *tree); + } *pendingiqs; + + struct c2c_s + { + struct c2c_s *next; + char *sessionname; + buddy_t *tob; + bresource_t *tor; + }; + + buddy_t *buddies; } jclient_t; jclient_t *jclient; +struct subtree_s; + +void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...); +void JCL_GeneratePresence(qboolean force); +void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, struct subtree_s *tree), char *iqtype, char *target, char *fmt, ...); +void JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, xmltree_t *node, qboolean destroynode); + +void JCL_Join(jclient_t *jcl, char *target) +{ + char *s; + xmltree_t *jingle; + struct icestate_s *ice; + if (!jcl) + return; + +#ifdef NOICE + ice = pICE_Create(NULL, NULL, target, ICE_RAW); +#else + ice = pICE_Create(NULL, NULL, target, ICE_ICE); +#endif + if (!ice) + { + Con_Printf("Unable to connect to %s (dedicated servers cannot initiate connections)\n", target); + return; + } + //FIXME: record the session name + + jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", ""); + XML_AddParameter(jingle, "sid", ice->conname); + XML_AddParameter(jingle, "responder", target); + XML_AddParameter(jingle, "initiator", jcl->jid); + XML_AddParameter(jingle, "action", "session-initiate"); + { + xmltree_t *content = XML_CreateNode(jingle, "content", "", ""); + XML_AddParameter(content, "senders", "both"); + XML_AddParameter(content, "name", "some-old-quake-game"); + XML_AddParameter(content, "creator", "initiator"); + { + xmltree_t *description; + xmltree_t *transport; + if (ice->mode == ICE_RAW) + { + transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:raw-udp:1", ""); + { + xmltree_t *candidate; + struct icecandidate_s *b = NULL; + struct icecandidate_s *c; + while ((c = pICE_GetLCandidateInfo(ice))) + { + if (!b || b->priority < c->priority) + b = c; + } + + if (b) + { + candidate = XML_CreateNode(transport, "candidate", "", ""); + XML_AddParameter(candidate, "ip", b->addr); + XML_AddParameteri(candidate, "port", b->port); + XML_AddParameter(candidate, "id", b->candidateid); + XML_AddParameteri(candidate, "generation", b->generation); + XML_AddParameteri(candidate, "component", b->component); + } + } + } + else if (ice->mode == ICE_ICE) + { + transport = XML_CreateNode(content, "transport", "urn:xmpp:jingle:transports:ice-udp:1", ""); + XML_AddParameter(transport, "ufrag", ice->lfrag); + XML_AddParameter(transport, "pwd", ice->lpwd); + { + struct icecandidate_s *c; + while ((c = pICE_GetLCandidateInfo(ice))) + { + char *ctypename[]={"host", "srflx", "prflx", "relay"}; + xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", ""); + XML_AddParameter(candidate, "type", ctypename[c->type]); + XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously? + XML_AddParameteri(candidate, "priority", c->priority); + XML_AddParameteri(candidate, "port", c->port); + XML_AddParameteri(candidate, "network", c->network); + XML_AddParameter(candidate, "ip", c->addr); + XML_AddParameter(candidate, "id", c->candidateid); + XML_AddParameteri(candidate, "generation", c->generation); + XML_AddParameteri(candidate, "foundation", c->foundation); + XML_AddParameteri(candidate, "component", c->component); + } + } + } + description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, ""); + XML_AddParameter(description, "media", QUAKEMEDIATYPE); + { + /* + xmltree_t *candidate = XML_CreateNode(description, "payload-type", "", ""); + XML_AddParameter(candidate, "channels", "1"); + XML_AddParameter(candidate, "clockrate", "8000"); + XML_AddParameter(candidate, "id", "104"); + XML_AddParameter(candidate, "name", "SPEEX"); + */ + } + } + } + Con_Printf("Sending connection start:\n"); + XML_ConPrintTree(jingle, 0); + JCL_SendIQNode(jcl, NULL, "set", target, jingle, true); +} + +void JCL_JingleParsePeerPorts(jclient_t *jcl, xmltree_t *inj, char *from) +{ + xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0); + xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0); + xmltree_t *incandidate; + struct icestate_s *ice; + struct icecandidate_s rem; + int i; + + ice = pICE_Find(NULL, XML_GetParameter(inj, "sid", "")); + if (ice && strcmp(ice->friendlyname, from)) + { + Con_Printf("%s is trying to mess with our connections...\n", from); + return; + } + + for (i = 0; incandidate = XML_ChildOfTree(intransport, "candidate", i); i++) + { + char *s; + memset(&rem, 0, sizeof(rem)); + rem.addr = XML_GetParameter(incandidate, "ip", ""); + rem.candidateid = XML_GetParameter(incandidate, "id", ""); + + s = XML_GetParameter(incandidate, "type", ""); + if (s && !strcmp(s, "srflx")) + rem.type = ICE_SRFLX; + else if (s && !strcmp(s, "prflx")) + rem.type = ICE_PRFLX; + else if (s && !strcmp(s, "relay")) + rem.type = ICE_RELAY; + else + rem.type = ICE_HOST; + rem.port = atoi(XML_GetParameter(incandidate, "port", "0")); + rem.priority = atoi(XML_GetParameter(incandidate, "priority", "0")); + rem.network = atoi(XML_GetParameter(incandidate, "network", "0")); + rem.generation = atoi(XML_GetParameter(incandidate, "generation", "0")); + rem.foundation = atoi(XML_GetParameter(incandidate, "foundation", "0")); + rem.component = atoi(XML_GetParameter(incandidate, "component", "0")); + s = XML_GetParameter(incandidate, "protocol", "udp"); + if (s && !strcmp(s, "udp")) + rem.transport = 0; + else + rem.transport = 0; + pICE_AddRCandidateInfo(ice, &rem); + } +} +struct icestate_s *JCL_JingleHandleInitiate(jclient_t *jcl, xmltree_t *inj, char *from) +{ +/*inj contains something like: + + + + + + + + + + + + +*/ + + xmltree_t *incontent = XML_ChildOfTree(inj, "content", 0); + xmltree_t *intransport = XML_ChildOfTree(incontent, "transport", 0); + xmltree_t *indescription = XML_ChildOfTree(incontent, "description", 0); + char *transportxmlns = intransport?intransport->xmlns:""; + char *descriptionxmlns = indescription?indescription->xmlns:""; + char *descriptionmedia = XML_GetParameter(indescription, "media", ""); + + xmltree_t *jingle; + struct icestate_s *ice; + qboolean accepted = false; + enum icemode_e imode; + + imode = strcmp(transportxmlns, "urn:xmpp:jingle:transports:raw-udp:1")?ICE_ICE:ICE_RAW; + + if (!incontent || strcmp(descriptionmedia, QUAKEMEDIATYPE) || strcmp(descriptionxmlns, QUAKEMEDIAXMLNS)) + { + //decline it + ice = NULL; + } + else + ice = pICE_Create(NULL, XML_GetParameter(inj, "sid", ""), from, imode); + + jingle = XML_CreateNode(NULL, "jingle", "urn:xmpp:jingle:1", ""); + XML_AddParameter(jingle, "sid", ice->conname); + XML_AddParameter(jingle, "responder", XML_GetParameter(inj, "responder", "")); + XML_AddParameter(jingle, "initiator", XML_GetParameter(inj, "initiator", "")); + if (!ice) + XML_AddParameter(jingle, "action", "session-terminate"); + else + { + xmltree_t *content = XML_CreateNode(jingle, "content", "", ""); +#ifdef NOICE + if (imode != ICE_RAW) + { + char buf[256]; + XML_AddParameter(jingle, "action", "transport-replace"); + Q_snprintf(buf, sizeof(buf), "raw-%s", XML_GetParameter(incontent, "name", "")); + XML_AddParameter(content, "name", buf); + } + else +#endif + { + XML_AddParameter(jingle, "action", "session-accept"); + XML_AddParameter(content, "name", XML_GetParameter(incontent, "name", "")); + accepted = true; + } + XML_AddParameter(content, "senders", "both"); + XML_AddParameter(content, "creator", "initiator"); + { + xmltree_t *description; + xmltree_t *transport; + transport = XML_CreateNode(content, "transport", transportxmlns, ""); + if (imode == ICE_RAW) + { + //raw-udp can send only one candidate + xmltree_t *candidate; + struct icecandidate_s *b = NULL; + struct icecandidate_s *c; + while ((c = pICE_GetLCandidateInfo(ice))) + { + if (!b || b->priority < c->priority) + b = c; + } + if (b) + { + candidate = XML_CreateNode(transport, "candidate", "", ""); + XML_AddParameter(candidate, "ip", b->addr); + XML_AddParameteri(candidate, "port", b->port); + XML_AddParameter(candidate, "id", b->candidateid); + XML_AddParameteri(candidate, "generation", b->generation); + XML_AddParameteri(candidate, "component", b->component); + } + } + else if (imode == ICE_ICE) + { + //ice can send multiple candidates + struct icecandidate_s *c; + XML_AddParameter(transport, "ufrag", ice->lfrag); + XML_AddParameter(transport, "pwd", ice->lpwd); + while ((c = pICE_GetLCandidateInfo(ice))) + { + char *ctypename[]={"host", "srflx", "prflx", "relay"}; + xmltree_t *candidate = XML_CreateNode(transport, "candidate", "", ""); + XML_AddParameter(candidate, "type", ctypename[c->type]); + XML_AddParameter(candidate, "protocol", "udp"); //is this not just a little bit redundant? ice-udp? seriously? + XML_AddParameteri(candidate, "priority", c->priority); + XML_AddParameteri(candidate, "port", c->port); + XML_AddParameteri(candidate, "network", c->network); + XML_AddParameter(candidate, "ip", c->addr); + XML_AddParameter(candidate, "id", c->candidateid); + XML_AddParameteri(candidate, "generation", c->generation); + XML_AddParameteri(candidate, "foundation", c->foundation); + XML_AddParameteri(candidate, "component", c->component); + } + } + else + { + //egads! can't cope with that. + } + description = XML_CreateNode(content, "description", QUAKEMEDIAXMLNS, ""); + XML_AddParameter(description, "media", QUAKEMEDIATYPE); + { + /* + xmltree_t *candidate = XML_CreateNode(description, "payload-type", "", ""); + XML_AddParameter(candidate, "channels", "1"); + XML_AddParameter(candidate, "clockrate", "8000"); + XML_AddParameter(candidate, "id", "104"); + XML_AddParameter(candidate, "name", "SPEEX"); + */ + } + } + } + if (!ice) + Con_Printf("Sending reject:\n"); + else + Con_Printf("Sending accept:\n"); + XML_ConPrintTree(jingle, 0); + JCL_SendIQNode(jcl, NULL, "set", from, jingle, true); + + if (ice) + JCL_JingleParsePeerPorts(jcl, inj, from); + + //if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted + if (ice && accepted) + pICE_Begin(ice, NULL, 0); + return ice; +} + +void JCL_ParseJingle(jclient_t *jcl, xmltree_t *tree, char *from, char *id) +{ + char *action = XML_GetParameter(tree, "action", ""); + char *initiator = XML_GetParameter(tree, "initiator", ""); + char *responder = XML_GetParameter(tree, "responder", ""); + char *sid = XML_GetParameter(tree, "sid", ""); + + //validate sender + struct icestate_s *ice = pICE_Find(NULL, sid); + if (ice && strcmp(ice->friendlyname, from)) + { + Con_Printf("%s is trying to mess with our connections...\n", from); + return; + } + + if (!strcmp(action, "session-terminate")) + { + if (ice) + pICE_Close(ice); + + Con_Printf("Session ended\n"); + XML_ConPrintTree(tree, 0); + } + else if (!strcmp(action, "session-accept")) + { + if (!ice) + { + Con_Printf("Cannot accept a session that was never created\n"); + } + else + { + Con_Printf("Session Accepted!\n"); + XML_ConPrintTree(tree, 0); + + if (ice) + JCL_JingleParsePeerPorts(jcl, tree, from); + + //if we didn't error out, the ICE stuff is meant to start sending handshakes/media as soon as the connection is accepted + if (ice) + pICE_Begin(ice, NULL, 0); + } + } + else if (!strcmp(action, "session-initiate")) + { + Con_Printf("Peer initiating connection!\n"); + XML_ConPrintTree(tree, 0); + + ice = JCL_JingleHandleInitiate(jcl, tree, from); + } + else + { + Con_Printf("Unknown jingle action: %s\n", action); + XML_ConPrintTree(tree, 0); + } + + JCL_AddClientMessagef(jcl, + "", from, id); +} + + +qintptr_t JCL_ConsoleLink(qintptr_t *args) +{ + char text[256]; + char link[256]; + Cmd_Argv(0, text, sizeof(text)); + Cmd_Argv(1, link, sizeof(link)); + + if (!strncmp(link, "\\xmppauth\\", 6)) + { + //we should friend them too. + if (jclient) + JCL_AddClientMessagef(jclient, "", link+10); + return true; + } + if (!strncmp(link, "\\xmppdeny\\", 6)) + { + if (jclient) + JCL_AddClientMessagef(jclient, "", link+10); + return true; + } + + if (!strncmp(link, "\\xmppjoin\\", 6)) + { + JCL_Join(jclient, link+10); + return false; + } + if (!strncmp(link, "\\xmpp\\", 6)) + { + if (jclient) + { + char *f; + buddy_t *b; + bresource_t *br; + + JCL_FindBuddy(jclient, link+6, &b, &br); + f = b->name; + b->defaultresource = br; + + if (BUILTINISVALID(Con_SubPrint)) + Con_SubPrint(f, ""); + if (BUILTINISVALID(Con_SetActive)) + Con_SetActive(f); + } + return true; + } + return false; +} + qintptr_t JCL_ConExecuteCommand(qintptr_t *args) { + buddy_t *b; + char consolename[256]; if (!jclient) { char buffer[256]; @@ -228,21 +710,104 @@ qintptr_t JCL_ConExecuteCommand(qintptr_t *args) Con_SubPrint(buffer, "You were disconnected\n"); return true; } - Cmd_Argv(0, jclient->defaultdest, sizeof(jclient->defaultdest)); - JCL_Command(); + Cmd_Argv(0, consolename, sizeof(consolename)); + for (b = jclient->buddies; b; b = b->next) + { + if (!strcmp(b->name, consolename)) + { + if (b->defaultresource) + Q_snprintf(jclient->defaultdest, sizeof(jclient->defaultdest), "%s/%s", b->accountdomain, b->defaultresource->resource); + else + Q_snprintf(jclient->defaultdest, sizeof(jclient->defaultdest), "%s", b->accountdomain); + break; + } + } + JCL_Command(consolename); return true; } +void JCL_FlushOutgoing(jclient_t *jcl) +{ + int sent; + if (!jcl || !jcl->outbuflen) + return; + + sent = Net_Send(jcl->socket, jcl->outbuf + jcl->outbufpos, jcl->outbuflen); //FIXME: This needs rewriting to cope with errors. + if (sent > 0) + { + //and print it on some subconsole if we're debugging + if (jcl->streamdebug) + { + char t = jcl->outbuf[jcl->outbufpos+sent]; + jcl->outbuf[jcl->outbufpos+sent] = 0; + Con_SubPrintf("xmppout", COLOURYELLOW "%s\n", jcl->outbuf + jcl->outbufpos); + jcl->outbuf[jcl->outbufpos+sent] = t; + } + + jcl->outbufpos += sent; + jcl->outbuflen -= sent; + } + else + Con_Printf("Unable to send anything\n"); +} void JCL_AddClientMessage(jclient_t *jcl, char *msg, int datalen) { - Net_Send(jcl->socket, msg, datalen); //FIXME: This needs rewriting to cope with errors. - Con_SubPrintf("xmppout", COLOURYELLOW "%s \n",msg); + int sent; + + //handle overflows + if (jcl->outbufpos+jcl->outbuflen+datalen > jcl->outbufmax) + { + if (jcl->outbuflen+datalen <= jcl->outbufmax) + { + //can get away with just moving the data + memmove(jcl->outbuf, jcl->outbuf + jcl->outbufpos, jcl->outbuflen); + jcl->outbufpos = 0; + } + else + { + //need to expand the buffer. + int newmax = (jcl->outbuflen+datalen)*2; + char *newbuf; + + if (newmax < jcl->outbuflen) + newbuf = NULL; //eep... some special kind of evil overflow. + else + newbuf = malloc(newmax+1); + + if (newbuf) + { + memcpy(newbuf, jcl->outbuf + jcl->outbufpos, jcl->outbuflen); + jcl->outbufmax = newmax; + jcl->outbufpos = 0; + jcl->outbuf = newbuf; + } + else + datalen = 0; //eep! + } + } + //and write our data to it + memcpy(jcl->outbuf + jcl->outbufpos + jcl->outbuflen, msg, datalen); + jcl->outbuflen += datalen; + + //try and flush it now + JCL_FlushOutgoing(jcl); } void JCL_AddClientMessageString(jclient_t *jcl, char *msg) { JCL_AddClientMessage(jcl, msg, strlen(msg)); } +void JCL_AddClientMessagef(jclient_t *jcl, char *fmt, ...) +{ + va_list argptr; + char body[2048]; + struct iq_s *iq; + + va_start (argptr, fmt); + vsnprintf (body, sizeof(body), fmt, argptr); + va_end (argptr); + JCL_AddClientMessageString(jcl, body); +} jclient_t *JCL_Connect(char *server, int defport, qboolean usesecure, char *account, char *password) { jclient_t *jcl; @@ -299,16 +864,18 @@ jclient_t *JCL_Connect(char *server, int defport, qboolean usesecure, char *acco // gethostname(jcl->hostname, sizeof(jcl->hostname)); // jcl->hostname[sizeof(jcl->hostname)-1] = 0; - jcl->noplain = true; + jcl->tlsconnect = usesecure; + jcl->streamdebug = !!Cvar_GetFloat("xmpp_debug"); *at = '\0'; + Q_strlcpy(jcl->server, server, sizeof(jcl->server)); Q_strlcpy(jcl->username, account, sizeof(jcl->username)); Q_strlcpy(jcl->domain, at+1, sizeof(jcl->domain)); Q_strlcpy(jcl->password, password, sizeof(jcl->password)); Q_strlcpy(jcl->resource, "Quake", sizeof(jcl->password)); - Con_Printf("Trying to connect\n"); + Con_Printf("Trying to connect to %s\n", at+1); JCL_AddClientMessageString(jcl, "" "= maxpos) - break; - pos++; - } - - if (pos == maxpos) - { - *startpos = pos; - return NULL; //nothing anyway. - } - - //expect a < - - if (buffer[pos] != '<') - { - Con_Printf("Missing open bracket\n"); - return NULL; //should never happen - } - - if (buffer[pos+1] == '/') - { - Con_Printf("Unexpected close tag.\n"); - return NULL; //err, terminating a parent tag - } - - tagend = strchr(buffer+pos, '>'); - if (!tagend) - { - Con_Printf("Missing close bracket\n"); - return NULL; //should never happen - } - *tagend = '\0'; - tagend++; - - - //assume no nulls in the tag header. - - tagstart = buffer+pos+1; - tagstart = COM_Parse(tagstart); - if (!tagstart) - { - Con_Printf("EOF on tag name\n"); - return NULL; - } - - pos = tagend - buffer; - - ret = malloc(sizeof(xmltree_t)); - memset(ret, 0, sizeof(*ret)); - - ns = strchr(com_token, ':'); - if (ns) - { - *ns = 0; - ns++; - - memcpy(ret->xmlns, "xmlns:", 6); - Q_strlcpy(ret->xmlns+6, com_token, sizeof(ret->xmlns)-6); - Q_strlcpy(ret->name, ns, sizeof(ret->name)); - } - else - { - Q_strlcpy(ret->xmlns, "xmlns", sizeof(ret->xmlns)); - Q_strlcpy(ret->name, com_token, sizeof(ret->name)); - } - - while(*tagstart) - { - int nlen; - - - while(*tagstart <= ' ' && *tagstart) - tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer) - - if (!*tagstart) - break; - - p = malloc(sizeof(xmlparams_t)); - nlen = 0; - while (nlen < sizeof(p->name)-2) - { - if(*tagstart <= ' ') - break; - - if (*tagstart == '=') - break; - p->name[nlen++] = *tagstart++; - } - p->name[nlen++] = '\0'; - - while(*tagstart <= ' ' && *tagstart) - tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer) - - if (*tagstart != '=') - continue; - tagstart++; - - while(*tagstart <= ' ' && *tagstart) - tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer) - - nlen = 0; - if (*tagstart == '\'') - { - tagstart++; - while (*tagstart && nlen < sizeof(p->name)-2) - { - if(*tagstart == '\'') - break; - - p->val[nlen++] = *tagstart++; - } - tagstart++; - p->val[nlen++] = '\0'; - } - else if (*tagstart == '\"') - { - tagstart++; - while (*tagstart && nlen < sizeof(p->name)-2) - { - if(*tagstart == '\"') - break; - - p->val[nlen++] = *tagstart++; - } - tagstart++; - p->val[nlen++] = '\0'; - } - else - { - while (*tagstart && nlen < sizeof(p->name)-2) - { - if(*tagstart <= ' ') - break; - - p->val[nlen++] = *tagstart++; - } - p->val[nlen++] = '\0'; - } - p->next = ret->params; - ret->params = p; - } - - ns = XML_ParameterOfTree(ret, ret->xmlns); - Q_strlcpy(ret->xmlns, ns?ns:"", sizeof(ret->xmlns)); - - ns = XML_ParameterOfTree(ret, "xmlns"); - Q_strlcpy(ret->xmlns_dflt, ns?ns:defaultnamespace, sizeof(ret->xmlns_dflt)); - - tagend[-1] = '>'; - - if (tagend[-2] == '/') - { //no body - *startpos = pos; - return ret; - } - if (ret->name[0] == '?') - { - //no body either - if (tagend[-2] == '?') - { - *startpos = pos; - return ret; - } - } - - if (headeronly) - { - *startpos = pos; - return ret; - } - - //does it have a body, or is it child tags? - - bodypos = 0; - while(1) - { - if (pos == maxpos) - { //malformed - Con_Printf("tree is malfored\n"); - XML_Destroy(ret); - return NULL; - } - - if (buffer[pos] == '<') - { - if (buffer[pos+1] == '/') - { //the end of this block - //FIXME: check name - - tagend = strchr(buffer+pos, '>'); - if (!tagend) - { - Con_Printf("No close tag\n"); - XML_Destroy(ret); - return NULL; //should never happen - } - tagend++; - pos = tagend - buffer; - break; - } - - child = XML_Parse(buffer, &pos, maxpos, false, ret->xmlns_dflt); - if (!child) - { - Con_Printf("Child block is unparsable\n"); - XML_Destroy(ret); - return NULL; - } - child->sibling = ret->child; - ret->child = child; - } - else - { - char c = buffer[pos++]; - if (bodypos < sizeof(ret->body)-1) - ret->body[bodypos++] = c; - } - } - ret->body[bodypos++] = '\0'; - - *startpos = pos; - - return ret; -} - -void XML_Destroy(xmltree_t *t) -{ - xmlparams_t *p, *np; - - if (t->child) - XML_Destroy(t->child); - if (t->sibling) - XML_Destroy(t->sibling); - - for (p = t->params; p; p = np) - { - np = p->next; - free(p); - } - free(t); -} - -xmltree_t *XML_ChildOfTree(xmltree_t *t, char *name, int childnum) -{ - for (t = t->child; t; t = t->sibling) - { - if (!strcmp(t->name, name)) - { - if (childnum-- == 0) - return t; - } - } - return NULL; -} - -void XML_ConPrintTree(xmltree_t *t, int indent) -{ - xmltree_t *c; - xmlparams_t *p; - int i; - for (i = 0; i < indent; i++) Con_Printf(" "); - - Con_Printf("<%s", t->name); - for (p = t->params; p; p = p->next) - Con_Printf(" %s='%s'", p->name, p->val); - - if (t->child) - { - Con_Printf(">\n"); - for (c = t->child; c; c = c->sibling) - XML_ConPrintTree(c , indent+2); - for (i = 0; i < indent; i++) Con_Printf(" "); - Con_Printf("\n", t->name); - } - else if (*t->body) - Con_Printf(">%s\n", t->body, t->name); - else - Con_Printf("/>\n"); -} - char base64[512+1]; unsigned int base64_len; //current output length unsigned int base64_cur; //current pending value @@ -721,24 +964,277 @@ void Base64_Finish(void) base64_cur = 0; } - -void RenameConsole(char *f) +char *TrimResourceFromJid(char *jid) { - //note that this function has a sideeffect - - //if I send a message to blah@blah.com, and they reply, the reply comes from blah@blah.com/resource - //so, if we rename the old console before printing, we don't spawn random extra consoles. - char old[256]; char *slash; - Q_strlcpy(old, f, sizeof(old)); - slash = strchr(f, '/'); + slash = strchr(jid, '/'); if (slash) { *slash = '\0'; - Con_RenameSub(f, old); + return slash+1; + } + return NULL; +} + +qboolean JCL_FindBuddy(jclient_t *jcl, char *jid, buddy_t **buddy, bresource_t **bres) +{ + char name[256]; + char *res; + buddy_t *b; + bresource_t *r = NULL; + + Q_strlcpy(name, jid, sizeof(name)); + res = TrimResourceFromJid(name); + + for (b = jcl->buddies; b; b = b->next) + { + if (!strcmp(b->accountdomain, name)) + break; + } + if (!b) + { + b = malloc(sizeof(*b) + strlen(name)); + memset(b, 0, sizeof(*b)); + b->next = jcl->buddies; + jcl->buddies = b; + strcpy(b->accountdomain, name); + Q_strlcpy(b->name, name, sizeof(b->name)); //default + } + *buddy = b; + if (res && bres) + { + for (r = b->resources; r; r = r->next) + { + if (!strcmp(r->resource, res)) + break; + } + if (!r) + { + r = malloc(sizeof(*r) + strlen(res)); + memset(r, 0, sizeof(*r)); + r->next = b->resources; + b->resources = r; + strcpy(r->resource, res); + } + *bres = r; + } + else if (bres) + *bres = NULL; + return false; +} + +void JCL_SendIQ(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, char *body) +{ + struct iq_s *iq; + + iq = malloc(sizeof(*iq)); + iq->next = jcl->pendingiqs; + jcl->pendingiqs = iq; + sprintf(iq->id, "%i", rand()); + iq->callback = callback; + + if (target) + { + if (*jcl->jid) + JCL_AddClientMessagef(jcl, "", iqtype, iq->id, jcl->jid, target); + else + JCL_AddClientMessagef(jcl, "", iqtype, iq->id, target); + } + else + { + if (*jcl->jid) + JCL_AddClientMessagef(jcl, "", iqtype, iq->id, jcl->jid); + else + JCL_AddClientMessagef(jcl, "", iqtype, iq->id); + } + JCL_AddClientMessageString(jcl, body); + JCL_AddClientMessageString(jcl, ""); +} +void JCL_SendIQf(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, char *fmt, ...) +{ + va_list argptr; + char body[2048]; + + va_start (argptr, fmt); + vsnprintf (body, sizeof(body), fmt, argptr); + va_end (argptr); + + JCL_SendIQ(jcl, callback, iqtype, target, body); +} +void JCL_SendIQNode(jclient_t *jcl, qboolean (*callback) (jclient_t *jcl, xmltree_t *tree), char *iqtype, char *target, xmltree_t *node, qboolean destroynode) +{ + char *s = XML_GenerateString(node); + JCL_SendIQ(jcl, callback, iqtype, target, s); + free(s); + if (destroynode) + XML_Destroy(node); +} +static void JCL_RosterUpdate(jclient_t *jcl, xmltree_t *listp) +{ + xmltree_t *i; + buddy_t *buddy; + int cnum = 0; + while ((i = XML_ChildOfTree(listp, "item", cnum++))) + { + char *name = XML_GetParameter(i, "name", ""); + char *jid = XML_GetParameter(i, "jid", ""); + char *sub = XML_GetParameter(i, "subscription", ""); + JCL_FindBuddy(jcl, jid, &buddy, NULL); + + if (*name) + Q_strlcpy(buddy->name, name, sizeof(buddy->name)); + buddy->friended = true; + } +} +static qboolean JCL_RosterReply(jclient_t *jcl, xmltree_t *tree) +{ + xmltree_t *c, *i; + c = XML_ChildOfTree(tree, "query", 0); + if (c) + { + JCL_RosterUpdate(jcl, c); + return true; + } + JCL_GeneratePresence(true); + return false; +} + +static qboolean JCL_BindReply(jclient_t *jcl, xmltree_t *tree) +{ + xmltree_t *c; + c = XML_ChildOfTree(tree, "bind", 0); + if (c) + { + c = XML_ChildOfTree(c, "jid", 0); + if (c) + { + Q_strlcpy(jcl->jid, c->body, sizeof(jcl->jid)); + Con_Printf("Bound to jid ^[[%s]\\xmpp\\%s^]\n", jcl->jid, jcl->jid); + return true; + } + } + return false; +} +static qboolean JCL_SessionReply(jclient_t *jcl, xmltree_t *tree) +{ + JCL_SendIQf(jcl, JCL_RosterReply, "get", NULL, ""); + return true; +} + + +static char *caps[] = +{ +#if 1 + "http://jabber.org/protocol/caps", + "http://jabber.org/protocol/disco#info", +// "http://jabber.org/protocol/disco#items", + +// "http://www.google.com/xmpp/protocol/camera/v1", +// "http://www.google.com/xmpp/protocol/session", +// "http://www.google.com/xmpp/protocol/voice/v1", +// "http://www.google.com/xmpp/protocol/video/v1", + + "jabber:iq:version", + "urn:xmpp:jingle:1", + QUAKEMEDIAXMLNS, +// "urn:xmpp:jingle:apps:rtp:1", //we don't support rtp video/voice chat +// "urn:xmpp:jingle:apps:rtp:audio",//we don't support rtp voice chat +// "urn:xmpp:jingle:apps:rtp:video",//we don't support rtp video chat + "urn:xmpp:jingle:transports:raw-udp:1", +#ifndef NOICE + "urn:xmpp:jingle:transports:ice-udp:1", +#endif +#ifndef Q3_VM + "urn:xmpp:time", +#endif + "urn:xmpp:ping", //FIXME: I'm not keen on this. I only added support to stop errors from pidgin when trying to debug. + +#else + //for testing, this is the list of features pidgin supports (which is the other client I'm testing against). + + "jabber:iq:last", + "jabber:iq:oob", + "urn:xmpp:time", + "jabber:iq:version", + "jabber:x:conference", + "http://jabber.org/protocol/bytestreams", + "http://jabber.org/protocol/caps", + "http://jabber.org/protocol/chatstates", + "http://jabber.org/protocol/disco#info", + "http://jabber.org/protocol/disco#items", + "http://jabber.org/protocol/muc", + "http://jabber.org/protocol/muc#user", + "http://jabber.org/protocol/si", +// "http://jabber.org/protocol/si/profile/file-transfer", + "http://jabber.org/protocol/xhtml-im", + "urn:xmpp:ping", + "urn:xmpp:attention:0", + "urn:xmpp:bob", + "urn:xmpp:jingle:1", + "http://www.google.com/xmpp/protocol/session", + "http://www.google.com/xmpp/protocol/voice/v1", + "http://www.google.com/xmpp/protocol/video/v1", + "http://www.google.com/xmpp/protocol/camera/v1", + "urn:xmpp:jingle:apps:rtp:1", + "urn:xmpp:jingle:apps:rtp:audio", + "urn:xmpp:jingle:apps:rtp:video", + "urn:xmpp:jingle:transports:raw-udp:1", + "urn:xmpp:jingle:transports:ice-udp:1", + "urn:xmpp:avatar:metadata", + "urn:xmpp:avatar:data", + "urn:xmpp:avatar:metadata+notify", + "http://jabber.org/protocol/mood", + "http://jabber.org/protocol/mood+notify", + "http://jabber.org/protocol/tune", + "http://jabber.org/protocol/tune+notify", + "http://jabber.org/protocol/nick", + "http://jabber.org/protocol/nick+notify", + "http://jabber.org/protocol/ibb", +#endif + NULL +}; +static void buildcaps(char *out, int outlen) +{ + int i; + Q_strncpyz(out, "", outlen); + + for (i = 0; caps[i]; i++) + { + Q_strlcat(out, "", outlen); } } +static int qsortcaps(const void *va, const void *vb) +{ + char *a = *(char**)va; + char *b = *(char**)vb; + return strcmp(a, b); +} +int SHA1(char *digest, int maxdigestsize, char *string, int stringlen); +char *buildcapshash(void) +{ + int i, l; + char out[8192]; + int outlen = sizeof(out); + unsigned char digest[64]; + + Q_strlcpy(out, "client/pc//FTEQW<", outlen); + + qsort(caps, sizeof(caps)/sizeof(caps[0]) - 1, sizeof(char*), qsortcaps); + for (i = 0; caps[i]; i++) + { + Q_strlcat(out, caps[i], outlen); + Q_strlcat(out, "<", outlen); + } + l = SHA1(digest, sizeof(digest), out, strlen(out)); + + for (i = 0; i < l; i++) + Base64_Byte(digest[i]); + Base64_Finish(); + return base64; +} #define JCL_DONE 0 #define JCL_CONTINUE 1 @@ -768,9 +1264,7 @@ int JCL_ClientFrame(jclient_t *jcl) if (ret>0) { jcl->bufferedinammount+=ret; - jcl->bufferedinmessage[jcl->bufferedinammount] = 0; - Con_TrySubPrint("xmppin", jcl->bufferedinmessage+jcl->bufferedinammount-ret); } olddepth = jcl->tagdepth; @@ -817,6 +1311,15 @@ int JCL_ClientFrame(jclient_t *jcl) tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, true, ""); } + if (jcl->streamdebug) + { + char t = jcl->bufferedinmessage[pos]; + jcl->bufferedinmessage[pos] = 0; + Con_TrySubPrint("xmppin", jcl->bufferedinmessage); + Con_TrySubPrint("xmppin", "\n"); + jcl->bufferedinmessage[pos] = t; + } + if (!tree) { Con_Printf("Not an xml stream\n"); @@ -861,6 +1364,15 @@ int JCL_ClientFrame(jclient_t *jcl) pos = 0; tree = XML_Parse(jcl->bufferedinmessage, &pos, jcl->instreampos, false, jcl->defaultnamespace); + if (jcl->streamdebug) + { + char t = jcl->bufferedinmessage[pos]; + jcl->bufferedinmessage[pos] = 0; + Con_TrySubPrint("xmppin", jcl->bufferedinmessage); + Con_TrySubPrint("xmppin", "\n"); + jcl->bufferedinmessage[pos] = t; + } + if (!tree) { // Con_Printf("No input tree: %s", jcl->bufferedinmessage); @@ -878,24 +1390,23 @@ int JCL_ClientFrame(jclient_t *jcl) if ((ot=XML_ChildOfTree(tree, "bind", 0))) { unparsable = false; - JCL_AddClientMessageString(jcl, ""); - JCL_AddClientMessageString(jcl, jcl->resource); - JCL_AddClientMessageString(jcl, ""); + JCL_SendIQf(jcl, JCL_BindReply, "set", NULL, "%s", jcl->resource); } if ((ot=XML_ChildOfTree(tree, "session", 0))) { unparsable = false; - JCL_AddClientMessageString(jcl, ""); - JCL_AddClientMessageString(jcl, ""); + JCL_SendIQf(jcl, JCL_SessionReply, "set", NULL, ""); jcl->connected = true; - JCL_AddClientMessageString(jcl, ""); + JCL_WriteConfig(); + +// JCL_AddClientMessageString(jcl, ""); } if (unparsable) { - if ((!jclient->issecure) && BUILTINISVALID(Net_SetTLSClient) && XML_ChildOfTree(tree, "starttls", 0) != NULL) + if ((!jclient->issecure) && BUILTINISVALID(Net_SetTLSClient) && XML_ChildOfTree(tree, "starttls", 0) != NULL && !Cvar_GetFloat("xmpp_disabletls")) { Con_Printf("Attempting to switch to TLS\n"); JCL_AddClientMessageString(jcl, ""); @@ -908,9 +1419,9 @@ int JCL_ClientFrame(jclient_t *jcl) if (!strcmp(ot->body, "PLAIN")) { char msg[2048]; - if (jclient->noplain && !jclient->issecure) //probably don't send plain without tls. + if (!jclient->issecure && !Cvar_GetFloat("xmpp_allowplainauth")) //probably don't send plain without tls. { - //plain can still be read with man-in-the-middle attacks, of course, even with stl. + //plain can still be read with man-in-the-middle attacks, of course, even with tls if the certificate is spoofed. Con_Printf("Ignoring auth \'%s\'\n", ot->body); continue; } @@ -1014,12 +1525,12 @@ int JCL_ClientFrame(jclient_t *jcl) char *to; char *id; - id = XML_ParameterOfTree(tree, "id"); - from = XML_ParameterOfTree(tree, "from"); - to = XML_ParameterOfTree(tree, "to"); + id = XML_GetParameter(tree, "id", ""); + from = XML_GetParameter(tree, "from", ""); + to = XML_GetParameter(tree, "to", ""); - f = XML_ParameterOfTree(tree, "type"); - if (f && !strcmp(f, "get")) + f = XML_GetParameter(tree, "type", ""); + if (!strcmp(f, "get")) { ot = XML_ChildOfTree(tree, "query", 0); if (ot) @@ -1027,21 +1538,19 @@ int JCL_ClientFrame(jclient_t *jcl) if (from && !strcmp(ot->xmlns, "http://jabber.org/protocol/disco#info")) { //http://xmpp.org/extensions/xep-0030.html char msg[2048]; + char *hash; int idletime = 0; unparsable = false; - Q_snprintf(msg, sizeof(msg), + buildcaps(msg, sizeof(msg)); + hash = buildcapshash(); + + JCL_AddClientMessagef(jcl, "" - "" - "" - "" -#ifndef Q3_VM - "" -#endif + "" + "%s" "" - "", from, id, idletime); - - JCL_AddClientMessageString(jcl, msg); + "", from, id, hash, msg); } else if (from && !strcmp(ot->xmlns, "jabber:iq:version")) { //client->client version request @@ -1051,10 +1560,12 @@ int JCL_ClientFrame(jclient_t *jcl) Q_snprintf(msg, sizeof(msg), "" "" - "FTEQW Jabber Plugin" + "FTEQW XMPP" "V"JCL_BUILD"" #ifdef Q3_VM "QVM plugin" +#else + //don't specify the os otherwise, as it gives away required base addresses etc for exploits #endif "" "", from, id); @@ -1086,26 +1597,35 @@ int JCL_ClientFrame(jclient_t *jcl) char timestamp[256]; int idletime = 0; struct tm * timeinfo; - int tzs; + int tzh, tzm; time_t rawtime; time (&rawtime); + timeinfo = localtime(&rawtime); + tzh = timeinfo->tm_hour; + tzm = timeinfo->tm_min; timeinfo = gmtime (&rawtime); - tzs = _timezone; - tzs *= -1; - Q_snprintf(tz, sizeof(tz), "%+i:%i", tzs/(60*60), abs(tzs/60) % 60); + tzh -= timeinfo->tm_hour; + tzm -= timeinfo->tm_min; + Q_snprintf(tz, sizeof(tz), "%+i:%i", tzh, tzm); strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", timeinfo); unparsable = false; //strftime Q_snprintf(msg, sizeof(msg), "" "" "", from, id, tz, timestamp); JCL_AddClientMessageString(jcl, msg); } #endif + + ot = XML_ChildOfTree(tree, "ping", 0); + if (ot && !strcmp(ot->xmlns, "urn:xmpp:ping")) + { + JCL_AddClientMessagef(jcl, "", from, id); + } if (unparsable) { //unsupported stuff @@ -1125,16 +1645,49 @@ int JCL_ClientFrame(jclient_t *jcl) JCL_AddClientMessageString(jcl, msg); } } - else if (f && !strcmp(f, "result")) + else if (!strcmp(f, "set")) { - xmltree_t *c, *v; - unparsable = false; - c = XML_ChildOfTree(tree, "bind", 0); - if (c) + xmltree_t *c; + + c = XML_ChildOfTree(tree, "query", 0); + if (c && !strcmp(c->xmlns, "jabber:iq:roster")) { - v = XML_ChildOfTree(c, "jid", 0); - if (v) - Con_Printf("Bound to jid \"%s\"\n", v->body); + unparsable = false; + JCL_RosterUpdate(jcl, c); + } + + c = XML_ChildOfTree(tree, "jingle", 0); + if (c && !strcmp(c->xmlns, "urn:xmpp:jingle:1")) + { + JCL_ParseJingle(jcl, c, from, id); + unparsable = false; + } + } + else if (!strcmp(f, "result") || !strcmp(f, "error")) + { + char *id = XML_GetParameter(tree, "id", ""); + struct iq_s **link, *iq; + unparsable = false; + for (link = &jcl->pendingiqs; *link; link = &(*link)->next) + { + iq = *link; + if (!strcmp(iq->id, id)) + break; + } + if (*link) + { + iq = *link; + *link = iq->next; + + if (iq->callback) + { + if (!iq->callback(jcl, !strcmp(f, "error")?NULL:tree)) + { + Con_Print("Invalid iq result\n"); + XML_ConPrintTree(tree, 0); + } + } + free(iq); } else { @@ -1152,90 +1705,169 @@ int JCL_ClientFrame(jclient_t *jcl) } else if (!strcmp(tree->name, "message")) { - f = XML_ParameterOfTree(tree, "from"); + f = XML_GetParameter(tree, "from", NULL); - if (f) - { - Q_strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest)); - RenameConsole(f); - } - - if (f) - { - ot = XML_ChildOfTree(tree, "composing", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s is typing\r", f); - } - ot = XML_ChildOfTree(tree, "paused", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s has stopped typing\r", f); - } - ot = XML_ChildOfTree(tree, "inactive", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s has become inactive\r", f); - } - ot = XML_ChildOfTree(tree, "active", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "\r", f); - } - ot = XML_ChildOfTree(tree, "gone", 0); - if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) - { - unparsable = false; - Con_SubPrintf(f, "%s has gone away\r", f); - } - } - - ot = XML_ChildOfTree(tree, "body", 0); - if (ot) - { + if (f && !strcmp(f, jcl->jid)) unparsable = false; + else + { if (f) - Con_SubPrintf(f, "%s: %s\n", f, ot->body); - else - Con_Print(ot->body); + { + buddy_t *b; + bresource_t *br; + Q_strlcpy(jcl->defaultdest, f, sizeof(jcl->defaultdest)); - LocalSound("misc/talk.wav"); - } + JCL_FindBuddy(jcl, f, &b, &br); + f = b->name; + b->defaultresource = br; + } - if (unparsable) - { - unparsable = false; - Con_Print("Received a message without a body\n"); - XML_ConPrintTree(tree, 0); + if (f) + { + ot = XML_ChildOfTree(tree, "composing", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "%s is typing\r", f); + } + ot = XML_ChildOfTree(tree, "paused", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "%s has stopped typing\r", f); + } + ot = XML_ChildOfTree(tree, "inactive", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "\r", f); + } + ot = XML_ChildOfTree(tree, "active", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "\r", f); + } + ot = XML_ChildOfTree(tree, "gone", 0); + if (ot && !strcmp(ot->xmlns, "http://jabber.org/protocol/chatstates")) + { + unparsable = false; + Con_SubPrintf(f, "%s has gone away\r", f); + } + } + + ot = XML_ChildOfTree(tree, "body", 0); + if (ot) + { + unparsable = false; + if (f) + Con_SubPrintf(f, "%s: %s\n", f, ot->body); + else + Con_Print(ot->body); + + if (BUILTINISVALID(LocalSound)) + LocalSound("misc/talk.wav"); + } + + if (unparsable) + { + unparsable = false; + if (jcl->streamdebug) + { + Con_Print("Received a message without a body\n"); + XML_ConPrintTree(tree, 0); + } + } } } else if (!strcmp(tree->name, "presence")) { - char *from = XML_ParameterOfTree(tree, "from"); + buddy_t *buddy; + bresource_t *bres; + + char *from = XML_GetParameter(tree, "from", ""); xmltree_t *show = XML_ChildOfTree(tree, "show", 0); xmltree_t *status = XML_ChildOfTree(tree, "status", 0); xmltree_t *quake = XML_ChildOfTree(tree, "quake", 0); - xmltree_t *serverip = NULL; + char *type = XML_GetParameter(tree, "type", ""); + char *serverip = NULL; + char *servermap = NULL; + char *server; if (quake && !strcmp(quake->xmlns, "fteqw.com:game")) - serverip = XML_ChildOfTree(quake, "serverip", 0); + { + serverip = XML_GetParameter(quake, "serverip", NULL); + servermap = XML_GetParameter(quake, "servermap", NULL); + } - if (serverip && *serverip->body) - Con_Printf("%s is now playing quake! ^[JOIN THEM!\\join\\%s^]\n", from, serverip->body); - else if (status && *status->body) - Con_Printf("%s is now %s: %s\n", from, show?show->body:"present", status->body); + if (type && !strcmp(type, "subscribe")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] wants to be your friend! ^[[Authorize]\\xmppauth\\%s^] ^[[Deny]\\xmppdeny\\%s^]\n", from, from, from, from); + } + else if (type && !strcmp(type, "unsubscribe")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] has unfriended you\n", from, from); + } + else if (type && !strcmp(type, "unsubscribed")) + { + Con_Printf("^[[%s]\\xmpp\\%s^] is no longer unfriended you\n", from, from); + } else - Con_Printf("%s is now %s\n", from, show?show->body:"present"); + { + JCL_FindBuddy(jcl, from, &buddy, &bres); + + if (bres) + { + if (servermap) + { + bres->servertype = 2; + Q_strlcpy(bres->server, servermap, sizeof(bres->server)); + } + else if (serverip) + { + bres->servertype = 1; + Q_strlcpy(bres->server, serverip, sizeof(bres->server)); + } + else + { + bres->servertype = 0; + Q_strlcpy(bres->server, "", sizeof(bres->server)); + } + Q_strlcpy(bres->fstatus, (status && *status->body)?status->body:"", sizeof(bres->fstatus)); + if (!tree->child) + Q_strlcpy(bres->bstatus, "offline", sizeof(bres->bstatus)); + else + Q_strlcpy(bres->bstatus, (show && *show->body)?show->body:"present", sizeof(bres->bstatus)); + + if (bres->servertype == 2) + Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\xmppjoin\\%s^]\n", buddy->name, from, bres->server, from); + else if (bres->servertype == 1) + Con_Printf("^[[%s]\\xmpp\\%s^] is now ^[[Playing Quake - %s]\\observe\\%s^]\n", buddy->name, from, bres->server, bres->server); + else if (*bres->fstatus) + Con_Printf("^[[%s]\\xmpp\\%s^] is now %s: %s\n", buddy->name, from, bres->bstatus, bres->fstatus); + else + Con_Printf("^[[%s]\\xmpp\\%s^] is now %s\n", buddy->name, from, bres->bstatus); + + if (!tree->child) + { + //remove this buddy resource + } + } + else + { + Con_Printf("Weird presence:\n"); + XML_ConPrintTree(tree, 0); + } + } //we should keep a list of the people that we know of. unparsable = false; } else + { Con_Printf("JCL unrecognised stanza: %s\n", tree->name); + XML_ConPrintTree(tree, 0); + } XML_Destroy(tree); @@ -1261,6 +1893,67 @@ void JCL_CloseConnection(jclient_t *jcl) free(jcl); } +//can be polled for server address updates +void JCL_GeneratePresence(qboolean force) +{ + int dummystat; + char serveraddr[1024*16]; + char servermap[1024*16]; + //get the last server address + + serveraddr[0] = 0; + servermap[0] = 0; + + if (!Cvar_GetFloat("xmpp_nostatus")) + { + if (Cvar_GetFloat("sv.state")) + { + Cvar_GetString("sv.mapname", servermap, sizeof(servermap)); + } + else + { + if (!Cvar_GetString("cl_serveraddress", serveraddr, sizeof(serveraddr))) + serveraddr[0] = 0; + if (BUILTINISVALID(CL_GetStats)) + { + //if we can't get any stats, its because we're not actually on the server. + if (!CL_GetStats(0, &dummystat, 1)) + serveraddr[0] = 0; + } + } + } + + if (force || strcmp(jclient->curquakeserver, *servermap?servermap:serveraddr)) + { + char caps[256]; + char *caphash; + Q_strlcpy(jclient->curquakeserver, *servermap?servermap:serveraddr, sizeof(jclient->curquakeserver)); + + //note: ext='voice-v1 camera-v1 video-v1' is some legacy nonsense, and is required for voice calls with googletalk clients or something stupid like that + Q_snprintf(caps, sizeof(caps), "", buildcapshash()); + + if (!*jclient->curquakeserver) + JCL_AddClientMessagef(jclient, + "" + "%s" + "", caps); + else if (*servermap) //if we're running a server, say so + JCL_AddClientMessagef(jclient, + "" + "" + "%s" + "" + , servermap, caps); + else //if we're connected to a server, say so + JCL_AddClientMessagef(jclient, + "" + "" + "%s" + "" + ,jclient->curquakeserver, caps); + } +} + //functions above this line allow connections to multiple servers. //it is just the control functions that only allow one server. @@ -1271,31 +1964,7 @@ qintptr_t JCL_Frame(qintptr_t *args) { if (jclient->connected) { - int dummystat; - char serveraddr[1024*16]; - //get the last server address - if (!Cvar_GetString("cl_serveraddress", serveraddr, sizeof(serveraddr))) - serveraddr[0] = 0; - //if we can't get any stats, its because we're not actually on the server. - if (!CL_GetStats(0, &dummystat, 1)) - serveraddr[0] = 0; - - if (strcmp(jclient->curquakeserver, serveraddr)) - { - char msg[1024]; - Q_strlcpy(jclient->curquakeserver, serveraddr, sizeof(jclient->curquakeserver)); - if (!*jclient->curquakeserver) - Q_strlcpy(msg, "", sizeof(msg)); - else - Q_snprintf(msg, sizeof(msg), - "" - "" - "sha1-hash-of-image" - "" - "" - ); - JCL_AddClientMessageString(jclient, msg); - } + JCL_GeneratePresence(false); } while(stat == JCL_CONTINUE) @@ -1306,11 +1975,126 @@ qintptr_t JCL_Frame(qintptr_t *args) jclient = NULL; } + + JCL_FlushOutgoing(jclient); } return 0; } -void JCL_Command(void) +void JCL_WriteConfig(void) +{ + if (jclient->connected) + { + qhandle_t config; + FS_Open("**plugconfig", &config, 2); + if (config >= 0) + { + char buffer[8192]; + Q_snprintf(buffer, sizeof(buffer), "%i \"%s\" \"%s@%s\" \"%s\"\n", + jclient->tlsconnect, jclient->server, jclient->username, jclient->domain, jclient->password); + FS_Write(config, buffer, strlen(buffer)); + FS_Close(config); + } + } +} +void JCL_LoadConfig(void) +{ + if (!jclient) + { + int len; + qhandle_t config; + char buf[8192]; + char tls[256]; + char server[256]; + char account[256]; + char password[256]; + char *line = buf; + qboolean oldtls; + len = FS_Open("**plugconfig", &config, 1); + if (config >= 0) + { + if (len >= sizeof(buf)) + len = sizeof(buf)-1; + buf[len] = 0; + FS_Read(config, buf, len); + FS_Close(config); + + line = COM_ParseOut(line, tls, sizeof(tls)); + line = COM_ParseOut(line, server, sizeof(server)); + line = COM_ParseOut(line, account, sizeof(account)); + line = COM_ParseOut(line, password, sizeof(password)); + + oldtls = atoi(tls); + + jclient = JCL_Connect(server, oldtls?5223:5222, oldtls, account, password); + } + } +} +void JCL_PrintBuddyList(char *console, jclient_t *jcl, qboolean all) +{ + buddy_t *b; + bresource_t *r; + if (!jcl->buddies) + Con_SubPrintf(console, "You have no friends\n"); + for (b = jcl->buddies; b; b = b->next) + { + //if we don't actually know them, don't list them. + if (!b->friended) + continue; + + if (!b->resources) //offline + { + if (all) + Con_SubPrintf(console, "^[^7[%s]\\xmpp\\%s^]: offline\n", b->name, b->accountdomain); + } + else if (b->resources->next) + { //multiple potential resources + Con_SubPrintf(console, "^[[%s]\\xmpp\\%s^]\n", b->name, b->accountdomain); + for (r = b->resources; r; r = r->next) + { + if (r->servertype == 2) + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\xmppjoin\\%s/%s^]\n", r->resource, b->accountdomain, r->resource, r->server, b->accountdomain, r->resource); + else if (r->servertype) + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\observe\\%s^]\n", r->resource, b->accountdomain, r->resource, r->server, r->server); + else if (*r->fstatus) + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: %s - %s\n", r->resource, b->accountdomain, r->resource, r->bstatus, r->fstatus); + else + Con_SubPrintf(console, " ^[[%s]\\xmpp\\%s/%s^]: %s\n", r->resource, b->accountdomain, r->resource, r->bstatus); + } + } + else //only one resource + { + r = b->resources; + if (!strcmp(r->server, "-")) + Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake]\\xmppjoin\\%s/%s^]\n", b->name, b->accountdomain, r->resource, b->accountdomain, r->resource); + else if (*r->server) + Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: ^[[Playing Quake - %s]\\observe\\%s^]\n", b->name, b->accountdomain, r->resource, r->server, r->server); + else if (*r->fstatus) + Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: %s - %s\n", b->name, b->accountdomain, r->resource, r->bstatus, r->fstatus); + else + Con_SubPrintf(console, "^[[%s]\\xmpp\\%s/%s^]: %s\n", b->name, b->accountdomain, r->resource, r->bstatus); + } + } +} + +void JCL_SendMessage(jclient_t *jcl, char *to, char *msg) +{ + char markup[256]; + char *d; + buddy_t *b; + bresource_t *br; + JCL_FindBuddy(jcl, to, &b, &br); + if (br) + JCL_AddClientMessagef(jcl, "", b->accountdomain, br->resource); + else + JCL_AddClientMessagef(jcl, "", b->accountdomain); + JCL_AddClientMessage(jcl, markup, XML_Markup(msg, markup, sizeof(markup)) - markup); + JCL_AddClientMessageString(jcl, ""); + Con_SubPrintf(b->name, "%s: "COLOURYELLOW"%s\n", ">>", msg); +} + + +void JCL_Command(char *console) { char imsg[8192]; char arg[6][256]; @@ -1324,34 +2108,33 @@ void JCL_Command(void) { if (!msg) continue; - msg = COM_Parse(msg); - Q_strlcpy(arg[i], com_token, sizeof(arg[i])); + msg = COM_ParseOut(msg, arg[i], sizeof(arg[i])); } if (*arg[0] == '/') { if (!strcmp(arg[0]+1, "tlsopen") || !strcmp(arg[0]+1, "tlsconnect")) - { + { //tlsconnect is 'old'. if (!*arg[1]) { - Con_Printf("tlsopen [server] [account] [password]\n"); + Con_TrySubPrint(console, "tlsopen [server] [account] [password]\n"); return; } if (jclient) { - Con_Printf("You are already connected\nPlease /quit first\n"); + Con_TrySubPrint(console, "You are already connected\nPlease /quit first\n"); return; } if (!*arg[1]) { - Con_Printf("%s \n", arg[0]+1); + Con_SubPrintf(console, "%s \n", arg[0]+1); return; } jclient = JCL_Connect(arg[1], 5223, true, arg[2], arg[3]); if (!jclient) { - Con_Printf("Connect failed\n"); + Con_TrySubPrint(console, "Connect failed\n"); return; } } @@ -1359,74 +2142,154 @@ void JCL_Command(void) { if (!*arg[1]) { - Con_Printf("open [server] [account] [password]\n"); + Con_TrySubPrint(console, "open [server] [account] [password]\n"); return; } if (jclient) { - Con_Printf("You are already connected\nPlease /quit first\n"); + Con_TrySubPrint(console, "You are already connected\nPlease /quit first\n"); return; } if (!*arg[1]) { - Con_Printf("%s \n", arg[0]+1); + Con_SubPrintf(console, "%s \n", arg[0]+1); return; } jclient = JCL_Connect(arg[1], 5222, false, arg[2], arg[3]); if (!jclient) { - Con_Printf("Connect failed\n"); + Con_TrySubPrint(console, "Connect failed\n"); return; } } + else if (!strcmp(arg[0]+1, "help")) + { + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /tlsconnect XMPPSERVER USERNAME@DOMAIN PASSWORD^]\n"); + if (BUILTINISVALID(Net_SetTLSClient)) + Con_Printf("for example: ^[/" COMMANDPREFIX " /tlsconnect talk.google.com myusername@gmail.com mypassword^]\n" + "Note that this info will be used the next time you start quake.\n"); + + //small note: + //for the account 'me@example.com' the server to connect to can be displayed with: + //nslookup -querytype=SRV _xmpp-client._tcp.example.com + //srv resolving seems to be non-standard on each system, I don't like having to special case things. + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /help^]\n" + "This text...\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /raw ^]\n" + "For debug hackery.\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /friend accountname friendlyname^]\n" + "Befriends accountname, and shows them in your various lists using the friendly name. Can also be used to rename friends.\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /unfriend accountname^]\n" + "Ostracise your new best enemy. You will no longer see them and they won't be able to contact you.\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /blist^]\n" + "Show all your friends! Names are clickable and will begin conversations.\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /quit^]\n" + "Disconnect from the XMPP server, noone will be able to hear you scream.\n"); + Con_TrySubPrint(console, "^[/" COMMANDPREFIX " /msg ACCOUNTNAME your message goes here^]\n" + "Sends a message to the named person. If given a resource postfix, your message will be sent only to that resource.\n"); + Con_TrySubPrint(console, "If no arguments, will print out your friends list. If no /command is used, the arguments will be sent as a message to the person you last sent a message to.\n"); + } else if (!jclient) { - Con_Printf("You are not connected. Cannot %s\n", arg[0]); + Con_SubPrintf(console, "You are not connected. Cannot %s\n", arg[0]); } else if (!strcmp(arg[0]+1, "quit")) { + //disconnect from the xmpp server. JCL_CloseConnection(jclient); jclient = NULL; } + else if (!strcmp(arg[0]+1, "blist")) + { + //print out a full list of everyone, even those offline. + JCL_PrintBuddyList(console, jclient, true); + } + else if (!strcmp(arg[0]+1, "clear")) + { + //just clears the current console. + if (*console) + { + Con_Destroy(console); + Con_TrySubPrint(console, ""); + Con_SetActive(console); + } + else + Cmd_AddText("\nclear\n", true); + } else if (!strcmp(arg[0]+1, "msg")) { + //FIXME: validate the dest. deal with xml markup in dest. + buddy_t *b; + bresource_t *br; Q_strlcpy(jclient->defaultdest, arg[1], sizeof(jclient->defaultdest)); msg = arg[2]; - JCL_AddClientMessageString(jclient, ""); - JCL_AddClientMessageString(jclient, msg); - JCL_AddClientMessageString(jclient, ""); - - Con_SubPrintf(jclient->defaultdest, "%s: "COLOURYELLOW"%s\n", ">>", msg); + JCL_SendMessage(jclient, jclient->defaultdest, msg); } - else if (!strcmp(arg[0]+1, "raw")) + else if (!strcmp(arg[0]+1, "friend")) { + //FIXME: validate the name. deal with xml markup. + + //can also rename. We should probably read back the groups for the update. + JCL_SendIQf(jclient, NULL, "set", NULL, "", arg[1], arg[2]); + + //start looking for em + JCL_AddClientMessagef(jclient, "", arg[1]); + + //let em see us + if (jclient->preapproval) + JCL_AddClientMessagef(jclient, "", arg[1]); + } + else if (!strcmp(arg[0]+1, "unfriend")) + { + //FIXME: validate the name. deal with xml markup. + + //hide from em + JCL_AddClientMessagef(jclient, "", arg[1]); + + //stop looking for em + JCL_AddClientMessagef(jclient, "", arg[1]); + + //stop listing em + JCL_SendIQf(jclient, NULL, "set", NULL, "", arg[1]); + } + else if (!strcmp(arg[0]+1, "join")) + { + JCL_Join(jclient, arg[1]); + } + else if (!strcmp(arg[0]+1, "raw")) + { + jclient->streamdebug = true; JCL_AddClientMessageString(jclient, arg[1]); } else - Con_Printf("Unrecognised command: %s\n", arg[0]); + Con_SubPrintf(console, "Unrecognised command: %s\n", arg[0]); } else { if (jclient) { + buddy_t *b; + bresource_t *br; msg = imsg; - JCL_AddClientMessageString(jclient, ""); - JCL_AddClientMessageString(jclient, msg); - JCL_AddClientMessageString(jclient, ""); - Con_SubPrintf(jclient->defaultdest, "%s: "COLOURYELLOW"%s\n", ">>", msg); + if (!*msg) + { + if (!*console) + { + JCL_PrintBuddyList(console, jclient, false); + Con_TrySubPrint(console, "For help, type \"^[/" COMMANDPREFIX " /help^]\"\n"); + } + } + else + { + JCL_SendMessage(jclient, jclient->defaultdest, msg); + } } else { - Con_Printf("Not connected\ntype \"" COMMANDPREFIX " /connect JABBERSERVER USERNAME@DOMAIN PASSWORD\" to connect\n"); - if (BUILTINISVALID(Net_SetTLSClient)) - Con_Printf("eg: " COMMANDPREFIX " /tlsconnect talk.google.com myusername@gmail.com mypassword\n"); + Con_TrySubPrint(console, "Not connected. For help, type \"^[/" COMMANDPREFIX " /help^]\"\n"); } } } diff --git a/plugins/jabber/xml.c b/plugins/jabber/xml.c new file mode 100644 index 00000000..5db94c8e --- /dev/null +++ b/plugins/jabber/xml.c @@ -0,0 +1,527 @@ +#include "../plugin.h" + +#include "xml.h" + +void XML_Destroy(xmltree_t *t); + +char *XML_GetParameter(xmltree_t *t, char *paramname, char *def) +{ + xmlparams_t *p; + if (t) + { + for (p = t->params; p; p = p->next) + if (!strcmp(p->name, paramname)) + return p->val; + } + return def; +} +void XML_AddParameter(xmltree_t *t, char *paramname, char *value) +{ + xmlparams_t *p = malloc(sizeof(xmlparams_t)); + Q_strlcpy(p->name, paramname, sizeof(p->name)); + Q_strlcpy(p->val, value, sizeof(p->val)); + + if (t->params) //reverse insert + { + xmlparams_t *prev; + for(prev = t->params; prev->next; prev = prev->next) + ; + prev->next = p; + p->next = NULL; + } + else + { + p->next = t->params; + t->params = p; + } +} +void XML_AddParameteri(xmltree_t *t, char *paramname, int value) +{ + char svalue[64]; + Q_snprintf(svalue, sizeof(svalue), "%i", value); + XML_AddParameter(t, paramname, svalue); +} +xmltree_t *XML_CreateNode(xmltree_t *parent, char *name, char *xmlns, char *body) +{ + struct subtree_s *node = malloc(sizeof(*node)); + + //clear out links + node->params = NULL; + node->child = NULL; + node->sibling = NULL; + //link into parent if we actually have a parent. + if (parent) + { + node->sibling = parent->child; + parent->child = node; + } + + Q_strlcpy(node->name, name, sizeof(node->name)); + Q_strlcpy(node->xmlns, xmlns, sizeof(node->xmlns)); + Q_strlcpy(node->xmlns_dflt, xmlns, sizeof(node->xmlns_dflt)); + Q_strlcpy(node->body, body, sizeof(node->xmlns_dflt)); + + if (*xmlns) + XML_AddParameter(node, "xmlns", xmlns); + + return node; +} + +const struct +{ + char code; + int namelen; + char *name; +} xmlchars[] = +{ + {'<', 2, "lt"}, + {'>', 2, "gt"}, + {'&', 3, "amp"}, + {'\'', 4, "apos"}, + {'\"', 4, "quot"}, + {0, 0, NULL} +}; +//converts < to < etc. +//returns the end of d. +char *XML_Markup(char *s, char *d, int dlen) +{ + int i; + dlen--; + while(*s) + { + for(i = 0; xmlchars[i].name; i++) + { + if (*s == xmlchars[i].code) + break; + } + if (xmlchars[i].name) + { + if (dlen < xmlchars[i].namelen+2) + break; + *d++ = '&'; + memcpy(d, xmlchars[i].name, xmlchars[i].namelen); + d+=xmlchars[i].namelen; + *d++ = ';'; + *s++; + } + else + { + if (!dlen) + break; + *d++ = *s++; + } + } + *d = 0; + return d; +} +//inplace. result will always be same length or shorter. +//converts < etc to their original chars +void XML_Unmark(char *s) +{ + char *d; + int i; + + for (d = s; *s; ) + { + if (*s == '&') + { + s++; + for (i = 0; xmlchars[i].name; i++) + { + if (!strncmp(s, xmlchars[i].name, xmlchars[i].namelen) && s[xmlchars[i].namelen] == ';') + break; + } + if (xmlchars[i].name) + { + s += xmlchars[i].namelen+1; + *d++ = xmlchars[i].code; + } + else + { + *d++ = '&'; + } + } + else + *d++ = *s++; + } + *d = 0; +} + +struct buf_ctx +{ + char *buf; + int len; + int maxlen; +}; +static void buf_cat(struct buf_ctx *buf, char *data, int datalen) +{ + int newlen = buf->len + datalen+1; + if (newlen > buf->maxlen) + { + char *newd; + newlen *= 2; + newd = malloc(newlen); + memcpy(newd, buf->buf, buf->len); + free(buf->buf); + buf->buf = newd; + buf->maxlen = newlen; + } + + memcpy(buf->buf + buf->len, data, datalen); + buf->len += datalen; +} +static XML_DumpToBuf(struct buf_ctx *buf, xmltree_t *t, int indent) +{ + xmltree_t *c; + xmlparams_t *p; + int i; + for (i = 0; i < indent; i++) + buf_cat(buf, " ", 1); + + buf_cat(buf, "<", 1); + buf_cat(buf, t->name, strlen(t->name)); + + for (p = t->params; p; p = p->next) + { + buf_cat(buf, " ", 1); + buf_cat(buf, p->name, strlen(p->name)); + buf_cat(buf, "=\'", 2); + buf_cat(buf, p->val, strlen(p->val)); + buf_cat(buf, "\'", 1); + } + + if (t->child) + { + buf_cat(buf, ">", 1); + if (indent>=0) + buf_cat(buf, "\n", 1); + for (c = t->child; c; c = c->sibling) + XML_DumpToBuf(buf, c, ((indent<0)?indent:(indent+2))); + for (i = 0; i < indent; i++) + buf_cat(buf, " ", 1); + buf_cat(buf, "name, strlen(t->name)); + buf_cat(buf, ">", 1); + } + else if (*t->body) + { + buf_cat(buf, ">", 1); + buf_cat(buf, t->body, strlen(t->body)); + buf_cat(buf, "name, strlen(t->name)); + buf_cat(buf, ">", 1); + } + else + { + buf_cat(buf, "/>", 2); + } + if (indent>=0) + buf_cat(buf, "\n", 1); +} + +char *XML_GenerateString(xmltree_t *root) +{ + struct buf_ctx buf = {NULL, 0, 0}; + XML_DumpToBuf(&buf, root, -1); + buf_cat(&buf, "", 1); + return buf.buf; +} +xmltree_t *XML_Parse(char *buffer, int *startpos, int maxpos, qboolean headeronly, char *defaultnamespace) +{ + xmlparams_t *p; + xmltree_t *child; + xmltree_t *ret; + int bodypos; + int pos, i; + char *tagend; + char *tagstart; + char *ns; + char token[1024]; + pos = *startpos; + while (buffer[pos] >= '\0' && buffer[pos] <= ' ') + { + if (pos >= maxpos) + break; + pos++; + } + + if (pos == maxpos) + { + *startpos = pos; + return NULL; //nothing anyway. + } + + //expect a < + + if (buffer[pos] != '<') + { + Con_Printf("Missing open bracket\n"); + return NULL; //should never happen + } + + if (buffer[pos+1] == '/') + { + Con_Printf("Unexpected close tag.\n"); + return NULL; //err, terminating a parent tag + } + + tagend = strchr(buffer+pos, '>'); + if (!tagend) + { + Con_Printf("Missing close bracket\n"); + return NULL; //should never happen + } + *tagend = '\0'; + tagend++; + + + //assume no nulls in the tag header. + tagstart = buffer+pos+1; + while (*tagstart == ' ' || *tagstart == '\n' || *tagstart == '\r' || *tagstart == '\t') + tagstart++; + for (i = 0; i < sizeof(token)-1 && *tagstart; ) + { + if (*tagstart == ' ' || *tagstart == '\n' || *tagstart == '\r' || *tagstart == '\t') + break; + token[i++] = *tagstart++; + } + token[i] = 0; + + pos = tagend - buffer; + + ret = malloc(sizeof(xmltree_t)); + memset(ret, 0, sizeof(*ret)); + + ns = strchr(token, ':'); + if (ns) + { + *ns = 0; + ns++; + + memcpy(ret->xmlns, "xmlns:", 6); + Q_strlcpy(ret->xmlns+6, token, sizeof(ret->xmlns)-6); + Q_strlcpy(ret->name, ns, sizeof(ret->name)); + } + else + { + Q_strlcpy(ret->xmlns, "xmlns", sizeof(ret->xmlns)); + Q_strlcpy(ret->name, token, sizeof(ret->name)); + } + + while(*tagstart) + { + int nlen; + + + while(*tagstart <= ' ' && *tagstart) + tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer) + + if (!*tagstart) + break; + + p = malloc(sizeof(xmlparams_t)); + nlen = 0; + while (nlen < sizeof(p->name)-2) + { + if(*tagstart <= ' ') + break; + + if (*tagstart == '=') + break; + p->name[nlen++] = *tagstart++; + } + p->name[nlen++] = '\0'; + + while(*tagstart <= ' ' && *tagstart) + tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer) + + if (*tagstart != '=') + continue; + tagstart++; + + while(*tagstart <= ' ' && *tagstart) + tagstart++; //skip whitespace (note that we know there is a null terminator before the end of the buffer) + + nlen = 0; + if (*tagstart == '\'') + { + tagstart++; + while (*tagstart && nlen < sizeof(p->name)-2) + { + if(*tagstart == '\'') + break; + + p->val[nlen++] = *tagstart++; + } + tagstart++; + p->val[nlen++] = '\0'; + } + else if (*tagstart == '\"') + { + tagstart++; + while (*tagstart && nlen < sizeof(p->name)-2) + { + if(*tagstart == '\"') + break; + + p->val[nlen++] = *tagstart++; + } + tagstart++; + p->val[nlen++] = '\0'; + } + else + { + while (*tagstart && nlen < sizeof(p->name)-2) + { + if(*tagstart <= ' ') + break; + + p->val[nlen++] = *tagstart++; + } + p->val[nlen++] = '\0'; + } + XML_Unmark(p->val); + p->next = ret->params; + ret->params = p; + } + + ns = XML_GetParameter(ret, ret->xmlns, ""); + Q_strlcpy(ret->xmlns, ns, sizeof(ret->xmlns)); + + ns = XML_GetParameter(ret, "xmlns", NULL); + Q_strlcpy(ret->xmlns_dflt, ns?ns:defaultnamespace, sizeof(ret->xmlns_dflt)); + + tagend[-1] = '>'; + + if (tagend[-2] == '/') + { //no body + *startpos = pos; + return ret; + } + if (ret->name[0] == '?') + { + //no body either + if (tagend[-2] == '?') + { + *startpos = pos; + return ret; + } + } + + if (headeronly) + { + *startpos = pos; + return ret; + } + + //does it have a body, or is it child tags? + + bodypos = 0; + while(1) + { + if (pos == maxpos) + { //malformed + Con_Printf("tree is malfored\n"); + XML_Destroy(ret); + return NULL; + } + + if (buffer[pos] == '<') + { + if (buffer[pos+1] == '/') + { //the end of this block + //FIXME: check name + + tagend = strchr(buffer+pos, '>'); + if (!tagend) + { + Con_Printf("No close tag\n"); + XML_Destroy(ret); + return NULL; //should never happen + } + tagend++; + pos = tagend - buffer; + break; + } + + child = XML_Parse(buffer, &pos, maxpos, false, ret->xmlns_dflt); + if (!child) + { + Con_Printf("Child block is unparsable\n"); + XML_Destroy(ret); + return NULL; + } + child->sibling = ret->child; + ret->child = child; + } + else + { + char c = buffer[pos++]; + if (bodypos < sizeof(ret->body)-1) + ret->body[bodypos++] = c; + } + } + ret->body[bodypos++] = '\0'; + + XML_Unmark(ret->body); + + *startpos = pos; + + return ret; +} + +void XML_Destroy(xmltree_t *t) +{ + xmlparams_t *p, *np; + + if (t->child) + XML_Destroy(t->child); + if (t->sibling) + XML_Destroy(t->sibling); + + for (p = t->params; p; p = np) + { + np = p->next; + free(p); + } + free(t); +} + +xmltree_t *XML_ChildOfTree(xmltree_t *t, char *name, int childnum) +{ + if (t) + { + for (t = t->child; t; t = t->sibling) + { + if (!strcmp(t->name, name)) + { + if (childnum-- == 0) + return t; + } + } + } + return NULL; +} + +void XML_ConPrintTree(xmltree_t *t, int indent) +{ + int start, c, chunk; + struct buf_ctx buf = {NULL, 0, 0}; + XML_DumpToBuf(&buf, t, indent); + buf_cat(&buf, "", 1); + + for (start = 0; start < buf.len; ) + { + chunk = buf.len - start; + if (chunk > 128) + chunk = 128; + c = buf.buf[start+chunk]; + buf.buf[start+chunk] = 0; + Con_Print(buf.buf+start); + buf.buf[start+chunk] = c; + + start += chunk; + } + + free(buf.buf); +} diff --git a/plugins/jabber/xml.h b/plugins/jabber/xml.h new file mode 100644 index 00000000..d7bb88f6 --- /dev/null +++ b/plugins/jabber/xml.h @@ -0,0 +1,33 @@ +typedef struct xmlparams_s +{ + char val[256]; //FIXME: make pointer + struct xmlparams_s *next; + char name[64]; //FIXME: make variable sized +} xmlparams_t; + +typedef struct subtree_s +{ + char name[64]; //FIXME: make pointer to tail of structure + char xmlns[64]; //namespace of the element //FIXME: make pointer to tail of structure + char xmlns_dflt[64]; //default namespace of children //FIXME: make pointer to tail of structure + char body[2048]; //FIXME: make pointer+variablesized + + xmlparams_t *params; + + struct subtree_s *child; + struct subtree_s *sibling; +} xmltree_t; + + + +char *XML_GetParameter(xmltree_t *t, char *paramname, char *def); +void XML_AddParameter(xmltree_t *t, char *paramname, char *value); +void XML_AddParameteri(xmltree_t *t, char *paramname, int value); +xmltree_t *XML_CreateNode(xmltree_t *parent, char *name, char *xmlns, char *body); +char *XML_Markup(char *s, char *d, int dlen); +void XML_Unmark(char *s); +char *XML_GenerateString(xmltree_t *root); +xmltree_t *XML_Parse(char *buffer, int *startpos, int maxpos, qboolean headeronly, char *defaultnamespace); +void XML_Destroy(xmltree_t *t); +xmltree_t *XML_ChildOfTree(xmltree_t *t, char *name, int childnum); +void XML_ConPrintTree(xmltree_t *t, int indent); diff --git a/plugins/mpq/fs_mpq.c b/plugins/mpq/fs_mpq.c index 852ad526..add0cae5 100644 --- a/plugins/mpq/fs_mpq.c +++ b/plugins/mpq/fs_mpq.c @@ -58,6 +58,8 @@ typedef struct typedef struct { + searchpathfuncs_t pub; + char desc[MAX_OSPATH]; vfsfile_t *file; ofs_t filestart; @@ -244,11 +246,6 @@ unsigned int mpq_lookuphash(mpqarchive_t *mpq, const char *filename, int locale) } vfsfile_t *MPQ_OpenVFS(void *handle, flocation_t *loc, const char *mode); -void MPQ_GetDisplayPath(void *handle, char *outpath, unsigned int pathsize) -{ - mpqarchive_t *mpq = handle; - Q_strlcpy(outpath, mpq->desc, pathsize); -} void MPQ_ClosePath(void *handle) { mpqarchive_t *mpq = handle; @@ -346,13 +343,13 @@ static int mpqwildcmp(const char *wild, const char *string, char **end) } return !*wild; } -int MPQ_EnumerateFiles(void *handle, const char *match, int (QDECL *func)(const char *fname, int fsize, void *parm, void *spath), void *parm) +int MPQ_EnumerateFiles(searchpathfuncs_t *handle, const char *match, int (QDECL *func)(const char *fname, int fsize, void *parm, searchpathfuncs_t *spath), void *parm) { int ok = 1; char *s, *n; char name[MAX_QPATH]; flocation_t loc; - mpqarchive_t *mpq = handle; + mpqarchive_t *mpq = (mpqarchive_t*)handle; if (mpq->listfile) { s = mpq->listfile; @@ -403,7 +400,17 @@ void MPQ_BuildHash(void *handle, int depth, void (QDECL *AddFileHash)(int depth, } } -void *MPQ_OpenNew(vfsfile_t *file, const char *desc) +int MPQ_GeneratePureCRC (void *handle, int seed, int usepure) +{ + return 0; +} + +qboolean MPQ_PollChanges(void *handle) +{ + return false; +} + +searchpathfuncs_t *MPQ_OpenArchive(vfsfile_t *file, const char *desc) { flocation_t lloc; mpqarchive_t *mpq; @@ -492,13 +499,20 @@ void *MPQ_OpenNew(vfsfile_t *file, const char *desc) break; } } - return mpq; + + mpq->pub.fsver = FSVER; + mpq->pub.ClosePath = MPQ_ClosePath; + mpq->pub.BuildHash = MPQ_BuildHash; + mpq->pub.FindFile = MPQ_FindFile; + mpq->pub.ReadFile = MPQ_ReadFile; + mpq->pub.EnumerateFiles = MPQ_EnumerateFiles; + mpq->pub.GeneratePureCRC = MPQ_GeneratePureCRC; + mpq->pub.OpenVFS = MPQ_OpenVFS; + mpq->pub.PollChanges = MPQ_PollChanges; + + return &mpq->pub; } -int MPQ_GeneratePureCRC (void *handle, int seed, int usepure) -{ - return 0; -} struct blastdata_s { @@ -815,38 +829,20 @@ vfsfile_t *MPQ_OpenVFS(void *handle, flocation_t *loc, const char *mode) return &f->funcs; } -qboolean MPQ_PollChanges(void *handle) -{ - return false; -} - -searchpathfuncs_t funcs = -{ - MPQ_GetDisplayPath, - MPQ_ClosePath, - MPQ_BuildHash, - MPQ_FindFile, - MPQ_ReadFile, - MPQ_EnumerateFiles, - MPQ_OpenNew, - MPQ_GeneratePureCRC, - MPQ_OpenVFS, - MPQ_PollChanges, -}; - qintptr_t Plug_Init(qintptr_t *args) { mpq_init_cryptography(); //we can't cope with being closed randomly. files cannot be orphaned safely. + //so ask the engine to ensure we don't get closed before everything else is. pPlug_ExportNative("UnsafeClose", NULL); - if (!pPlug_ExportNative("FS_RegisterArchiveType_mpq", &funcs)) + if (!pPlug_ExportNative("FS_RegisterArchiveType_mpq", MPQ_OpenArchive)) { Con_Printf("avplug: Engine doesn't support media decoder plugins\n"); return false; } - if (!pPlug_ExportNative("FS_RegisterArchiveType_MPQ", &funcs)) + if (!pPlug_ExportNative("FS_RegisterArchiveType_MPQ", MPQ_OpenArchive)) { Con_Printf("avplug: Engine doesn't support media decoder plugins\n"); return false; diff --git a/plugins/plugin.h b/plugins/plugin.h index 52c99202..ff65604d 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -116,6 +116,7 @@ extern "C" { extern qintptr_t (*plugin_syscall)( qintptr_t arg, ... ); void Q_strlcpy(char *d, const char *s, int n); +void Q_strlcat(char *d, const char *s, int n); int Q_snprintf(char *buffer, size_t maxlen, const char *format, ...); int Q_vsnprintf(char *buffer, size_t maxlen, const char *format, va_list vargs); diff --git a/plugins/qvm_api.c b/plugins/qvm_api.c index 3d9c6ab7..37d74c5d 100644 --- a/plugins/qvm_api.c +++ b/plugins/qvm_api.c @@ -561,4 +561,13 @@ void Q_strlcpy(char *d, const char *s, int n) } *d='\0'; } - +void Q_strlcat(char *d, const char *s, int n) +{ + if (n) + { + int dlen = strlen(d); + int slen = strlen(s)+1; + memcpy(d+dlen, s, min((n-1)-dlen, slen)); + d[n - 1] = 0; + } +} diff --git a/plugins/xsv/m_x.c b/plugins/xsv/m_x.c index 05781f19..9ee7b079 100644 --- a/plugins/xsv/m_x.c +++ b/plugins/xsv/m_x.c @@ -382,7 +382,7 @@ void X_SendIntialResponse(xclient_t *cl) setup->maxKeyCode = 255; vendor = (char *)(setup+1); - strcpy(vendor, FULLENGINENAME " X"); + strcpy(vendor, "FTE X"); setup->nbytesVendor = (strlen(vendor)+3)&~3; pixmapformats = (xPixmapFormat *)(vendor + setup->nbytesVendor);