ref: ed2752fe715680692023bbcaf85ddf03c2444c2c
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, *myplainjid;
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 &&
strncmp(to->v, myplainjid, strlen(myplainjid) != 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]);
/* myjid will get set later to a value given by the server,
* but sometimes that value isn't used, so we also
* check against the old value */
myplainjid = strdup(myjid);
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=%q user=%q", 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);
}