ref: 38fbed97371f4f970a542945883e7d68762a2184
dir: /xmpp.c/
#include <u.h> #include <libc.h> #include <auth.h> #include <bio.h> #include <thread.h> #include "xml.h" #include "xmpp.h" /* commands are at xmpp.c:/^handle */ char *enttypes[] = { [Emuc] "groupchat", [Emucent] "chat", [Erost] "chat", }; static int pfd; static Biobuf pb; static char lastid[32]; int debug, nopresence, nohistory, plainallow; Biobuf kbin; char *server, *mydomain, *myjid, *mynick, *myresource; static QLock prlock; static void inerror(Xelem *x) { Xattr *from, *type; Xelem *err; from = xmlgetattr(x->a, "from"); err = xmlget(x->ch, "error"); type = err == nil ? nil : xmlgetattr(err->a, "type"); print("[%s] (%s, error) %s %s", strtime(), (from == nil) ? mydomain : from->v, x->n, type == nil ? "" : type->v); err = err == nil ? nil : err->ch; if(err != nil) print(": %s", (err->v == nil) ? err->n : err->v); print("\n"); } static void inmsg(Xelem *x) { Xattr *type, *from, *stamp, *to; Xelem *body, *delay, *subj; char *s, *nick, *bodyv, tmp[64]; Target *t, *room; int i; type = xmlgetattr(x->a, "type"); from = xmlgetattr(x->a, "from"); body = xmlget(x->ch, "body"); subj = xmlget(x->ch, "subject"); /* ignore "composing..." messages */ if(body == nil && subj == nil) return; to = xmlgetattr(x->a, "to"); if(to != nil && strncmp(to->v, myjid, strlen(myjid)) != 0) return; if((delay = xmlget(x->ch, "delay")) == nil) delay = xmlget(x->ch, "x"); if((stamp = delay ? xmlgetattr(delay->a, "stamp") : nil) == nil) stamp = xmlgetattr(x->a, "ts"); bodyv = (body == nil) ? nil : ((body->v == nil) ? "" : body->v); /* * there is no difference between mucpriv and raw jid * try to find the target */ t = room = nil; for(i = 0; i < numtargets; i++, t = nil){ t = targets[i]; if(t->type == Emuc && strncmp(t->jid, from->v, strlen(t->jid)) == 0) room = t; else if((t->type == Emucent && strcmp(t->jid, from->v) == 0) || (t->type == Erost && strncmp(t->jid, from->v, strlen(t->jid)) == 0)){ break; } } if(subj != nil && room != nil){ free(room->muc.subj); room->muc.subj = strdup((subj->v == nil) ? "" : subj->v); return; } if(bodyv == nil) return; print("[%s] ", (stamp != nil) ? strstamp(stamp->v) : strtime()); if(t == nil && room == nil) nick = from->v; else if(t != nil && t->type == Erost){ snprint(tmp, sizeof(tmp), "%t", t); nick = tmp; }else{ /* extract nick and muc */ if(nick = strchr(from->v, '/')) nick++; if(s = strchr(from->v, '@')) *s = 0; print("(%s", from->v); if(type != nil && strcmp(type->v, enttypes[Emucent]) == 0) print(", private) "); else print(") "); } if(nick == nil) print("%s\n", bodyv); else if(strncmp(bodyv, "/me ", 4) == 0) print("→ %s %s\n", nick, bodyv+4); else print("%s → %s\n", nick, bodyv); } static int iniq(Xelem *x, int fd) { Xelem *e; Xattr *a, *from, *id; int isget, isset; a = xmlgetattr(x->a, "type"); if(a == nil || x->ch == nil) return 0; id = xmlgetattr(x->a, "id"); if(strcmp(a->v, "result") == 0){ if(x->ch != nil && (e = xmlget(x->ch->ch, "storage")) != nil){ /* autojoin bookmarked MUCs http://xmpp.org/extensions/xep-0048.html */ a = xmlgetattr(e->a, "xmlns"); if(strcmp(a->v, "storage:bookmarks") != 0) return 0; return mucbookmarks(e, fd); } if(x->ch != nil && x->ch->ch != nil && strcmp(x->ch->n, "bind") == 0){ if(strcmp(x->ch->ch->n, "jid") == 0){ free(myjid); myjid = strdup(x->ch->ch->v); } } if(id != nil && strcmp(id->v, "afflist") == 0){ int width, len, num; Xattr *aff, *jid; for(e = x->ch->ch, width = 0; e != nil; e = e->next){ if((jid = xmlgetattr(e->a, "jid")) != nil && (len = strlen(jid->v)) > width) width = len; } for(e = x->ch->ch, num = 0; e != nil; e = e->next, num++){ if((jid = xmlgetattr(e->a, "jid")) != nil && (aff = xmlgetattr(e->a, "affiliation")) != nil) print(" %*s %-8s\n", -width, jid->v, aff->v); } print("%d jid(s)\n", num); return 0; }else if(id != nil && strcmp(id->v, "gimme0") == 0){ /* bookmarks http://xmpp.org/extensions/xep-0048.html */ if(fprint(pfd, "<iq type='get' from='%Ӽ' id='gimme1'>" "<query xmlns='jabber:iq:private'>" "<storage xmlns='storage:bookmarks'/>" "</query></iq>", myjid) < 0) return -1; /* ask for roster */ if(fprint(pfd, "<iq type='get' from='%Ӽ' id='gimme2'>" "<query xmlns='jabber:iq:roster'/></iq>", myjid) < 0) return -1; } } /* incoming queries */ isget = strcmp(a->v, "get") == 0; isset = strcmp(a->v, "set") == 0; if(!isget && !isset && strcmp(a->v, "result") != 0) return 0; from = xmlgetattr(x->a, "from"); if(isget && (from == nil || id == nil)) return 0; if(e = xmlget(x->ch, "query")){ a = xmlgetattr(e->a, "xmlns"); if(a != nil && isget &&strcmp(a->v, "jabber:iq:version") == 0){ /* software version http://xmpp.org/extensions/xep-0092.html */ return fprint(fd, "<iq type='result' to='%Ӽ' id='%Ӽ'>" "<query xmlns='jabber:iq:version'>" "<name>xmpp</name>" "<version>9front edition</version>" "<os>Plan 9</os>" "</query></iq>", from->v, id->v); } if(a != nil && isget && strcmp(a->v, "http://jabber.org/protocol/disco#info") == 0){ /* service discovery http://xmpp.org/extensions/xep-0030.html */ return fprint(fd, "<iq type='result' to='%Ӽ' id='%Ӽ'>" "<query xmlns='http://jabber.org/protocol/disco#info'>" "<feature var='http://jabber.org/protocol/disco#info'/>" "<feature var='jabber:iq:version'/>" "<feature var='urn:xmpp:time'/>" "<feature var='urn:xmpp:ping'/>" "<feature var='http://jabber.org/protocol/muc'/>" "</query></iq>", from->v, id->v); } if(a != nil && !isget && strcmp(a->v, "jabber:iq:roster") == 0) return rostupdate(x, fd); }else if(isget && (e = xmlget(x->ch, "time")) != nil){ a = xmlgetattr(e->a, "xmlns"); if(a != nil && strcmp(a->v, "urn:xmpp:time") == 0){ /* entity time http://xmpp.org/extensions/xep-0202.html */ char *utc, *tzo; utc = strenttime(&tzo); return fprint(fd, "<iq type='result' to='%Ӽ' id='%Ӽ'>" "<time xmlns='urn:xmpp:time'>" "<utc>%Ӽ</utc><tzo>%Ӽ</tzo>" "</time></iq>", from->v, id->v, utc, tzo); } }else if(isget && (e = xmlget(x->ch, "ping")) != nil){ a = xmlgetattr(e->a, "xmlns"); if(a != nil && strcmp(a->v, "urn:xmpp:ping") == 0){ /* ping http://xmpp.org/extensions/xep-0199.html */ return fprint(fd, "<iq type='result' to='%Ӽ' id='%Ӽ'/>", from->v, id->v); } } if(isget) return fprint(fd, "<iq type='error' to='%Ӽ' id='%Ӽ'>" "<error type='cancel'>" "<service-unavailable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>" "</error></iq>", from->v, id->v); return 0; } static void inpresence(Xelem *x, int fd) { int i, found; Target *t; Xattr *from, *type; from = xmlgetattr(x->a, "from"); type = xmlgetattr(x->a, "type"); if(type != nil && rostsubscr(from->v, type->v, fd)) return; found = 0; for(i = 0; i < numtargets; i++){ t = targets[i]; if(t->type == Erost && strncmp(t->jid, from->v, strlen(t->jid)) == 0){ rostpresence(x, t); found = 1; break; } if(t->type == Emucent && strcmp(t->jid, from->v) == 0){ mucpresence(x, t->mucent.room, from); found = 1; } if(t->type == Emuc && strncmp(t->jid, from->v, strlen(t->jid)) == 0){ mucpresence(x, t, from); found = 1; } } if(!found && debug > 0) fprint(2, "presence from unknown target: %q\n", from->v); } static void reader(void *pd) { Xelem *x; Xattr *type, *id; int err; USED(pd); threadsetname("reader"); fprint(pfd, "<iq type='get' from='%Ӽ' to='%Ӽ' id='gimme0'>" "<query xmlns='http://jabber.org/protocol/disco#info'/>" "</iq>", myjid, server); for(err = 0; err == 0;){ if((x = xmlread(&pb, 0, &err)) == nil) continue; qlock(&prlock); if((id = xmlgetattr(x->a, "id")) != nil && strcmp(id->v, lastid) == 0){ xmlprint(x, 2); lastid[0] = 0; }else if(debug > 1){ if(strcmp(x->n, "presence") != 0 || debug > 2) xmlprint(x, 2); } type = xmlgetattr(x->a, "type"); if(type != nil && strcmp(type->v, "error") == 0) inerror(x); else if(strcmp(x->n, "message") == 0) inmsg(x); else if(strcmp(x->n, "presence") == 0) inpresence(x, pfd); else if(strcmp(x->n, "iq") == 0) iniq(x, pfd); xmlfree(x); qunlock(&prlock); } fprint(2, "%r\n"); threadexitsall(nil); } static int cmdmsg(int fd, int, char **) { char *s; int res; if(curr < 0) return 0; s = readlines(); res = fprint(fd, "<message to='%Ӽ' type='%Ӽ'><body>%Ӽ</body></message>", targets[curr]->jid, enttypes[targets[curr]->type], s); free(s); return res; } static int handle(int fd, char *s) { typedef int (*cmdf)(int, int, char **); char *ps, *pe, *argv[3]; static cmdf cmds[256] = { ['a'] cmdaff, /* muc.c:/^cmdaff */ ['b'] cmdbookmark, /* muc.c:/^cmdbookmark */ ['j'] cmdjoin, /* muc.c:/^cmdjoin */ ['m'] cmdmsg, /* xmpp.c:/^cmdmsg */ ['n'] cmdnick, /* muc.c:/^cmdnick */ ['p'] cmdpart, /* muc.c:/^cmdpart */ ['r'] cmdroster, /* rost.c:/^cmdroster */ ['R'] cmdroster, /* rost.c:/^cmdroster */ ['s'] cmdsubj, /* muc.c:/^cmdsubj */ ['S'] cmdsubj, /* muc.c:/^cmdsubj */ ['t'] cmdtarget, /* targ.c:/^cmdtarget */ ['w'] cmdwho, /* muc.c:/^cmdwho */ ['W'] cmdwho, /* muc.c:/^cmdwho */ }; int argc; cleaninput(utflen(s)+1); if(*s == '/' && *(++s) != '/'){ if(*s == 'q'){ for(s++; (*s == ' ' || *s == '\t'); s++); lastid[0] = 0; if((ps = utfutf(s, "id='")) != nil && (pe = utfrune(ps+4, '\'')) != nil){ ps += 4; if(sizeof(lastid) > pe-ps) strncpy(lastid, ps, pe-ps); } return fprint(fd, "%s", s); }else if(*s == 'm' && s[1] == 'e'){ s--; }else if(cmds[*s] != nil){ argc = tokenize(s, argv, nelem(argv)); return cmds[*s](fd, argc, argv); }else{ s--; print("unknown cmd %q\n", s); return 0; } } if(curr < 0) return 0; return fprint(fd, "<message to='%Ӽ' type='%Ӽ'><body>%Ӽ</body></message>", targets[curr]->jid, enttypes[targets[curr]->type], s); } static void writer(void *pd) { char *s; int err; USED(pd); threadsetname("writer"); Binit(&kbin, 0, OREAD); for(err = 0; (s = Brdstr(&kbin, '\n', 1)) != nil && err >= 0;){ qlock(&prlock); if(s[0] != 0) err = handle(pfd, s); free(s); qunlock(&prlock); } } static void usage(void) { fprint(2, "usage: xmpp [-n nick] [-r resource] [-p] [-y] jid\n"); threadexits("usage"); } static int die(void *, char *) { setlabel(nil, nil); return 0; } static void pblethal(char *m) { threadexitsall(m); } void threadmain(int argc, char **argv) { UserPasswd *up; char *user; debug = 0; plainallow = 0; nopresence = 1; nohistory = 1; myjid = nil; mynick = getuser(); myresource = nil; curr = -1; ARGBEGIN{ case 'd': debug++; break; case 'n': mynick = EARGF(usage()); break; case 'p': nopresence = 0; break; case 'r': myresource = EARGF(usage()); break; case 'y': plainallow = 1; break; case 'h': nohistory = 0; break; }ARGEND if(argc != 1) usage(); myjid = strdup(argv[0]); quotefmtinstall(); fmtinstall('H', encodefmt); fmtinstall('[', encodefmt); fmtinstall('t', targetfmt); fmtinstall(L'Ӽ', xmlstrfmt); user = strdup(myjid); server = strrchr(user, '@'); if(server == nil) sysfatal("invalid jid: %q", user); *server++ = 0; server = strdup(server); mydomain = strrchr(user, '@'); if(mydomain == nil) mydomain = server; else mydomain = strdup(mydomain); srand(time(nil)); up = auth_getuserpasswd(auth_getkey, "proto=pass service=xmpp server=%s user=%s", server, user); if(up == nil) sysfatal("no password: %r"); if((pfd = connect(&pb, up->user, up->passwd)) < 0) sysfatal("connect: %r"); memset(up->passwd, 0, strlen(up->passwd)); free(up); free(user); setlabel("", nil); Blethal(&pb, pblethal); threadnotify(die, 1); proccreate((void*)reader, nil, 8*1024); writer(nil); fprint(2, "%r\n"); threadexitsall(nil); }