summaryrefslogtreecommitdiffstats
path: root/jacs
diff options
context:
space:
mode:
Diffstat (limited to 'jacs')
-rw-r--r--jacs/dat.c89
-rw-r--r--jacs/dat.h10
-rw-r--r--jacs/jacs.c231
-rw-r--r--jacs/jacs.h33
-rw-r--r--jacs/mkfile24
-rw-r--r--jacs/mkfile.plan9port25
-rw-r--r--jacs/recv.c292
-rw-r--r--jacs/recv.h8
-rw-r--r--jacs/roster.c102
-rw-r--r--jacs/roster.h33
-rw-r--r--jacs/xmlpull.h51
11 files changed, 898 insertions, 0 deletions
diff --git a/jacs/dat.c b/jacs/dat.c
new file mode 100644
index 0000000..094f27f
--- /dev/null
+++ b/jacs/dat.c
@@ -0,0 +1,89 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+
+void *
+reallocj(void *p, int s, short d)
+{
+ p = realloc(p, s);
+ if(p == nil)
+ sysfatal("realloc: %r");
+
+ if(d != 0)
+ memset(p, 0, s);
+
+ return (void *)p;
+}
+
+char *
+setwindowlbl(char *w)
+{
+ int s;
+
+ s = open("/dev/label", OWRITE);
+ if(s < 0)
+ return nil;
+
+ write(s, w, strlen(w));
+
+ close(s);
+ return w;
+}
+
+char *
+getwindowlbl(void)
+{
+ int s;
+ char *ret;
+ short i;
+
+ s = open("/dev/label", OREAD);
+ if(s < 0)
+ return nil;
+
+ i = 0;
+ ret = malloc(0);
+ while(realloc(ret, ++i) != nil && read(s, &ret[i - 1], 1) > 0 &&
+ i < 513);
+
+ ret[i - 1] = '\0';
+
+ close(s);
+ return ret;
+}
+
+char *
+mktmstmp(char bord, char bord_e)
+{
+ Tm *tim;
+ char *ret;
+
+ ret = reallocj(nil, 32, 2);
+ tim = localtime(time(0));
+ snprint(ret, 31, "%c%.2d:%.2d%c ", bord, tim->hour, tim->min, bord_e);
+
+ return ret;
+}
+
+char *
+printjid(char *user, char *serv, char *reso)
+{
+ char *ret;
+ int i;
+
+ if(user == nil || serv == nil)
+ return nil;
+
+ i = strlen(user) + strlen(serv) + 3 + ((reso != nil) ? strlen(reso) : 0);
+
+ ret = reallocj(nil, i, 2);
+ snprint(ret, i, "%s@%s%s%s", user, serv, (reso != nil) ? "/" : "\0",
+ ((reso != nil) ? reso : ""));
+
+ return ret;
+}
diff --git a/jacs/dat.h b/jacs/dat.h
new file mode 100644
index 0000000..fca069f
--- /dev/null
+++ b/jacs/dat.h
@@ -0,0 +1,10 @@
+#ifndef JDAT_H
+#define JDAT_H
+
+void *reallocj(void *p, int s, short d);
+char *setwindowlbl(char *w);
+char *getwindowlbl(void);
+char *mktmstmp(char bord, char bord_e);
+char *printjid(char *user, char *serv, char *reso);
+
+#endif
diff --git a/jacs/jacs.c b/jacs/jacs.c
new file mode 100644
index 0000000..21f3139
--- /dev/null
+++ b/jacs/jacs.c
@@ -0,0 +1,231 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#include <u.h>
+#include <libc.h>
+#include <auth.h>
+#include <mp.h>
+#include <libsec.h>
+#include "xmlpull.h"
+#include "jacs.h"
+#include "dat.h"
+#include "roster.h"
+#include "recv.h"
+
+#define NAME "jacs - Jabber Service Registry for Plan9"
+#define VERSION "2nd ed"
+#define OS "Plan9 4th ed"
+
+int
+xmljacc(int sock)
+{
+ return fprint(sock, "<?xml version=\"1.0\"?>\n");
+}
+
+int
+loginjacc(int sock, char *serv)
+{
+ return fprint(sock, "<stream:stream xmlns:stream=\"http://etherx.jabber.org/streams\""
+ " xmlns=\"jabber:client\" to=\"%s\">\n", serv);
+}
+
+int
+userjacc(int sock, char *user, char *pass, char *res)
+{
+ return fprint(sock, "<iq type=\"set\" id=\"auth_1\">\n"
+ "<query xmlns=\"jabber:iq:auth\">\n"
+ "<username>%s</username>\n"
+ "<password>%s</password>\n"
+ "<resource>%s</resource>\n"
+ "</query>\n"
+ "</iq>\n", user, pass, res);
+}
+
+int
+presencejacc(int sock, char *stat, char *show, char *from, char *to)
+{
+ return fprint(sock, "<presence%s%s%s%s%s%s>\n"
+ "<show>%s</show>\n"
+ "<status>%s</status>\n"
+ "<priority>1</priority>\n"
+ "</presence>\n", (from != nil) ? " from=\"" : "",
+ (from != nil) ? from : "",
+ (from != nil) ? "\"" : "",
+ (to != nil) ? " to=\"" : "",
+ (to != nil) ? to : "",
+ (to != nil) ? "\"" : "",
+ (show != nil) ? show : "",
+ (stat != nil) ? stat : "");
+}
+
+int
+versionjacc(int sock, char *from, char *to, char *id)
+{
+ return fprint(sock, "<iq from=\"%s\" type=\"result\" id=\"%s\" to=\"%s\">\n"
+ "<query xmlns=\"jabber:iq:version\">\n"
+ "<name>" NAME "</name>\n"
+ "<version>" VERSION "</version>\n"
+ "<os>" OS "</os>\n"
+ "</query>\n"
+ "</iq>\n", from, id, to);
+}
+
+int
+featuresjacc(int sock, char *from, char *to, char *id)
+{
+ return fprint(sock, "<iq from=\"%s\" type=\"result\" to=\"%s\" id=\"%s\">\n"
+ "<query xmlns=\"http://jabber.org/protocol/disco#info\">\n"
+ "</query>\n"
+ "</iq>\n", from, to, id);
+}
+
+int
+answersjacc(int sock, char *who, char *t, char *id, ilist *l)
+{
+ fprint(sock, "<iq type=\"set\" to=\"%s\" id=\"%s\">\n"
+ "<query xmlns=\"%s\">\n", who, id, t);
+ for(; l; l = l->n)
+ fprint(sock, "<%s>%s</%s>\n", l->name, l->val, l->name);
+
+ return fprint(sock, "</query>\n"
+ "</iq>\n");
+}
+
+int
+xmlnsjacc(int sock, char *who, char *t, char *id)
+{
+ return fprint(sock, "<iq type=\"get\" to=\"%s\" id=\"%s\">\n"
+ "<query xmlns=\"%s\"/>\n"
+ "</iq>\n", who, id, t);
+}
+
+int
+xmlnsnegjacc(int sock, char *who, char *t, char* id)
+{
+ return fprint(sock, "<iq type=\"set\" to=\"%s\" id=\"%s\">\n"
+ "<query xmlns=\"%s\">\n"
+ "<remove/>\n"
+ "</query>\n"
+ "</iq>\n", who, id, t);
+}
+
+void
+usage(void)
+{
+ print("usage: jacs [-dtu] [-e dest] [-s tosrv] [-r res] net!server!port\n");
+ exits(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ char *server, *user, *lbl, *b, *dest, *buf, *toserver;
+ int sock, ts, tls, debug, unreg;
+ UserPasswd *i;
+ TLSconn conn;
+ jabberc *me;
+
+ tls = 0;
+ b = nil;
+ dest = nil;
+ unreg = 0;
+ debug = 0;
+ toserver = nil;
+
+ ARGBEGIN {
+ case 't':
+ tls = 1;
+ break;
+ case 'r':
+ b = EARGF(usage());
+ break;
+ case 'e':
+ dest = EARGF(usage());
+ break;
+ case 'u':
+ unreg = 1;
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 's':
+ toserver = EARGF(usage());
+ break;
+ default:
+ usage();
+ } ARGEND;
+
+ if(argc < 1 || dest == nil)
+ usage();
+ server = strdup(argv[0]);
+
+ lbl = getwindowlbl();
+ user = reallocj(nil, strlen(server) + 9, 2);
+ snprint(user, strlen(server) + 8, "jacs - %s", server);
+ setwindowlbl(user);
+ free(user);
+
+ i = auth_getuserpasswd(auth_getkey, "proto=pass server=%s service=jabber", server);
+ if(i == nil)
+ sysfatal("auth_getuserpasswd: %r");
+
+ sock = dial(netmkaddr(server, "tcp", tls ? "5223" : "5222"), 0, 0, 0);
+ if(sock < 0)
+ sysfatal("dial: &r");
+
+ if(tls){
+ ts = tlsClient(sock, &conn);
+ if(ts < 0)
+ sysfatal("tlsClient: %r");
+ sock = ts;
+
+ if(conn.cert != nil)
+ free(conn.cert);
+ }
+
+ buf = strchr(server, '!');
+ if(buf != nil) {
+ *buf++ = '\0';
+ user = strchr(buf, '!');
+ if(user != nil)
+ *user = '\0';
+ user = strdup(buf);
+ free(server);
+ server = user;
+ }
+
+ if(toserver == nil)
+ toserver = server;
+
+ me = mkjabberc();
+ me->dest = strdup(dest);
+ me->show = strdup("Online");
+ me->stat = strdup("Online");
+ me->name = strdup(i->user);
+ me->serv = strdup(toserver);
+
+ if(b != nil)
+ me->reso = strdup(b);
+ else
+ me->reso = strdup("Plan9-Service");
+ me->jid = printjid(me->name, me->serv, me->reso);
+ me->debug = debug;
+ me->unreg = unreg;
+
+ free(buf);
+
+ if(recvjacc(sock, me, i->passwd) < 0)
+ perror("recv_jacc");
+
+ if(lbl != nil){
+ setwindowlbl(lbl);
+ lbl = nil;
+ free(lbl);
+ }
+
+ freejabberc(me);
+ exits(0);
+ return 0;
+}
diff --git a/jacs/jacs.h b/jacs/jacs.h
new file mode 100644
index 0000000..2475a23
--- /dev/null
+++ b/jacs/jacs.h
@@ -0,0 +1,33 @@
+#ifndef JACS_H
+#define JACS_H
+
+#include "roster.h"
+
+enum {
+ NONE = 0x00,
+ STREAM,
+ AUTH,
+ ERROR,
+ MESSAGE,
+ MESSAGE_INNER,
+ IQ,
+ IQ_INNER,
+ IQ_ERROR,
+ IQ_REGISTER,
+ IQ_REGISTER_INST,
+ IQ_REGISTER_INNE,
+ END
+};
+
+int xmljacc(int sock);
+int loginjacc(int sock, char *serv);
+int userjacc(int sock, char *user, char *pass, char *res);
+int versionjacc(int sock, char *from, char *to, char *id);
+int presencejacc(int sock, char *stat, char *show, char *from, char *to);
+int featuresjacc(int sock, char *from, char *to, char *id);
+int answersjacc(int sock, char *who, char *t, char *id, ilist *l);
+int xmlnsjacc(int sock, char *who, char *t, char *id);
+int xmlnsnegjacc(int sock, char *who, char *t, char *id);
+
+#endif
+
diff --git a/jacs/mkfile b/jacs/mkfile
new file mode 100644
index 0000000..eeebb9a
--- /dev/null
+++ b/jacs/mkfile
@@ -0,0 +1,24 @@
+</$objtype/mkfile
+BIN=/$objtype/bin
+
+TARG=jacs
+OFILES=\
+ dat.$O\
+ roster.$O\
+ recv.$O\
+ jacs.$O\
+
+HFILES=\
+ roster.h\
+ dat.h\
+ recv.h\
+ jacs.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/386/bin/%}\
+
+</sys/src/cmd/mkone
+
diff --git a/jacs/mkfile.plan9port b/jacs/mkfile.plan9port
new file mode 100644
index 0000000..0003be3
--- /dev/null
+++ b/jacs/mkfile.plan9port
@@ -0,0 +1,25 @@
+</$PLAN9/src/mkhdr
+BIN=$PLAN9/bin
+
+LDFLAGS=-lxmlpull
+CFLAGS=-DPLAN9PORT
+TARG=jacc
+OFILES=\
+ dat.$O\
+ roster.$O\
+ recv.$O\
+ jacs.$O\
+
+HFILES=\
+ roster.h\
+ dat.h\
+ recv.h\
+ jacs.h\
+
+UPDATE=\
+ mkfile\
+ $HFILES\
+ ${OFILES:%.$O=%.c}\
+ ${TARG:%=/386/bin/%}\
+
+<$PLAN9/src/mkone
diff --git a/jacs/recv.c b/jacs/recv.c
new file mode 100644
index 0000000..63b1b91
--- /dev/null
+++ b/jacs/recv.c
@@ -0,0 +1,292 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#include <u.h>
+#include <libc.h>
+#include "xmlpull.h"
+#include "jacs.h"
+#include "dat.h"
+#include "roster.h"
+
+char *
+getline(void)
+{
+ char *ret;
+ int l;
+
+ l = -1;
+ ret = reallocj(nil, 1025, 2);
+
+ while(read(0, &ret[++l], 1) && l < 1024)
+ if(ret[l] == '\n')
+ break;
+ ret[l] = '\0';
+
+ return ret;
+}
+
+void
+askanswers(ilist *i, char *tmstmp)
+{
+ ilist *ac;
+ char *val;
+
+ ac = i;
+ while(ac != nil){
+ print("%s%s[%s] = ", tmstmp, ac->name, (ac->val != nil) ? ac->val : "");
+ val = getline();
+ if(ac->val == nil)
+ ac->val = val;
+ else {
+ if(*val != '\0'){
+ free(ac->val);
+ ac->val = val;
+ } else
+ free(val);
+ }
+ ac = ac->n;
+ }
+
+ return;
+}
+
+int
+recvjacc(int sock, jabberc *me, char *pass)
+{
+ xmlpull *x, *b;
+ char *id, *to, *from, *tmstmp, st, *type;
+ ilist *ac;
+
+ type = nil;
+ id = nil;
+ from = nil;
+ to = nil;
+ st = NONE;
+ ac = nil;
+
+ if(xmljacc(sock) < 0)
+ return -1;
+ if(loginjacc(sock, me->serv) < 0)
+ return -1;
+
+ x = openxmlpull(sock);
+ while((b = nextxmlpull(x)) != nil && st != END){
+ tmstmp = mktmstmp('(', ')');
+ switch(b->ev){
+ case START_DOCUMENT:
+ if(me->debug)
+ print("Start.\n");
+ st = NONE;
+ break;
+ case START_TAG:
+ if(me->debug)
+ print("Tag: %s\n", x->na);
+ if(!strcmp(x->na, "stream:stream")){
+ st = STREAM;
+ break;
+ }
+ if(!strcmp(x->na, "stream:error")){
+ st = ERROR;
+ break;
+ }
+ if(st == ERROR){
+ if(strcmp(x->na, "text"))
+ fprint(2, "%serror: %s\n", tmstmp, x->na);
+ break;
+ }
+ if(!strcmp(x->na, "iq")){
+ st = IQ;
+ break;
+ }
+ if(!strcmp(x->na, "error") && st == IQ){
+ st = IQ_ERROR;
+ break;
+ }
+ if(st == IQ_ERROR){
+ print("IQ-Error: %s\n", x->na);
+ break;
+ }
+ if(!strcmp(x->na, "query") && st == IQ){
+ st = IQ_INNER;
+ break;
+ }
+ if(!strcmp(x->na, "instructions") && st == IQ_REGISTER){
+ st = IQ_REGISTER_INST;
+ break;
+ }
+ if(!strcmp(x->na, "query") && st == IQ_REGISTER)
+ break;
+ if(st == IQ_REGISTER){
+ st = IQ_REGISTER_INNE;
+ me->list = addilist(me->list, x->na, nil);
+ ac = lastilist(me->list);
+ break;
+ }
+ break;
+ case START_END_TAG:
+ if(me->debug)
+ print("Startend: %s\n", x->na);
+ if(st == IQ_REGISTER){
+ if(!strcmp(x->na, "registered")){
+ print("%sAlready registerd.\n", tmstmp);
+ break;
+ }
+ if(strcmp(x->na, "remove"))
+ me->list = addilist(me->list, x->na, nil);
+ break;
+ }
+ if(st == ERROR){
+ fprint(2, "%serror: %s\n", tmstmp, x->na);
+ break;
+ }
+ break;
+ case TEXT:
+ if(me->debug)
+ print("Text: %s\n", x->na);
+ switch(st){
+ case IQ_REGISTER_INST:
+ print("%s %s\n", tmstmp, x->na);
+ break;
+ case IQ_REGISTER_INNE:
+ ac->val = strdup(x->na);
+ break;
+ default:
+ break;
+ }
+ break;
+ case ATTR:
+ if(me->debug)
+ print("Attr: %s = %s\n", x->na, x->va);
+ switch(st){
+ case STREAM:
+ if(!strcmp(x->na, "id")){
+ st = NONE;
+ if(userjacc(sock, me->name, pass, me->reso) < 0) {
+ memset(pass, 0, strlen(pass));
+ st = AUTH;
+ break;
+ }
+ }
+ break;
+ case IQ:
+ if(!strcmp(x->na, "id")){
+ if(!strcmp(x->va, "auth_1")) {
+ presencejacc(sock, me->stat, me->show, me->jid, nil);
+ if(me->unreg)
+ xmlnsnegjacc(sock, me->dest, "jabber:iq:register", "service_1");
+ else
+ xmlnsjacc(sock, me->dest, "jabber:iq:register", "service_0");
+ }
+ if(!strcmp(x->va, "service_0"))
+ st = IQ_REGISTER;
+ id = strdup(x->va);
+ }
+ if(!strcmp(x->na, "from"))
+ from = strdup(x->va);
+ if(!strcmp(x->na, "to"))
+ to = strdup(x->va);
+ if(!strcmp(x->na, "type"))
+ type = strdup(x->va);
+ break;
+ case IQ_INNER:
+ if(!strcmp(x->na, "xmlns")){
+ if(!strcmp(x->va, "jabber:iq:version")) {
+ if(!strcmp(to, me->jid)){
+ print("%s%s:\n", tmstmp, from);
+ break;
+ } else
+ versionjacc(sock, me->jid, from, id);
+ break;
+ }
+ if(!strcmp(x->va, "http://jabber.org/protocol/disco#info"))
+ if(!strcmp(me->jid, to))
+ featuresjacc(sock, to, from, id);
+
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case END_TAG:
+ if(me->debug)
+ print("Endtag: %s\n", x->na);
+ if(!strcmp(x->na, "stream:stream")){
+ st = END;
+ break;
+ }
+ if(!strcmp(x->na, "stream:error") && st == ERROR){
+ st = NONE;
+ break;
+ }
+ if(st == ERROR)
+ break;
+ if(!strcmp(x->na, "iq") && (st == IQ || st == IQ_REGISTER)){
+ st = NONE;
+ if(type != nil){
+ if(!strcmp(type, "result") && !strcmp(id, "service_1")){
+ print("%sSuccess.\n", tmstmp);
+ st = END;
+ }
+ free(type);
+ }
+ if(from != nil)
+ free(from);
+ if(to != nil)
+ free(to);
+ if(id != nil)
+ free(id);
+ from = nil;
+ to = nil;
+ id = nil;
+ type = nil;
+ break;
+ }
+ if(!strcmp(x->na, "error") && st == IQ_ERROR){
+ st = IQ;
+ break;
+ }
+ if(!strcmp(x->na, "query") && st == IQ_INNER){
+ st = IQ;
+ break;
+ }
+ if(!strcmp(x->na, "query") && st == IQ_REGISTER){
+ st = IQ;
+ if(me->list != nil){
+ askanswers(me->list, tmstmp);
+ answersjacc(sock, me->dest, "jabber:iq:register", "service_1", me->list);
+ freeilist(me->list);
+ me->list = nil;
+ }
+ break;
+ }
+ if(!strcmp(x->na, "instructions") && st == IQ_REGISTER_INST){
+ st = IQ_REGISTER;
+ break;
+ }
+ if(st == IQ_REGISTER_INNE){
+ st = IQ_REGISTER;
+ break;
+ }
+ break;
+ case END_DOCUMENT:
+ if(me->debug)
+ print("Documentend.\n");
+ st = END;
+ break;
+ default:
+ print("Please contact the xmlpull author about this. %x\n", b->ev);
+ st = END;
+ break;
+ }
+ free(tmstmp);
+ }
+
+ if(id != nil)
+ free(id);
+ freexmlpull(x);
+
+ return 0;
+}
diff --git a/jacs/recv.h b/jacs/recv.h
new file mode 100644
index 0000000..94f09ff
--- /dev/null
+++ b/jacs/recv.h
@@ -0,0 +1,8 @@
+#ifndef RECV_H
+#define RECV_H
+
+char *getline(void);
+void askanswer(ilist *i, char *tmstmp);
+int recvjacc(int sock, jabberc *me, char *pass);
+
+#endif
diff --git a/jacs/roster.c b/jacs/roster.c
new file mode 100644
index 0000000..d4cc372
--- /dev/null
+++ b/jacs/roster.c
@@ -0,0 +1,102 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#include <u.h>
+#include <libc.h>
+#include "dat.h"
+#include "roster.h"
+
+void
+freeilist(ilist *i)
+{
+ while(i != nil){
+ if(i->name != nil)
+ free(i->name);
+ if(i->val != nil)
+ free(i->val);
+ if(i->n != nil){
+ i = i->n;
+ free(i->p);
+ } else {
+ free(i);
+ i = nil;
+ }
+ }
+}
+
+
+void
+freejabberc(jabberc *j)
+{
+ if(j != nil){
+ if(j->stat != nil)
+ free(j->stat);
+ if(j->name != nil)
+ free(j->name);
+ if(j->reso != nil)
+ free(j->reso);
+ if(j->serv != nil)
+ free(j->serv);
+ if(j->jid != nil)
+ free(j->jid);
+ if(j->show != nil)
+ free(j->show);
+ if(j->dest != nil)
+ free(j->dest);
+ if(j->list != nil)
+ freeilist(j->list);
+ free(j);
+ }
+ return;
+}
+
+jabberc *
+mkjabberc(void)
+{
+ return reallocj(nil, sizeof(jabberc), 2);
+}
+
+ilist *
+lastilist(ilist *i)
+{
+ if(i != nil)
+ while(i->n != nil)
+ i = i->n;
+
+ return i;
+}
+
+ilist *
+mkilist(char *name, char *val)
+{
+ ilist *ret;
+
+ ret = reallocj(nil, sizeof(ilist), 2);
+ if(name != nil)
+ ret->name = strdup(name);
+ if(val != nil)
+ ret->val = strdup(val);
+
+ return ret;
+}
+
+ilist *
+addilist(ilist *i, char *name, char *val)
+{
+ ilist *ret;
+
+ ret = lastilist(i);
+ if(ret == nil)
+ return mkilist(name, val);
+ else {
+ ret->n = mkilist(name, val);
+ ret->n->p = ret;
+ }
+ ret->n->n = nil;
+
+ return i;
+}
+
+ \ No newline at end of file
diff --git a/jacs/roster.h b/jacs/roster.h
new file mode 100644
index 0000000..75d49b5
--- /dev/null
+++ b/jacs/roster.h
@@ -0,0 +1,33 @@
+#ifndef ROSTER_H
+#define ROSTER_H
+
+typedef struct ilist ilist;
+struct ilist {
+ char *name;
+ char *val;
+ ilist *p;
+ ilist *n;
+};
+
+typedef struct jabberc jabberc;
+struct jabberc {
+ char *stat;
+ char *show;
+ char *name;
+ char *reso;
+ char *serv;
+ char *jid;
+ char *dest;
+ ilist *list;
+ int debug;
+ int unreg;
+};
+
+void freeilist(ilist *i);
+void freejabberc(jabberc *j);
+jabberc *mkjabberc(void);
+ilist *lastilist(ilist *i);
+ilist *mkilist(char *name, char *val);
+ilist *addilist(ilist *i, char *name, char *val);
+
+#endif
diff --git a/jacs/xmlpull.h b/jacs/xmlpull.h
new file mode 100644
index 0000000..5a008f4
--- /dev/null
+++ b/jacs/xmlpull.h
@@ -0,0 +1,51 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifdef nil
+#pragma lib "libxmlpull.a"
+#endif
+
+#ifndef XMLPULL_H
+#define XMLPULL_H
+
+#ifndef nil
+#define nil NULL
+#define print printf
+#define snprint snprintf
+#define exits return
+#endif
+
+enum {
+ START_DOCUMENT = 0x0,
+ START_TAG,
+ START_END_TAG,
+ TEXT,
+ TEXT_C,
+ ATTR,
+ END_TAG,
+ END_TAG_S,
+ END_TAG_N,
+ END_DOCUMENT,
+};
+
+typedef struct xmlpull xmlpull;
+struct xmlpull {
+ int fd;
+ char ev;
+ char nev;
+ char *lm;
+ char *na;
+ char *va;
+ int la;
+ int lv;
+ int ln;
+};
+
+void freexmlpull(xmlpull *x);
+xmlpull *openxmlpull(int fd);
+xmlpull *nextxmlpull(xmlpull *x);
+xmlpull *writexmlpull(xmlpull *x);
+
+#endif