/* * XML is a right horror to handle in its full generality; it's a * depressingly good example of second-system effect. We do a vague * approximation that, loosely speaking, depends on the server to not * depend on things like default values and namespace prefixes too * much. (As an example of the kind of horror: what does it mean if * an element's name has a namespace prefix but also has an xmlns * attribute? What if an element's name has multiple string: * prefixes? They're probably stated somewhere, but the namespaces * document is such a maze I have no idea where.) */ #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "client.h" typedef enum { MT__UNSPEC = 1, MT_CHAT, MT_ERROR, MT_GROUPCHAT, MT_HEADLINE, MT_NORMAL, } MSGTYPE; typedef struct xmpppriv XMPPPRIV; typedef struct xstack XSTACK; typedef struct xattr XATTR; typedef struct xelem XELEM; typedef struct uw_priv UW_PRIV; typedef struct sasl_state SASL_STATE; typedef struct pend_id PEND_ID; typedef struct namespace NAMESPACE; typedef struct nsname NSNAME; typedef struct jid JID; typedef struct rooms_state ROOMS_STATE; typedef struct room_query ROOM_QUERY; struct jid { // full text const char *fp; int fl; // local part const char *lp; int ll; // domain part const char *dp; int dl; // resource part const char *rp; int rl; } ; struct nsname { NSNAME *link; WSTR name; NAMESPACE *ns; } ; struct namespace { NAMESPACE *link; unsigned int hash; WSTR ns; } ; struct pend_id { PEND_ID *link; XMPPPRIV *xp; WSTR id; void (*handler)(PEND_ID *, XELEM *); void *priv; } ; struct uw_priv { FILE *f; } ; struct xelem { XELEM *sibling; XELEM *parent; char *name; XATTR *attrs; char *content; XELEM *children; NAMESPACE *ns; NAMESPACE *defns; NSNAME *nsctx; } ; struct xattr { XATTR *link; char *name; NAMESPACE *ns; char *value; } ; struct xstack { XSTACK *link; XELEM *elem; ES content; XELEM **childtail; } ; #define YXBUFSIZE 8192 // size of yxml's buffer struct xmpppriv { SERVER *s; char *yxbuf; yxml_t yx; int xerr; yxml_ret_t chain; int elopen; ES chainval; XSTACK *xstack; unsigned int flags; #define XPF_AUTH 0x00000001 #define XPF_FEATURE_BIND 0x00000002 #define XPF__ALL_FEATURES (XPF_FEATURE_BIND) SASL_STATE *sasl; PEND_ID *pend_iq; NAMESPACE *nss; char *id; int idlen; char *jidtext; JID jid; NAMESPACE *ns_xml; NAMESPACE *ns_xmlns; NAMESPACE *ns_stream; NAMESPACE *ns_xsasl; NAMESPACE *ns_jclient; NAMESPACE *ns_jxdata; NAMESPACE *ns_xbind; NAMESPACE *ns_disco_info; NAMESPACE *ns_disco_items; } ; struct sasl_state { XMPPPRIV *priv; ES snonce; ES salt; FILE *saltf; ES sver; FILE *sverf; int icnt; char *cnonce; char *cfmb; // 5802's client-first-message-bare (NUL terminated) // Names are from RFC5802. Sizes assume SHA1. unsigned char SaltedPassword[20]; unsigned char ClientKey[20]; unsigned char StoredKey[20]; unsigned char ServerKey[20]; char *AuthMessage; int AuthMessage_len; unsigned char ClientSignature[20]; unsigned char ServerSignature[20]; } ; struct rooms_state { XMPPPRIV *p; PEND_ID *pi; ROOM_QUERY *queue; } ; struct room_query { ROOM_QUERY *link; char *name; } ; #define PRIV(s) ((XMPPPRIV *)(s)->priv) #define NONCE_BASE 93 static const char nonce_charset[NONCE_BASE] = "!\"#$%&'()*+-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; static FILE *logf = 0; #define Cisspace(c) isspace((unsigned char)(c)) static int w_username_wrap(void *pv, const char *data, int len) { int i; FILE *f; f = ((UW_PRIV *)pv)->f; for (i=0;i': txt = ">"; } for (;*txt;txt++) chunk[chlen++] = *txt; break; default: chunk[chlen++] = *data; break; } } flushchunk(); } #undef CHUNKMAX /* * If this changes, xmpp_send_text may need to change too. */ static int xmppable_text(const void *data, int len) { const unsigned char *dp; for (dp=data;len>0;dp++,len--) { if ((*dp < 32) || (*dp >= 126)) return(0); } return(1); } static void warn(SERVER *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void warn(SERVER *s, const char *fmt, ...) { va_list ap; char *t; int l; va_start(ap,fmt); l = vasprintf(&t,fmt,ap); va_end(ap); foo("%s",t); ohl_append_server(s,0,0,t,l); free(t); } static JID crack_jid(const char *s, int l) { const char *p; JID r; r.fp = s; r.fl = l; p = memchr(s,'@',l); if (p) { r.lp = s; r.ll = p - s; s = p + 1; l -= r.ll + 1; } else { r.lp = 0; r.ll = 0; } r.dp = s; p = memchr(s,'/',l); if (p) { r.dl = p - s; s = p + 1; l -= r.dl + 1; } else { r.dl = l; s = 0; } if (s) { r.rp = s; r.rl = l; } else { r.rp = 0; r.rl = 0; } return(r); } static const char *bad_jid(const JID *j) { int i; if (j->lp) { for (i=j->ll-1;i>=0;i--) { switch (j->lp[i]) { case '"': case '&': case '\'': case '/': case ':': case '<': case '>': case '@': return("invalid character in local-part"); break; } if ((j->lp[i] < 32) || (j->lp[i] > 126)) { return("invalid character in local-part"); } } } if (! j->dp) return("no domain part"); for (i=j->dl-1;i>=0;i--) { switch (j->dp[i]) { case '-': case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': break; default: return("invalid character in domain name"); break; } } if (j->rp) { /* * XXX Is this right? If we need to be able to use these, code has * to be changed to escape JIDs in attributes and/or content. */ for (i=j->rl-1;i>=0;i--) { switch (j->rp[i]) { case '"': case '&': case '\'': case '<': case '>': return("invalid character in resource-part"); break; } if ((j->rp[i] < 32) || (j->rp[i] > 126)) { return("invalid character in resource-part"); } } } return(0); } static void op_xmpp_init(SERVER *s) { XMPPPRIV *p; p = malloc(sizeof(XMPPPRIV)); p->s = s; es_init(&p->chainval); p->yxbuf = malloc(YXBUFSIZE); p->elopen = 0; p->xstack = 0; xmlparse_reset(p); p->flags = 0; p->sasl = 0; p->pend_iq = 0; p->nss = 0; p->id = 0; p->idlen = 0; p->jidtext = 0; p->ns_xml = dummy_namespace(p,"(xml)"); p->ns_xmlns = dummy_namespace(p,"(xmlns)"); p->ns_stream = find_namespace(p,"http://etherx.jabber.org/streams"); p->ns_xsasl = find_namespace(p,"urn:ietf:params:xml:ns:xmpp-sasl"); p->ns_jclient = find_namespace(p,"jabber:client"); p->ns_jxdata = find_namespace(p,"jabber:x:data"); p->ns_xbind = find_namespace(p,"urn:ietf:params:xml:ns:xmpp-bind"); p->ns_disco_info = find_namespace(p,"http://jabber.org/protocol/disco#info"); p->ns_disco_items = find_namespace(p,"http://jabber.org/protocol/disco#items"); s->priv = p; } static void sasl_state_free(SASL_STATE *ss) { if (! ss) return; es_done(&ss->snonce); es_done(&ss->salt); if (ss->saltf) abort(); es_done(&ss->sver); if (ss->sverf) abort(); free(ss->cnonce); free(ss->cfmb); free(ss->AuthMessage); free(ss); } static void op_xmpp_done(SERVER *s) { XMPPPRIV *p; p = s->priv; if (! p->xerr) yxml_eof(&p->yx); free(p->yxbuf); es_done(&p->chainval); while (p->xstack) { XSTACK *x; x = p->xstack; p->xstack = x->link; xstack_free(x); } sasl_state_free(p->sasl); free(p); s->priv = 0; } static const char *op_xmpp_canuse(SERVER *s __attribute__((__unused__))) { return(pass?0:"no password specified"); } static void send_xmpp_stream_initial(SERVER *s) { JID j; const char *err; j = crack_jid(s->nick.text,s->nick.len); err = bad_jid(&j); if (err) { hlprintf_srv(s,"Unacceptable JID %.*s: %s",s->nick.len,s->nick.text,err); return; } j = crack_jid(s->host,strlen(s->host)); err = bad_jid(&j); if (!err && (j.lp || j.rp)) err = "hostname must not contain @ or /"; if (err) { hlprintf_srv(s,"Unacceptable hostname %s: %s",s->host,err); return; } xmpp_send_printf(s,"",s->nick.len,s->nick.text,s->host); } static void op_xmpp_connected(SERVER *s) { send_xmpp_stream_initial(s); } static void op_xmpp_disconnected(SERVER *s) { (void)s; } static void stream_preauth_features(XMPPPRIV *p, XELEM *e) { XELEM *mechs; XELEM *mech; int found; FILE *f; FILE *g; char *s; int l; int i; unsigned int v; struct timeval tv; unsigned char tvb[sizeof(struct timeval)+1]; ES nonce; char *at; SASL_STATE *ss; do <"found"> { for (mechs=e->children;mechs;mechs=mechs->sibling) { if ((mechs->ns == p->ns_xsasl) && !strcmp(mechs->name,"mechanisms")) { break <"found">; } } warn(p->s,"No mechanisms listed in stream features!"); p->xerr = 1; return; } while (0); found = 0; for (mech=mechs->children;mech;mech=mech->sibling) { if (strcmp(mech->name,"mechanism")) { warn(p->s,"Unrecognized child `%s' of ignored",mech->name); continue; } if (! mech->content) { warn(p->s," with no content ignored"); continue; } if (strcmp(mech->content,"SCRAM-SHA-1")) { warn(p->s,"Unrecognized `%s' ignored",mech->content); continue; } found = 1; } if (! found) { warn(p->s,"No supported auth mechanism found"); return; } ss = malloc(sizeof(SASL_STATE)); p->sasl = ss; ss->priv = p; es_init(&ss->snonce); es_init(&ss->salt); ss->saltf = 0; es_init(&ss->sver); ss->sverf = 0; ss->cnonce = 0; ss->cfmb = 0; ss->AuthMessage = 0; xmpp_send_point(p->s,"",-1); f = base64_fwrap(util_memopen_w(&s,&l),BASE64_OP_E|BASE64_DIR_W|BASE64_CLOSE); fprintf(f,"n,,"); // gs2 header, no channel binding support at = memchr(p->s->nick.text,'@',p->s->nick.len); fprintf(f,"n="); g = username_wrap(f); fwrite(p->s->nick.text,1,at?(at-p->s->nick.text):p->s->nick.len,g); // fwrite(p->s->nick.text,1,p->s->nick.len,g); fclose(g); gettimeofday(&tv,0); v = 0xf; for (i=sizeof(tv)-1;i>=0;i--) { v = ((v & 0x0f) << 8) + ((const unsigned char *)&tv)[i]; tvb[i+1] = v >> 4; } tvb[0] = (v << 4) | 0xf; l = sizeof(tv) + 1; es_init(&nonce); while (l > 0) { v = 0; for (i=l-1;i>=0;i--) { v = (v << 8) + tvb[i]; tvb[i] = v / 93; v %= 93; } es_append_1(&nonce,nonce_charset[v]); if (tvb[l-1] == 0) l --; } ss->cnonce = blk_to_nt(es_buf(&nonce),es_len(&nonce)); fprintf(f,",r=%s",ss->cnonce); fclose(f); f = util_memopen_w(&ss->cfmb,0); g = base64_fwrap(f,BASE64_OP_D|BASE64_DIR_W); fwrite(s+4,1,l-4,g); // 4: skip (encoding of) leading n,, fclose(g); putc('\0',f); fclose(f); xmpp_send_free(p->s,s,l); xmpp_send_point(p->s,"",-1); } static void hmac_sha1(const void *key, int keylen, const void *text, int textlen, unsigned char *result) { void *hi; void *ho; int i; int j; unsigned char c; unsigned char ihash[20]; hi = sha1_init(); ho = sha1_init(); j = 0; for (i=0;i<64;i++) { c = ((j < keylen) ? ((const unsigned char *)key)[j] : 0) ^ 0x36; sha1_process_bytes(hi,&c,1); c ^= 0x36 ^ 0x5c; sha1_process_bytes(ho,&c,1); j ++; } sha1_process_bytes(hi,text,textlen); sha1_result(hi,&ihash[0]); sha1_process_bytes(ho,&ihash[0],20); sha1_result(ho,result); } static void xor_into(void *to, const void *from1, const void *from2, int len) { unsigned char *t; const unsigned char *f1; const unsigned char *f2; f1 = from1; f2 = from2; t = to; for (;len>0;len--) *t++ = *f1++ ^ *f2++; } static void parse_sasl_data(FILE *f, void *cbarg, ...) { va_list ap; unsigned int k; int c; int karg; void (*chfn)(void *, int); void (*fn)(void *, int); k = 0; while (1) { c = getc(f); if (c == EOF) break; switch (k) { case 0: va_start(ap,cbarg); chfn = 0; while (1) { karg = va_arg(ap,int); if (karg == 0) break; fn = va_arg(ap,__typeof__(void (*)(void *, int))); if (c == karg) chfn = fn; } va_end(ap); k = chfn ? (c << 1) | 1 : 1; break; case 1: if (c == ',') k = 0; break; default: if (k & 1) { if (c == '=') { k &= ~1U; } else if (c == ',') { k = 0; } else { k = 1; } } else if (c == ',') { k = 0; } else { (*chfn)(cbarg,c); } break; } } } static void sasl_snonce(void *ssv, int c) { es_append_1(&((SASL_STATE *)ssv)->snonce,c); } static void sasl_sver(void *ssv, int c) { putc(c,((SASL_STATE *)ssv)->sverf); } static void sasl_salt(void *ssv, int c) { putc(c,((SASL_STATE *)ssv)->saltf); } static void sasl_icnt(void *ssv, int c) { SASL_STATE *ss; int dv; ss = ssv; dv = -1; switch (c) { case '0': if (ss->icnt == 0) { warn(ss->priv->s,"SCRAM iteration count begins with a 0"); } dv = 0; break; case '1': dv = 1; break; case '2': dv = 2; break; case '3': dv = 3; break; case '4': dv = 4; break; case '5': dv = 5; break; case '6': dv = 6; break; case '7': dv = 7; break; case '8': dv = 8; break; case '9': dv = 9; break; default: warn(ss->priv->s,"SCRAM iteration count contains invalid character %c",c); break; } if ((dv >= 0) && (ss->icnt >= 0)) { ss->icnt = (ss->icnt * 10) + dv; if (ss->icnt > 10000000) { warn(ss->priv->s,"SCRAM iteration count is unreasonable"); ss->icnt = -1; } } } static void stream_preauth_challenge(XMPPPRIV *p, XELEM *e) { SASL_STATE *ss; FILE *f; const void *un; int unlen; int passlen; void *hh; unsigned char ubuf[20]; char *sfm; int sfml; ES cfm; int i; unsigned char cp[20]; char *cq; int cql; if (! e->content) { warn(p->s,"SCRAM server challenge contains no content"); return; } f = util_teeopen_r( base64_fwrap( util_memopen_r(e->content,strlen(e->content)), BASE64_OP_D|BASE64_DIR_R|BASE64_CLOSE ), util_memopen_w(&sfm,&sfml), TEE_F_CLOSE_R|TEE_F_CLOSE_TEE ); ss = p->sasl; es_clear(&ss->snonce); es_clear(&ss->salt); ss->saltf = base64_fwrap(es_fopen_w(&ss->salt),BASE64_OP_D|BASE64_DIR_W|BASE64_CLOSE); ss->icnt = 0; parse_sasl_data(f,(void *)ss, 'r', &sasl_snonce, 's', &sasl_salt, 'i', &sasl_icnt, '\0'); fclose(f); fclose(ss->saltf); ss->saltf = 0; if (es_len(&ss->snonce) == 0) { warn(p->s,"SCRAM server challenge contains no nonce"); return; } if (es_len(&ss->salt) == 0) { warn(p->s,"SCRAM server challenge contains no salt"); return; } if (ss->icnt < 0) return; if (ss->icnt == 0) { warn(p->s,"SCRAM server challenge contains no iteration count"); return; } passlen = strlen(pass); //fprintf(p->logf,"Salt "); //foo_hex(p->logf,es_buf(&salt),es_len(&salt)); //fprintf(p->logf,"\n"); // Do SCRAM-SHA-1. Names come from RFC5802. es_append_n(&ss->salt,"\0\0\0\1",4); // salt now holds "salt + INT(1)" for Hi(). un = es_buf(&ss->salt); unlen = es_len(&ss->salt); bzero(&ss->SaltedPassword[0],20); for (i=ss->icnt;i>0;i--) { //fprintf(p->logf,"\tHMAC "); //foo_hex(p->logf,pass,passlen); //fprintf(p->logf," "); //foo_hex(p->logf,un,unlen); // un/unlen are Ui; compute Ui+1. hmac_sha1(pass,passlen,un,unlen,&ubuf[0]); // XOR it in. xor_into(&ss->SaltedPassword[0],&ss->SaltedPassword[0],&ubuf[0],20); // Reset un/unlen for next time around. //fprintf(p->logf," -> "); //foo_hex(p->logf,&ubuf[0],20); //fprintf(p->logf,"\n"); un = &ubuf[0]; unlen = 20; } //fprintf(p->logf,"SaltedPassword "); //foo_hex(p->logf,&ss->SaltedPassword[0],20); //fprintf(p->logf,"\n"); hmac_sha1(&ss->SaltedPassword[0],20,"Client Key",10,&ss->ClientKey[0]); //fprintf(p->logf,"ClientKey "); //foo_hex(p->logf,&ss->ClientKey[0],20); //fprintf(p->logf,"\n"); hh = sha1_init(); sha1_process_bytes(hh,&ss->ClientKey[0],20); sha1_result(hh,&ss->StoredKey[0]); //fprintf(p->logf,"StoredKey "); //foo_hex(p->logf,&ss->StoredKey[0],20); //fprintf(p->logf,"\n"); es_init(&cfm); es_append_n(&cfm,"c=biws,r=",9); // "biws" = base64("n,,") es_append_n(&cfm,es_buf(&ss->snonce),es_len(&ss->snonce)); i = strlen(ss->cfmb); //fprintf(p->logf,"AuthMessage pieces:\n"); ss->AuthMessage_len = i + 1 + sfml + 1 + es_len(&cfm); ss->AuthMessage = malloc(ss->AuthMessage_len); bcopy(ss->cfmb,ss->AuthMessage,i); //fprintf(p->logf," client-first-message-bare %.*s\n",i,ss->cfmb); ss->AuthMessage[i++] = ','; bcopy(sfm,ss->AuthMessage+i,sfml); i += sfml; //fprintf(p->logf," server-first-message %.*s\n",sfml,sfm); ss->AuthMessage[i++] = ','; bcopy(es_buf(&cfm),ss->AuthMessage+i,es_len(&cfm)); //fprintf(p->logf," client-final-message-without-proof %.*s\n",es_len(&cfm),es_buf(&cfm)); i += es_len(&cfm); if (i != ss->AuthMessage_len) abort(); hmac_sha1(&ss->StoredKey[0],20,ss->AuthMessage,ss->AuthMessage_len,&ss->ClientSignature[0]); //fprintf(p->logf,"ClientSignature "); //foo_hex(p->logf,&ss->ClientSignature[0],20); //fprintf(p->logf,"\n"); es_append_n(&cfm,",p=",3); f = base64_fwrap(es_fopen_w(&cfm),BASE64_OP_E|BASE64_DIR_W|BASE64_CLOSE); xor_into(&cp[0],&ss->ClientKey[0],&ss->ClientSignature[0],20); //fprintf(p->logf,"Client proof "); //foo_hex(p->logf,&cp[0],20); //fprintf(p->logf,"\n"); fwrite(&cp[0],1,20,f); fclose(f); //fprintf(p->logf,"Response text: "); xmpp_send_point(p->s,"",-1); f = base64_fwrap( // util_teeopen_w( util_memopen_w(&cq,&cql), // p->logf,TEE_F_CLOSE_A), BASE64_OP_E|BASE64_DIR_W|BASE64_CLOSE); fwrite(es_buf(&cfm),1,es_len(&cfm),f); fclose(f); //fprintf(p->logf,"\n"); xmpp_send_free(p->s,cq,cql); xmpp_send_point(p->s,"",-1); } static void stream_preauth_failure(XMPPPRIV *p, XELEM *e) { if (e->content) { warn(p->s,"Authentication failed:"); warn(p->s," %s",e->content); } else { warn(p->s,"Authentication failed (no content provided)"); } } static void stream_preauth_success(XMPPPRIV *p, XELEM *e) { SASL_STATE *ss; FILE *f; int l; int i; char *hex; if (! e->content) { warn(p->s,"NOTE: authorization has no content"); } else { f = base64_fwrap(util_memopen_r(e->content,strlen(e->content)),BASE64_OP_D|BASE64_DIR_R|BASE64_CLOSE); ss = p->sasl; es_clear(&ss->sver); ss->sverf = base64_fwrap(es_fopen_w(&ss->sver),BASE64_OP_D|BASE64_DIR_W|BASE64_CLOSE); parse_sasl_data(f,(void *)ss, 'v', &sasl_sver, '\0'); fclose(f); fclose(ss->sverf); ss->sverf = 0; l = es_len(&ss->sver); if (l == 0) { warn(p->s,"NOTE: SCRAM success response contains no verifier"); } else { hmac_sha1(&ss->SaltedPassword[0],20,"Server Key",10,&ss->ServerKey[0]); hmac_sha1(&ss->ServerKey[0],20,ss->AuthMessage,ss->AuthMessage_len,&ss->ServerSignature[0]); if ((l != 20) || bcmp(es_buf(&ss->sver),&ss->ServerSignature[0],20)) { warn(p->s,"NOTE: SCRAM success response verifier wrong:"); f = util_memopen_w(&hex,0); for (i=0;i<20;i++) fprintf(f,"%02x",ss->ServerSignature[i]); fclose(f); warn(p->s," Expected: %s",hex); free(hex); f = util_memopen_w(&hex,0); for (i=0;isver))[i]); fclose(f); warn(p->s," Observed: %s",hex); free(hex); } } } send_xmpp_stream_initial(p->s); yxml_init(&p->yx,p->yxbuf,YXBUFSIZE); p->xerr = 0; p->flags |= XPF_AUTH; sasl_state_free(ss); p->sasl = 0; } /* * This code promises it will never return a string that needs escaping * (eg, with ', &, etc). */ static WSTR nextid(XMPPPRIV *p) { int x; WSTR r; do <"incremented"> { for (x=0;xidlen;x++) { if (p->id[x] < 'z') { p->id[x] ++; break <"incremented">; } p->id[x] = 'a'; } p->id = realloc(p->id,p->idlen+1); p->id[p->idlen] = 'a'; p->idlen ++; } while (0); r.text = malloc(p->idlen); bcopy(p->id,r.text,p->idlen); r.len = p->idlen; return(r); } /* * Caller must set handler and priv in the returned PEND_ID. */ static PEND_ID *open_iq(XMPPPRIV *, const char *, const char *, ...) __attribute__((__format__(__printf__,3,4))); static PEND_ID *open_iq(XMPPPRIV *p, const char *type, const char *extrafmt, ...) { PEND_ID *pi; char *t; va_list ap; if (extrafmt) { va_start(ap,extrafmt); vasprintf(&t,extrafmt,ap); va_end(ap); } else { t = 0; } pi = malloc(sizeof(PEND_ID)); pi->xp = p; pi->id = nextid(p); pi->handler = 0; pi->priv = 0; pi->link = p->pend_iq; p->pend_iq = pi; xmpp_send_printf(p->s,"",pi->id.len,pi->id.text,type,(t&&*t)?" ":"",t?t:""); free(t); return(pi); } static void close_iq(PEND_ID *pi) { xmpp_send_point(pi->xp->s,"",-1); } static void pend_handle_disco_info(PEND_ID *pi, XELEM *e) { XMPPPRIV *p; XATTR *a; p = pi->xp; if ((e->ns != p->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(p->s,"Got disco#info ID back in unexpected element <%s>",e->name); return; } do <"isresult"> { for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (! strcmp(a->value,"result")) break <"isresult">; warn(p->s,"disco#info response has unexpected type `%s'",a->value); return; } } warn(p->s,"disco#info response has no type"); return; } while (0); do <"gotquery"> { for (e=e->children;e;e=e->sibling) if ((e->ns == p->ns_disco_info) && !strcmp(e->name,"query")) break <"gotquery">; warn(p->s,"disco#info response has no child"); return; } while (0); for (e=e->children;e;e=e->sibling) { if ((e->ns == p->ns_disco_info) && !strcmp(e->name,"identity")) { // for the moment, ignore } else if ((e->ns == p->ns_disco_info) && !strcmp(e->name,"feature")) { // for the moment, ignore } else if ((e->ns == p->ns_jxdata) && !strcmp(e->name,"x")) { // for the moment, ignore } else { warn(p->s,"disco#info response has unrecognized child <%s> ns %.*s",e->name,e->ns->ns.len,e->ns->ns.text); } } } static void pend_handle_disco_items(PEND_ID *pi, XELEM *e) { XMPPPRIV *p; XATTR *a; p = pi->xp; if ((e->ns != p->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(p->s,"Got disco#items ID back in unexpected element <%s>",e->name); return; } do <"isresult"> { for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (! strcmp(a->value,"result")) break <"isresult">; warn(p->s,"disco#items response has unexpected type `%s'",a->value); return; } } warn(p->s,"disco#items response has no type"); return; } while (0); do <"gotquery"> { for (e=e->children;e;e=e->sibling) if ((e->ns == p->ns_disco_items) && !strcmp(e->name,"query")) break <"gotquery">; warn(p->s,"disco#items response has no child"); return; } while (0); for (e=e->children;e;e=e->sibling) { if ((e->ns == p->ns_disco_items) && !strcmp(e->name,"item")) { // for the moment, ignore } else { warn(p->s,"disco#items response has unrecognized child <%s> ns %.*s",e->name,e->ns->ns.len,e->ns->ns.text); } } } static void pend_handle_bind(PEND_ID *pi, XELEM *e) { XMPPPRIV *p; XATTR *a; p = pi->xp; if ((e->ns != p->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(p->s,"Got ID back in unexpected element <%s>",e->name); return; } do <"isresult"> { for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (! strcmp(a->value,"result")) break <"isresult">; warn(p->s," response has unexpected type `%s'",a->value); return; } } warn(p->s," response has no type"); return; } while (0); do <"gotbind"> { for (e=e->children;e;e=e->sibling) if ((e->ns == p->ns_xbind) && !strcmp(e->name,"bind")) break <"gotbind">; warn(p->s," response has no child"); return; } while (0); do <"gotjid"> { for (e=e->children;e;e=e->sibling) if ((e->ns == p->ns_xbind) && !strcmp(e->name,"jid")) break <"gotjid">; warn(p->s," response 's has no child"); return; } while (0); if (! e->content) { warn(p->s," response 's 's has no content"); return; } if (p->jidtext) abort(); p->jidtext = strdup(e->content); warn(p->s," returned JID %s",p->jidtext); p->jid = crack_jid(p->jidtext,strlen(p->jidtext)); if (! p->jid.lp) { warn(p->s," JID contains no local part"); } if (! p->jid.rp) { warn(p->s," JID contains no resource part"); } if (!p->jid.lp || !p->jid.rp) return; pi = open_iq(p,"get","to='%.*s'",p->jid.dl,p->jid.dp); xmpp_send_point(p->s,"",-1); pi->handler = &pend_handle_disco_info; pi->priv = p; close_iq(pi); pi = open_iq(p,"get","to='%.*s'",p->jid.dl,p->jid.dp); xmpp_send_point(p->s,"",-1); pi->handler = &pend_handle_disco_items; pi->priv = p; close_iq(pi); xmpp_send_point(p->s,"Client development1",-1); } static void stream_postauth_features(XMPPPRIV *p, XELEM *e) { XELEM *f; XATTR *a; p->flags &= ~XPF__ALL_FEATURES; for (f=e->children;f;f=f->sibling) { if (! strcmp(f->name,"bind")) { for (a=f->attrs;a;a=a->link) { if (!a->ns && !strcmp(a->name,"xmlns") && !strcmp(a->value,"urn:ietf:params:xml:ns:xmpp-bind")) { p->flags |= XPF_FEATURE_BIND; } } } } if (! XPF_FEATURE_BIND) { warn(p->s,"NOTE: server didn't advertise support for resource binding"); } else { PEND_ID *pi; pi = open_iq(p,"set",0); xmpp_send_point(p->s,"",-1); pi->handler = &pend_handle_bind; pi->priv = p; close_iq(pi); } } /* * Instead of looping over XATTRs for each arg, would it maybe be * better to loop over the arglist for each XATTR? I'm not sure which * is better. I suspect it matters little in practice. */ static void collect_attributes(XMPPPRIV *p, XELEM *e, ...) { va_list ap; const char *name; const char *val; const char **valp; XATTR *a; int warned; va_start(ap,e); while (1) { name = va_arg(ap,const char *); if (! name) break; valp = va_arg(ap,const char **); warned = 0; val = 0; for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,name)) { if (val) { if (! warned) { warn(p->s,"NOTE: <%s> has multiple %s= attributes",e->name,name); warned = 1; } } val = a->value; } } *valp = val; } va_end(ap); } /* * Per 8.2.3: * id is required * type must be one of get, set, result, error (or return bad-request - 8.3.3.1) * get/set MUST contain exactly one child * result MUST include zero or one children * error MAY include the get/set child, MUST include error child (8.3) * error returns stanza of same kind (iq in this case), with * type="error", "typically" (whatever that means) swaps from= and to= * addresses, copies id= if any, contains child */ static void stream_postauth_iq(XMPPPRIV *p, XELEM *e) { PEND_ID *pi; PEND_ID **pip; int vl; const char *type; const char *from; const char *to; const char *id; JID fjid; JID tjid; collect_attributes(p,e,"type",&type,"from",&from,"to",&to,"id",&id,(const char *)0); if (from) fjid = crack_jid(from,strlen(from)); if (to) tjid = crack_jid(to,strlen(to)); if (! type) { warn(p->s,"NOTE: stanza has no type= attribute!"); // would check that type isn't error, but we have no type! xmpp_send_printf(p->s,"s," to='%.*s'",fjid.fl,fjid.fp); if (to && !bad_jid(&tjid)) xmpp_send_printf(p->s," from='%.*s'",tjid.fl,tjid.fp); xmpp_send_point(p->s,">",-1); return; } if (! id) { warn(p->s,"NOTE: stanza has no id= attribute!"); if (strcmp(type,"result") && strcmp(type,"error")) { xmpp_send_point(p->s,"s," to='%.*s'",fjid.fl,fjid.fp); if (to && !bad_jid(&tjid)) xmpp_send_printf(p->s," from='%.*s'",tjid.fl,tjid.fp); xmpp_send_point(p->s,">",-1); } return; } if (! strcmp(type,"result")) { vl = strlen(id); pip = &p->pend_iq; while ((pi = *pip)) { if ((vl == pi->id.len) && !bcmp(id,pi->id.text,vl)) { *pip = pi->link; (*pi->handler)(pi,e); free(pi->id.text); free(pi); return; } else { pip = &pi->link; } } warn(p->s,"NOTE: server sent result with unrecognized id `%s'",id); return; } if (! strcmp(type,"error")) { warn(p->s,"NOTE: received an error stanza"); return; } if (strcmp(type,"get") && strcmp(type,"set")) { warn(p->s,"NOTE: server sent stanza with unrecognized type %s",type); xmpp_send_printf(p->s,"s," to='%.*s'",fjid.fl,fjid.fp); if (to && !bad_jid(&tjid)) xmpp_send_printf(p->s," from='%.*s'",tjid.fl,tjid.fp); xmpp_send_point(p->s,">",-1); return; } if (!e->children || e->children->sibling) { warn(p->s,"NOTE: server sent %s stanza with %s child elements",type,e->children?"multiple":"no"); xmpp_send_printf(p->s,"s," to='%.*s'",fjid.fl,fjid.fp); if (to && !bad_jid(&tjid)) xmpp_send_printf(p->s," from='%.*s'",tjid.fl,tjid.fp); xmpp_send_point(p->s,">",-1); return; } if (from && bad_jid(&fjid)) { warn(p->s,"NOTE: server sent %s bearing bad from= JID %.*s",type,fjid.fl,fjid.fp); xmpp_send_printf(p->s,"s," from='%.*s'",tjid.fl,tjid.fp); xmpp_send_point(p->s,">",-1); return; } if (to && bad_jid(&tjid)) { warn(p->s,"NOTE: server sent %s bearing bad to= JID %.*s",type,tjid.fl,tjid.fp); xmpp_send_printf(p->s,"s," to='%.*s'",fjid.fl,fjid.fp); xmpp_send_point(p->s,">",-1); return; } // XXX Implement some! warn(p->s,"NOTE: server sent %s for unimplemented element <%s/>",type,e->children->name); xmpp_send_printf(p->s,"s," to='%.*s'",fjid.fl,fjid.fp); if (to) xmpp_send_printf(p->s," from='%.*s'",tjid.fl,tjid.fp); xmpp_send_point(p->s,">",-1); } static void stream_postauth_message(XMPPPRIV *p, XELEM *e) { XATTR *a; JID fjid; JID tjid; int have_f; int have_t; MSGTYPE type; XELEM *b; have_f = 0; have_t = 0; type = MT__UNSPEC; for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"from")) { fjid = crack_jid(a->value,strlen(a->value)); have_f = 1; } if (! strcmp(a->name,"to")) { tjid = crack_jid(a->value,strlen(a->value)); have_t = 1; } if (! strcmp(a->name,"type")) { MSGTYPE mt; mt = MT__UNSPEC; switch (a->value[0]) { case 'c': if (! strcmp(a->value,"chat")) mt = MT_CHAT; break; case 'e': if (! strcmp(a->value,"error")) mt = MT_ERROR; break; case 'g': if (! strcmp(a->value,"groupchat")) mt = MT_GROUPCHAT; break; case 'h': if (! strcmp(a->value,"headline")) mt = MT_HEADLINE; break; case 'n': if (! strcmp(a->value,"normal")) mt = MT_NORMAL; break; } if (mt == MT__UNSPEC) { warn(p->s," has invalid type `%s'",a->value); } else if (type != MT__UNSPEC) { if (mt == type) { warn(p->s," has duplicate type"); } else { warn(p->s," has multiple conflicting types"); } } else { type = mt; } } } if (! have_t) { warn(p->s," has no to="); } else if ((tjid.dl != p->jid.dl) || strncasecmp(tjid.dp,p->jid.dp,tjid.dl)) { warn(p->s,"'s to= is on an unexpected server (%.*s, expecting %.*s)",tjid.dl,tjid.dp,p->jid.dl,p->jid.dp); } else if (! tjid.lp) { warn(p->s,"'s to= has no local part"); } else if ((tjid.ll != p->jid.ll) || bcmp(tjid.lp,p->jid.lp,tjid.ll)) { warn(p->s,"'s to= isn't us (%.*s, expecting %.*s)",tjid.ll,tjid.lp,p->jid.ll,p->jid.lp); } else if (tjid.rp && ((tjid.rl != p->jid.rl) || bcmp(tjid.rp,p->jid.rp,tjid.rl))) { warn(p->s,"'s to= is to a different resource of ours (%.*s, expecting %.*s)",tjid.rl,tjid.rp,p->jid.rl,p->jid.rp); } if (! have_f) { warn(p->s,"'s has no from="); } if (type == MT__UNSPEC) { // the warn() is to see how common this is // it is not an error condition per 6121 warn(p->s," has no type, using `normal'"); type = MT_NORMAL; } if (e->content) { warn(p->s," contains direct content"); // XXX do something with it? } for (b=e->children;b;b=b->sibling) { if ((b->ns == p->ns_jclient) && !strcmp(b->name,"body")) { if (! b->content) { warn(p->s,"'s has no content"); } else { char *t; if (have_f) { if (fjid.lp) { if (fjid.rp) { asprintf(&t,"%.*s/%.*s",fjid.ll,fjid.lp,fjid.rl,fjid.rp); } else { asprintf(&t,"%.*s",fjid.ll,fjid.lp); } } else { t = strdup("(no name)"); } } else { t = strdup("(no from)"); } ohl_append_private(p->s, have_f ? fjid.fp : "---", have_f ? fjid.fl : 3, t, strlen(t), b->content, strlen(b->content), 0); free(t); } } // XXX do something with and/or ? // XXX do something with other children? } } static void stream_postauth_presence(XMPPPRIV *p, XELEM *e) { XATTR *a; char *type; char *from; type = 0; from = 0; for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (type) { warn(p->s," has multiple type= attributes, using last"); } type = a->value; } else if (! strcmp(a->name,"from")) { if (from) { warn(p->s," has multiple from= attributes, using last"); } from = a->value; } } if (! from) { warn(p->s," has no from= attribute, ignoring"); return; } if (! type) { warn(p->s," from %s has no type= attribute, ignoring",from); return; } hlprintf_srv(p->s,"Presence from %s, type = %s",from,type); } static void stream_preauth_elem(XMPPPRIV *p, XELEM *e) { if (e->ns == p->ns_stream) { if (! strcmp(e->name,"features")) { stream_preauth_features(p,e); return; } } else if (e->ns == p->ns_xsasl) { if (! strcmp(e->name,"challenge")) { stream_preauth_challenge(p,e); return; } else if (! strcmp(e->name,"failure")) { stream_preauth_failure(p,e); return; } else if (! strcmp(e->name,"success")) { stream_preauth_success(p,e); return; } } if (e->ns) { warn(p->s,"unrecognized preauth element %s (namespace %.*s)",e->name,e->ns->ns.len,e->ns->ns.text); } else { warn(p->s,"unrecognized preauth element %s (no namespace)",e->name); } } static void stream_postauth_elem(XMPPPRIV *p, XELEM *e) { if (e->ns == p->ns_stream) { if (! strcmp(e->name,"features")) { stream_postauth_features(p,e); return; } } else if (e->ns == p->ns_jclient) { if (! strcmp(e->name,"iq")) { stream_postauth_iq(p,e); return; } else if (! strcmp(e->name,"message")) { stream_postauth_message(p,e); return; } else if (! strcmp(e->name,"presence")) { stream_postauth_presence(p,e); return; } } if (e->ns) { warn(p->s,"unrecognized postauth element %s (namespace %.*s)",e->name,e->ns->ns.len,e->ns->ns.text); } else { warn(p->s,"unrecognized postauth element %s (no namespace)",e->name); } } static void set_nsctx(XMPPPRIV *p, XELEM *e, const char *name, NAMESPACE *ns) { NSNAME *nn; (void)p; nn = malloc(sizeof(NSNAME)); nn->name = nt_to_wstr(name); nn->ns = ns; nn->link = e->nsctx; e->nsctx = nn; } static NAMESPACE *reserved_ns(XMPPPRIV *p, const char *name, int nl) { switch (nl) { case 3: if (! bcmp(name,"xml",3)) return(p->ns_xml); break; case 5: if (! bcmp(name,"xmlns",5)) return(p->ns_xmlns); break; } return(0); } static void set_namespace(XMPPPRIV *p, XELEM *e) { XATTR *a; NSNAME *nn; char *colon; int cl; XELEM *u; char *t; e->defns = 0; e->ns = 0; for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"xmlns")) { e->defns = find_namespace(p,a->value); } else if (! strncmp(a->name,"xmlns:",6)) { set_nsctx(p,e,a->name+6,find_namespace(p,a->value)); } } colon = index(e->name,':'); if (colon) { cl = colon - e->name; do <"nsdone"> { do <"havens"> { e->ns = reserved_ns(p,e->name,cl); if (e->ns) break <"havens">; for (u=e;u;u=u->parent) { for (nn=u->nsctx;nn;nn=nn->link) { if ((nn->name.len == cl) && !bcmp(nn->name.text,e->name,cl)) { e->ns = nn->ns; break <"havens">; } } } warn(p->s,"NOTE: element %s has unset namespace prefix",e->name); break <"nsdone">; } while (0); t = strdup(colon+1); free(e->name); e->name = t; } while (0); } else { for (u=e;u;u=u->parent) { if (u->defns) { e->ns = u->defns; break; } } } for (a=e->attrs;a;a=a->link) { colon = index(a->name,':'); if (colon) { cl = colon - a->name; do <"nsdone"> { do <"havens"> { a->ns = reserved_ns(p,a->name,cl); if (a->ns) break <"havens">; for (u=e;u;u=u->parent) { for (nn=u->nsctx;nn;nn=nn->link) { if ((nn->name.len == cl) && !bcmp(nn->name.text,a->name,cl)) { a->ns = nn->ns; break <"havens">; } } } warn(p->s,"NOTE: element %s attribute %s has unset namespace prefix",e->name,a->name); break <"nsdone">; } while (0); t = strdup(colon+1); free(a->name); a->name = t; } while (0); } } } static void elem_start(XMPPPRIV *p) { XELEM *e; XSTACK *s; s = p->xstack; e = s->elem; foo("text name %s",e->name); set_namespace(p,e); if (e->ns) { foo("name %s namespace %.*s",e->name,e->ns->ns.len,e->ns->ns.text); } else { foo("name %s no namespace",e->name); } } static void elem_end(XMPPPRIV *p) { XELEM *e; XSTACK *s; s = p->xstack; e = s->elem; p->xstack = s->link; e->content = es_len(&s->content) ? blk_to_nt(es_buf(&s->content),es_len(&s->content)) : 0; s->elem = 0; xstack_free(s); if (! p->xstack) { p->xerr = 1; warn(p->s,"stream element closed"); xelem_free(e); return; } if (! ((p->xstack->elem->ns == p->ns_stream) && !strcmp(p->xstack->elem->name,"stream"))) { *p->xstack->childtail = e; p->xstack->childtail = &e->sibling; return; } foo("elem_end %s, auth %s",e->name,(p->flags&XPF_AUTH)?"YES":"NO"); if (p->flags & XPF_AUTH) { stream_postauth_elem(p,e); } else { stream_preauth_elem(p,e); } xelem_free(e); } static void op_xmpp_input(SERVER *s, void *data, int len) { XMPPPRIV *p; yxml_ret_t pr; const char *dp; p = PRIV(s); if (p->xerr) return; dp = data; foo("XML input: %.*s",len,dp); for (;len>0;dp++,len--) { pr = yxml_parse(&p->yx,*dp); if (pr == YXML_OK) continue; if (p->elopen) { switch (pr) { case YXML_ATTRSTART: case YXML_ATTRVAL: case YXML_ATTREND: break; default: p->elopen = 0; foo("End of ELEMSTART"); elem_start(p); break; } } if ((p->chain != YXML_OK) && (pr != p->chain)) { switch (p->chain) { case YXML_CONTENT: foo("CONTENT %.*s",es_len(&p->chainval),es_buf(&p->chainval)); es_append_n(&p->xstack->content,es_buf(&p->chainval),es_len(&p->chainval)); break; case YXML_ATTRVAL: foo("ATTRVAL %.*s",es_len(&p->chainval),es_buf(&p->chainval)); if (p->xstack->elem->attrs->value) abort(); p->xstack->elem->attrs->value = blk_to_nt(es_buf(&p->chainval),es_len(&p->chainval)); break; case YXML_PICONTENT: foo("PICONTENT %.*s",es_len(&p->chainval),es_buf(&p->chainval)); break; default: abort(); break; } p->chain = YXML_OK; } switch (pr) { case YXML_EREF: p->xerr = 1; foo("XML Error: invalid &...; construct"); return; break; case YXML_ECLOSE: p->xerr = 1; foo("XML Error: closing tag wrong"); return; break; case YXML_ESTACK: p->xerr = 1; foo("XML Error: XML parser stack overflow"); return; break; case YXML_ESYN: p->xerr = 1; foo("XML Error: XML syntax error"); return; break; case YXML_ELEMSTART: { XSTACK *xs; foo("ELEMSTART %s",p->yx.elem); xs = malloc(sizeof(XSTACK)); xs->elem = malloc(sizeof(XELEM)); xs->elem->name = strdup(p->yx.elem); xs->elem->attrs = 0; xs->elem->content = 0; xs->elem->children = 0; xs->elem->ns = 0; xs->elem->defns = 0; xs->elem->nsctx = 0; xs->elem->sibling = 0; xs->elem->parent = p->xstack ? p->xstack->elem : 0; es_init(&xs->content); xs->childtail = &xs->elem->children; xs->link = p->xstack; p->xstack = xs; } p->elopen = 1; break; case YXML_ATTRSTART: { XATTR *xa; foo("ATTRSTART %s",p->yx.attr); xa = malloc(sizeof(XATTR)); xa->name = strdup(p->yx.attr); xa->ns = 0; xa->value = 0; xa->link = p->xstack->elem->attrs; p->xstack->elem->attrs = xa; } break; case YXML_ATTRVAL: case YXML_CONTENT: case YXML_PICONTENT: if (p->chain == YXML_OK) { p->chain = pr; es_clear(&p->chainval); } es_append_n(&p->chainval,&p->yx.data[0],strlen(&p->yx.data[0])); break; case YXML_ATTREND: foo("ATTREND"); break; case YXML_ELEMEND: foo("ELEMEND"); elem_end(p); break; case YXML_PISTART: foo("PISTART %s",p->yx.pi); break; case YXML_PIEND: foo("PIEND"); break; case YXML_EEOF: foo("EEOF"); break; default: if (pr < 0) { p->xerr = 1; foo("yxml_parse returned unexpected error %d",(int)pr); } else { foo("yxml_parse returned unexpected token %d",(int)pr); } break; } } } static void op_xmpp_send(SERVER *s, const char *dest, int destlen, const char *body, int bodylen, unsigned int flags) { XMPPPRIV *p; const char *t; JID jid; const char *err; char *j; int jl; if (! xmppable_text(dest,destlen)) { hlprintf_srv(s,"Destination `%.*s' not acceptable - use `/xmpptext' for more",destlen,dest); return; } if (! xmppable_text(body,bodylen)) { hlprintf_srv(s,"Body text not acceptable - use `/xmpptext' for more"); return; } if (flags & SENDF_ACTION) { hlprintf_srv(s,"Actions not supported over XMPP"); return; } p = PRIV(s); j = 0; if (memchr(dest,'@',destlen)) { jid = crack_jid(dest,destlen); err = bad_jid(&jid); if (err) { hlprintf_srv(p->s,"Invalid destination %.*s (%s)\n",destlen,dest,err); return; } } else { t = memchr(dest,'/',destlen); if (t) { jl = asprintf(&j,"%.*s@%.*s%.*s", (int)(t-dest), dest, p->jid.dl, p->jid.dp, (int)(destlen-(t-dest)), t); jid = crack_jid(j,jl); } else { jl = asprintf(&j,"%.*s@%.*s",destlen,dest,p->jid.dl,p->jid.dp); jid = crack_jid(j,jl); } err = bad_jid(&jid); if (err) { hlprintf_srv(p->s,"Invalid destination %s (%s)\n",j,err); free(j); return; } } xmpp_send_printf(s,"",p->jidtext,jid.fl,jid.fp); xmpp_send_textesc(s,body,bodylen); xmpp_send_point(s,"",-1); free(j); } static int op_xmpp_channeldest(SERVER *s, const char *dest, int destlen) { (void)s; (void)dest; (void)destlen; return(1); } static int op_xmpp_okchan(SERVER *s, const char *name, int namelen) { (void)s; (void)name; (void)namelen; return(1); } static void op_xmpp_sendjoin(SERVER *s, const char *name, int namelen) { JID rjid; JID njid; const char *err; rjid = crack_jid(name,namelen); err = bad_jid(&rjid); if (err) { hlprintf_srv(s,"Can't join %.*s: %s",namelen,name,err); return; } if (rjid.rp) { xmpp_send_printf(s,"1",namelen,name); } else { njid = crack_jid(s->nick.text,s->nick.len); xmpp_send_printf(s,"1",namelen,name,njid.ll,njid.lp); } } static void op_xmpp_sendpart(SERVER *s, const char *name, int namelen) { (void)s; (void)name; (void)namelen; } static void slash_xmpptext(SERVER *s) { hlprintf_srv(s, "XMPP is misdesigned, mandating that all text be in UTF-8. Because, " "like most Unix variants, input to this program is octets rather than " "characters, we cannot recode to UTF-8 even in principle. Thus, their " "attempt to ram their i18n religion down everyone's throats ends up " "actually crippling i18n, not only making it effectively impossible to " "internationalize this program, but making it impossible for users to " "agree on an encoding and use it (as works just fine in, for example, " "IRC). So, ugly as it is, we restrict all text to ASCII. (This is " "theoretically broken, in that in theory we don't even know that the " "user is using superset of ASCII. But without assuming something of " "the sort we can't operate at all.)"); } static void pend_handle_disco(PEND_ID *pi, XELEM *e) { XMPPPRIV *p; XATTR *a; XELEM *s; XELEM *qe; const char *jid; const char *name; p = pi->xp; if ((e->ns != p->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(p->s,"Got ID back in unexpected element <%s>",e->name); return; } do <"isresult"> { for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (! strcmp(a->value,"result")) break <"isresult">; warn(p->s," response has unexpected type `%s'",a->value); return; } } warn(p->s," response has no type"); return; } while (0); qe = 0; for (s=e->children;s;s=s->sibling) { if ((s->ns == p->ns_disco_items) && !strcmp(s->name,"query")) { if (qe) { warn(p->s," response has multiple children"); } qe = s; } } if (! qe) { warn(p->s," response has no children"); return; } while (0); for (s=qe->children;s;s=s->sibling) { if ((s->ns == p->ns_disco_items) && !strcmp(s->name,"item")) { jid = 0; name = 0; for (a=s->attrs;a;a=a->link) { if (! strcmp(a->name,"jid")) { if (jid) warn(p->s," has multiple jid= attributes"); jid = a->value; } else if (! strcmp(a->name,"name")) { if (name) warn(p->s," has multiple name= attributes"); name = a->value; } } if (! jid) { warn(p->s,"ignoring with no jid= attribute"); } else if (name) { hlprintf_srv(p->s,"%s - %s",jid,name); } else { hlprintf_srv(p->s,"%s (no description)",jid); } } } } static void slash_disco(SERVER *s, const char *cb, int cblen) { XMPPPRIV *p; PEND_ID *pi; int i; int t0; int tl; const char *sharp; int sharplen; p = s->priv; for (i=0;(i 0) && (cb[t0] == '#')) { sharp = &cb[t0]; sharplen = tl; } else { hlprintf_srv(s,"No #tag specified (use #info or #items)"); return; } if (i < cblen) { pi = open_iq(p,"get","to='%.*s'",cblen-i,cb+i); } else { pi = open_iq(p,"get",0); } xmpp_send_printf(s,"",sharplen,sharp); pi->handler = &pend_handle_disco; pi->priv = p; close_iq(pi); } static void query_next_room(ROOMS_STATE *); // forward static void pend_handle_rooms_2(PEND_ID *pi, XELEM *e) { XMPPPRIV *p; XATTR *a; XELEM *s; XELEM *qe; const char *jidstr; JID jid; const char *name; ROOMS_STATE *rs; ROOM_QUERY *rq; const char *err; p = pi->xp; rs = pi->priv; rq = rs->queue; rs->queue = rq->link; if ((e->ns != p->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(p->s,"Got rooms ID back in unexpected element <%s>",e->name); return; } do <"isresult"> { for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (! strcmp(a->value,"result")) break <"isresult">; warn(p->s,"Rooms response has unexpected type `%s'",a->value); return; } } warn(p->s,"Rooms response has no type"); return; } while (0); qe = 0; for (s=e->children;s;s=s->sibling) { if ((s->ns == p->ns_disco_items) && !strcmp(s->name,"query")) { if (qe) { warn(p->s,"Rooms response has multiple children (using last)"); } qe = s; } } if (! qe) { warn(p->s,"Rooms response has no children"); return; } while (0); for (s=qe->children;s;s=s->sibling) { if ((s->ns == p->ns_disco_items) && !strcmp(s->name,"item")) { jidstr = 0; name = 0; for (a=s->attrs;a;a=a->link) { if (! strcmp(a->name,"jid")) { if (jidstr) warn(p->s," has multiple jid= attributes (using last)"); jidstr = a->value; } else if (! strcmp(a->name,"name")) { if (name) warn(p->s," has multiple name= attributes (using last)"); name = a->value; } } if (! jidstr) { warn(p->s,"ignoring with no jid= attribute"); } else { jid = crack_jid(jidstr,strlen(jidstr)); err = bad_jid(&jid); if (err) { warn(p->s,"...ignoring %s - bad JID (%s)",jidstr,err); } else if (! jid.lp) { warn(p->s,"...ignoring %s - no local-part",jidstr); } else { if (name) { warn(p->s,"Room: %s - %s",jidstr,name); } else { warn(p->s,"%s (no description)",jidstr); } // XXX save it somewhere! } } } } query_next_room(rs); } static void query_next_room(ROOMS_STATE *rs) { XMPPPRIV *p; PEND_ID *pi; ROOM_QUERY *rq; p = rs->p; rq = rs->queue; if (rq) { pi = open_iq(p,"get","to='%s'",rq->name); xmpp_send_point(p->s,"",-1); pi->handler = &pend_handle_rooms_2; pi->priv = rs; rs->pi = pi; close_iq(pi); } else { free(rs); } } static void pend_handle_rooms_1(PEND_ID *pi, XELEM *e) { XMPPPRIV *p; XATTR *a; XELEM *s; XELEM *qe; const char *jid; const char *name; ROOMS_STATE *rs; ROOM_QUERY *rq; p = pi->xp; rs = pi->priv; if ((e->ns != p->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(p->s,"Got rooms ID back in unexpected element <%s>",e->name); return; } do <"isresult"> { for (a=e->attrs;a;a=a->link) { if (! strcmp(a->name,"type")) { if (! strcmp(a->value,"result")) break <"isresult">; warn(p->s,"Rooms response has unexpected type `%s'",a->value); return; } } warn(p->s,"Rooms response has no type"); return; } while (0); qe = 0; for (s=e->children;s;s=s->sibling) { if ((s->ns == p->ns_disco_items) && !strcmp(s->name,"query")) { if (qe) { warn(p->s,"Rooms response has multiple children (using last)"); } qe = s; } } if (! qe) { warn(p->s,"Rooms response has no children"); return; } while (0); for (s=qe->children;s;s=s->sibling) { if ((s->ns == p->ns_disco_items) && !strcmp(s->name,"item")) { jid = 0; name = 0; for (a=s->attrs;a;a=a->link) { if (! strcmp(a->name,"jid")) { if (jid) warn(p->s," has multiple jid= attributes (using last)"); jid = a->value; } else if (! strcmp(a->name,"name")) { if (name) warn(p->s," has multiple name= attributes (using last)"); name = a->value; } } if (! jid) { warn(p->s,"ignoring with no jid= attribute"); } else { rq = malloc(sizeof(ROOM_QUERY)); rq->name = strdup(jid); rq->link = rs->queue; rs->queue = rq; } } } query_next_room(rs); } static void slash_rooms(SERVER *s) { XMPPPRIV *p; PEND_ID *pi; ROOMS_STATE *rs; p = s->priv; rs = malloc(sizeof(ROOMS_STATE)); pi = open_iq(p,"get","to='%.*s'",p->jid.dl,p->jid.dp); xmpp_send_point(s,"",-1); pi->handler = &pend_handle_rooms_1; pi->priv = rs; rs->p = p; rs->pi = pi; rs->queue = 0; close_iq(pi); } static int op_xmpp_slashcmd(SERVER *s, const char *cmd, int len, int cx, int cl, int bx) { switch (cl) { case 5: if (! bcmp(cmd+cx,"disco",5)) { slash_disco(s,cmd+bx,len-bx); return(1); } else if (! bcmp(cmd+cx,"rooms",5)) { slash_rooms(s); return(1); } break; case 8: if (! bcmp(cmd+cx,"xmpptext",8)) { slash_xmpptext(s); return(1); } break; } return(0); } static int op_xmpp_oknick(SERVER *s, const char *cn, int len) { (void)s; for (;len>0;cn++,len--) { if ((*cn < 32) || (*cn > 126)) return(0); switch (*cn) { case '"': case '&': case '\'': case '/': case ':': case '<': case '>': case '@': return(0); break; } } return(1); } static void op_xmpp_setnick(SERVER *s, const char *nick, int nicklen) { (void)s; (void)nick; (void)nicklen; } static void op_xmpp_sendaway(SERVER *s, const char *body, int bodylen) { (void)s; (void)body; (void)bodylen; } static void pend_handle_ping(PEND_ID *pi, XELEM *e) { if ((e->ns != pi->xp->ns_jclient) || strcmp(e->name,"iq")) { // got ID back in unexpected element? warn(pi->xp->s,"Got ID back in unexpected element <%s>",e->name); return; } pi->xp->s->lastpong = ts_now(); } static void op_xmpp_sendping(SERVER *s, unsigned long long int seq) { XMPPPRIV *p; PEND_ID *pi; (void)seq; p = s->priv; pi = open_iq(p,"get","from='%s'",p->jidtext); xmpp_send_point(s,"",-1); pi->handler = &pend_handle_ping; pi->priv = p; close_iq(pi); } static const char *op_xmpp_defaultport(void) { return("5222"); } const PROTOOPS ops_xmpp = PROTOOPS_INIT(xmpp); /* XEP notes: 0030 disco 0045 MUC (old?) 0054 vcard format 0068 data forms 0077 in-band registration 0106 escaping extension 0115 feature broadcast 0165 anti-JID-mimicing measures 0203 delay 0237 rosterver (obsolete; folded into 6121) 0245 /me 0249 MUC directed invitation 0280 message carbons 0313 message archive management (urn:xmpp:mam:2) 0363 file upload 0369 MIX, replacement for MUC For public IM networks: &xep0077; (should be supported, but not enabled in default server configurations) and &xep0157; File uploads should be indicated using &xep0066;, optionally also using &xep0385; &xep0392; for cross-client consistency of user names &xep0393; for simple styling of plaintext messages that is loosely compatible with legacy IM networks &xep0433; to improve the discovery of public rooms hosted on a domain &xep0424; and &xep0425; for managing misbehavior in public rooms 0443 compliance suite
  • Client connection optimizations: &xep0386; and &xep0409;, maybe also &xep0397;
  • Improved on-boarding of new users:
    • &xep0401; to create account invitations
    • &xep0379; for contact invitations
    • Pre-Authenticated In-Band Registration (XEP-xxxx) to register accounts based on an invitation
  • &xep0333;
  • &xep0369;
  • End-to-End Encryption (E2EE): &xep0380; for tagging encrypted messages, &xep0420; to protect all payloads; and also one or multiple of the following for actual encryption:
    • &xep0384; and &xep0396;
    • &xep0374;
  • &xep0402; to phase out &xep0048;, &xep0049;, and &xep0411;
  • &xep0225; to phase out &xep0114;
  • &xep0390; to phase out &xep0115;
  • */