Index: jabber_dcc.c
===================================================================
--- jabber_dcc.c	(wersja 4749)
+++ jabber_dcc.c	(kopia robocza)
@@ -233,7 +233,7 @@
 				char *fulluid;
 
 				if (!s->connected) continue;
-				if (!(session_check(s, 1, "jid"))) continue;
+				if (!(session_check(s, 1, "xmpp"))) continue;
 
 				fulluid = saprintf("%s/%s", s->uid+5, j->resource);
 
Index: commands.c
===================================================================
--- commands.c	(wersja 4753)
+++ commands.c	(kopia robocza)
@@ -69,6 +69,7 @@
 
 #include "jabber.h"
 #include "jabber_dcc.h"
+#include "muc.h"
 
 const char *jabber_prefixes[2] = { "xmpp:", "tlen:" };
 extern int config_jabber_disable_chatstates; /* in jabber.c */
@@ -2290,6 +2291,222 @@
 	return 0;
 }
 
+static COMMAND(jabber_command_dcc) {
+	jabber_private_t *j = session_private_get(session);
+	// XXX
+
+	if (!xstrncasecmp(params[0], "se", 2)) { /* send */
+		struct stat st;
+		userlist_t *u;
+		dcc_t *d;
+		FILE *fd;
+		const char *fn;
+
+		if (!params[1] || !params[2]) {
+			printq("not_enough_params", name);
+			return -1;
+		}
+
+		if (!(fn = prepare_path_user(params[2]))) {
+			printq("generic_error", "path too long"); /* XXX? */
+			return -1;
+		}
+
+		if (!(u = userlist_find(session, get_uid(session, params[1])))) {
+			printq("user_not_found", params[1]);
+			return -1;
+		}
+
+		if (!session_connected_get(session)) {
+			printq("not_connected");
+			return -1;
+		}
+
+		if ((u->status <= EKG_STATUS_NA) || !u->resources) {
+			printq("dcc_user_not_avail", format_user(session, u->uid));
+			return -1;
+		}
+
+		if (!stat(fn, &st) && !S_ISREG(st.st_mode)) {
+			printq("io_nonfile", params[2]);
+			return -1;
+		}
+
+		if ((fd = fopen(fn, "r")) == NULL) {
+			printq("io_cantopen", params[2], strerror(errno));
+			return -1;
+		}
+
+		if (u->resources == NULL) {
+			// XXX
+			return -1;
+		}
+
+		{
+			string_t sid = NULL;
+			jabber_dcc_t *p;
+			char *filename;
+			char *pathtmp;
+			char *touid;
+
+	/* XXX, introduce function jabber_get_resource(u, input_uid); */
+			touid = saprintf("%s/%s", u->uid, u->resources->name);
+
+			d 	= dcc_add(session, touid, DCC_SEND, NULL);
+			d->filename 	= xstrdup(fn);
+			d->size		= st.st_size;
+
+			dcc_close_handler_set(d, jabber_dcc_close_handler);
+
+			d->priv = p = xmalloc(sizeof(jabber_dcc_t));
+			p->session 	= session;
+			p->req		= saprintf("offer%d", dcc_id_get(d));
+		
+		/* copied from iris/jabber/s5b.cpp (C) 2003 Justin Karneges under LGPL 2.1 */
+			do {
+				/* generate hash like Psi do */
+				int i;
+
+				sid = string_init("s5b_");
+				for (i = 0; i < 4; i++) {
+					int word = rand() & 0xffff;
+					int n;
+
+					for (n = 0; n < 4; n++) {
+						int dgst  = (word >> (n * 4)) & 0xf;	/* from 0..9 -> '0'..'9', 10..15 - 'A'..'F' */
+
+						if (dgst < 10)	string_append_c(sid, dgst + '0');
+						else		string_append_c(sid, dgst - 10 + 'a');
+					}
+				}
+				debug_function("[jabber] jabber_command_dcc() hash generated: %s errors below are ok.\n", sid->str);
+			} while (jabber_dcc_find(NULL, NULL, sid->str) && !string_free(sid, 1));	/* loop, [if sid exists] + free string if yes */
+
+			p->sid		= string_free(sid, 0);
+			p->sfd		= -1;
+			p->fd		= fd;
+
+				/* XXX: introduce prepare_filename() */
+			if ((pathtmp = xstrrchr(fn, '/'))) 
+				pathtmp++;			/* skip '/' */ 
+			else 	pathtmp = (char*) fn;		/* no '/' ok.  */
+
+			filename = jabber_escape(pathtmp);	/* escape string */
+
+			watch_write(j->send_watch, 
+				"<iq type=\"set\" id=\"%s\" to=\"%s\">"
+				"<si xmlns=\"http://jabber.org/protocol/si\" id=\"%s\" profile=\"http://jabber.org/protocol/si/profile/file-transfer\">"
+					"<file xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\" size=\"%d\" name=\"%s\"><range/></file>"
+					"<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"
+					"<x xmlns=\"jabber:x:data\" type=\"form\">"
+						"<field type=\"list-single\" var=\"stream-method\">"
+							"<option><value>http://jabber.org/protocol/bytestreams</value></option>"
+/*							"<option><value>http://jabber.org/protocol/ibb</value></option>" */
+				"</field></x></feature></si></iq>", p->req, d->uid+5, p->sid, st.st_size, filename);
+			xfree(filename);
+			xfree(touid);
+		}
+		return 0;
+	}
+	if (!xstrncasecmp(params[0], "g", 1) || !xstrncasecmp(params[0], "re", 2)) { /* get, resume */
+		dcc_t *d = NULL;
+		list_t l;
+		
+		for (l = dccs; l; l = l->next) {
+			dcc_t *D = l->data;
+			userlist_t *u;
+
+			if (!dcc_filename_get(D) || dcc_type_get(D) != DCC_GET)
+				continue;
+			
+			if (!params[1]) {
+				if (dcc_active_get(D))
+					continue;
+				d = D;
+				break;
+			}
+
+			if (params[1][0] == '#' && xstrlen(params[1]) > 1 && atoi(params[1] + 1) == dcc_id_get(D)) {
+				d = D;
+				break;
+			}
+
+			if ((u = userlist_find(session, dcc_uid_get(D)))) {
+				if (!xstrcasecmp(params[1], u->uid) || (u->nickname && !xstrcasecmp(params[1], u->nickname))) {
+					d = D;
+					break;
+				}
+			}
+		}
+
+		if (!d || !d->priv) {
+			printq("dcc_not_found", (params[1]) ? params[1] : "");
+			return -1;
+		}
+		
+		if (dcc_active_get(d)) {
+			printq("dcc_receiving_already", dcc_filename_get(d), format_user(session, dcc_uid_get(d)));
+			return -1;
+		}
+
+		if (xstrncmp(d->uid, "xmpp:", 5)) {
+			debug_error("%s:%d /dcc command, incorrect `%s`!\n", __FILE__, __LINE__, __(d->uid));
+			printq("generic_error", "Use /dcc on correct session, sorry");
+			return -1;
+		}
+
+		{
+			jabber_dcc_t *p = d->priv;
+			session_t *s = p->session;
+			jabber_private_t *j = jabber_private(s);
+			char *filename;
+
+			if (p->fd) {
+				debug_error("[jabber] p->fd: 0x%x\n", p->fd);
+				printq("generic_error", "Critical dcc error p->fd != NULL");
+				return -1;
+			}
+
+			filename = saprintf("%s/%s", config_dcc_dir ? config_dcc_dir : prepare_path("download", 1), d->filename);
+			debug("[jabber] DCC/GET Downloading file as: %s\n", filename);
+			/* XXX, sanity d->filename */
+		
+		/* XXX, jesli to jest rget to plik moze istniec */
+			while ((p->fd = fopen(filename, "r"))) {
+				filename = xrealloc(filename, xstrlen(filename)+3);
+				debug_error("[jabber] DCC/GET FILE ALREADY EXISTS APPENDING '.1': %s\n", filename);
+
+				xstrcat(filename, ".1");
+
+				fclose(p->fd);
+			}
+			
+			if (!(p->fd = fopen(filename, "w"))) {
+				int err = errno;
+				debug_error("[jabber] DCC/GET CANNOT CREATE FILE: %s (%s)\n", filename, strerror(err));
+				printq("dcc_get_cant_create", filename, strerror(err));
+				return -1;
+			}
+			/* if resume fseek() to d->offset XXX */
+
+			printq("dcc_get_getting", format_user(session, dcc_uid_get(d)), filename);
+
+			watch_write(j->send_watch, "<iq type=\"result\" to=\"%s\" id=\"%s\">"
+					"<si xmlns=\"http://jabber.org/protocol/si\"><feature xmlns=\"http://jabber.org/protocol/feature-neg\">"
+					"<x xmlns=\"jabber:x:data\" type=\"submit\">"
+					"<field var=\"stream-method\"><value>http://jabber.org/protocol/bytestreams</value></field>"
+					"</x></feature></si></iq>", d->uid+5, p->req);
+		}
+		return 0;
+	}
+
+	if (!xstrncasecmp(params[0], "vo", 2)) { /* voice */
+		printq("not_implemented");
+		return -1;
+	}
+	return cmd_dcc(name, params, session, target, quiet);
+}
+
 void jabber_register_commands()
 {
 #define JABBER_ONLY	    SESSION_MUSTBELONG | SESSION_MUSTHASPRIVATE
@@ -2319,6 +2536,8 @@
 	command_add(&jabber_plugin, "xmpp:chat", "!uU !", jabber_command_msg,	JABBER_FLAGS_MSG, NULL);
 	command_add(&jabber_plugin, "xmpp:connect", NULL, jabber_command_connect, JABBER_ONLY, NULL);
 	command_add(&jabber_plugin, "xmpp:conversations", NULL, jabber_command_conversations,	JABBER_FLAGS, NULL);
+	command_add(&jabber_plugin, "xmpp:dcc", "p uU f ?", jabber_command_dcc,	JABBER_ONLY, 
+			"send get resume voice close list");
 	command_add(&jabber_plugin, "xmpp:del", "!u", jabber_command_del,	JABBER_FLAGS_TARGET, NULL);
 	command_add(&jabber_plugin, "xmpp:disconnect", "r", jabber_command_disconnect, JABBER_ONLY, NULL);
 	command_add(&jabber_plugin, "xmpp:dnd", "r", jabber_command_away,	JABBER_ONLY, NULL);
Index: jabber_handlers_iq_result.inc
===================================================================
--- jabber_handlers_iq_result.inc	(wersja 4749)
+++ jabber_handlers_iq_result.inc	(kopia robocza)
@@ -951,22 +951,140 @@
 		watch_write(j->send_watch, "<iq type=\"get\" id=\"gmail%d\"><query xmlns=\"google:mail:notify\"/></iq>", j->id++);
 }
 
+// XXX
+JABBER_HANDLER_SET(jabber_handle_bytestreams_set) {
+	jabber_private_t *j = s->priv;
+
+	char *uid = jabber_unescape(from);		/* jid */
+	char *sid = jabber_attr(n->atts, "sid");	/* session id */
+#if 0 /* unused */
+	char *smode = jabber_attr(q->atts, "mode"); 	/* tcp, udp */
+#endif
+	dcc_t *d = NULL;
+
+	if ((d = jabber_dcc_find(uid, NULL, sid))) {
+		/* w sumie jak nie mamy nawet tego dcc.. to mozemy kontynuowac ;) */
+		/* problem w tym czy user chce ten plik.. etc.. */
+		/* i tak to na razie jest jeden wielki hack, trzeba sprawdzac czy to dobry typ dcc. etc, XXX */
+		xmlnode_t *node;
+		jabber_dcc_t *p = d->priv;
+		jabber_dcc_bytestream_t *b = NULL;
+
+		list_t host_list = NULL, l;
+		struct jabber_streamhost_item *streamhost;
+
+		if (d->type == DCC_SEND) {
+			watch_write(j->send_watch, 
+					"<iq type=\"error\" to=\"%s\" id=\"%s\"><error code=\"406\">Declined</error></iq>", d->uid+5, id);
+			return;
+		}
+
+		p->protocol = JABBER_DCC_PROTOCOL_BYTESTREAMS;
+
+		xfree(p->req);
+		p->req = xstrdup(id);
+		/* XXX, set our streamhost && send them too */
+		for (node = n->children; node; node = node->next) {
+			if (!xstrcmp(node->name, "streamhost")) {
+				struct jabber_streamhost_item *newstreamhost = xmalloc(sizeof(struct jabber_streamhost_item));
+
+				newstreamhost->ip	= xstrdup(jabber_attr(node->atts, "host"));	/* XXX in host can be hostname */
+				newstreamhost->port	= atoi(jabber_attr(node->atts, "port"));
+				newstreamhost->jid	= xstrdup(jabber_attr(node->atts, "jid"));
+				list_add(&host_list, newstreamhost);
+			}
+		}
+		l = host_list;
+find_streamhost:
+		streamhost = NULL;
+		for (; l; l = l->next) {
+			struct jabber_streamhost_item *item = l->data;
+			struct sockaddr_in sin;
+			/* let's search the list for ipv4 address... for now only this we can handle */
+			if ((inet_pton(AF_INET, item->ip, &(sin.sin_addr)) > 0)) {
+				streamhost = host_list->data;
+				break;
+			}
+		}
+
+		if (streamhost) {
+			struct sockaddr_in sin;
+			int fd;
+			char socks5[4];
+
+			fd = socket(AF_INET, SOCK_STREAM, 0);
+
+			sin.sin_family = AF_INET;
+			sin.sin_port	= htons(streamhost->port);
+			inet_pton(AF_INET, streamhost->ip, &(sin.sin_addr));
+
+			if (connect(fd, (struct sockaddr *) &sin, sizeof(struct sockaddr_in)) == -1) {
+				/* let's try connect once more to another host? */
+				debug_error("[jabber] dcc connecting to: %s failed (%s)\n", streamhost->ip, strerror(errno));
+				goto find_streamhost;
+			}
+			p->sfd = fd;
+
+			watch_add(&jabber_plugin, fd, WATCH_READ, jabber_dcc_handle_recv, d);
+
+			p->priv_data.bytestream = b = xmalloc(sizeof(jabber_dcc_bytestream_t));
+			b->validate	= JABBER_DCC_PROTOCOL_BYTESTREAMS;
+			b->step		= SOCKS5_CONNECT;
+			b->streamlist	= host_list;
+			b->streamhost	= streamhost;
+
+			socks5[0] = 0x05;	/* socks version 5 */
+			socks5[1] = 0x02;	/* number of methods */
+			socks5[2] = 0x00;	/* no auth */
+			socks5[3] = 0x02;	/* username */
+			write(fd, (char *) &socks5, sizeof(socks5));
+		} else {
+			list_t l;
+
+			debug_error("[jabber] We cannot connect to any streamhost with ipv4 address.. sorry, closing connection.\n");
+
+			for (l = host_list; l; l = l->next) {
+				struct jabber_streamhost_item *i = l->data;
+
+				xfree(i->jid);
+				xfree(i->ip);
+			}
+			list_destroy(host_list, 1);
+
+			watch_write(j->send_watch, 
+					"<iq type=\"error\" to=\"%s\" id=\"%s\"><error code=\"404\" type=\"cancel\">"
+					/* Psi: <error code='404'>Could not connect to given hosts</error> */
+					"<item-not-found xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/>"
+					"</error></iq>", d->uid+5, id);
+
+			print("dcc_error_refused", format_user(s, d->uid));
+
+			d->active = 1;		/* hack to avoid sending 403 */
+			dcc_close(d);		/* zamykamy dcc */
+		}
+	}
+}
+
+/**
+ * jabber_handle_si_set()
+ *
+ * <b>XEP-0096: SI File Transfer</b> (http://xmpp.org/extensions/xep-0096.html) <i>[1.1 2004-04-13]</i> (<b><i>iq:type='set' iq::si:xmlns='http://jabber.org/protocol/si'</i></b>)<br>
+ */
 JABBER_HANDLER_SET(jabber_handle_si_set) {
 	xmlnode_t *p;
 
+	/* XXX, profiles, http://jabber.org/protocol/feature-neg */
+
 	if (((p = xmlnode_find_child(n, "file")))) {  /* JEP-0096: File Transfer */
-		dcc_t *D;
 		char *uin = jabber_unescape(from);
-		char *uid;
 		char *filename	= jabber_unescape(jabber_attr(p->atts, "name"));
 		char *size 	= jabber_attr(p->atts, "size");
-#if 0
-		xmlnode_t *range; /* unused? */
-#endif
+
+		char *uid = xmpp_uid(uin);
+
+		dcc_t *D;
 		jabber_dcc_t *jdcc;
 
-		uid = xmpp_uid(uin);
-
 		jdcc = xmalloc(sizeof(jabber_dcc_t));
 		jdcc->session	= s;
 		jdcc->req 	= xstrdup(id);
@@ -978,22 +1096,17 @@
 		dcc_size_set(D, atoi(size));
 		dcc_private_set(D, jdcc);
 		dcc_close_handler_set(D, jabber_dcc_close_handler);
-/* XXX, result
-		if ((range = xmlnode_find_child(p, "range"))) {
-			char *off = jabber_attr(range->atts, "offset");
-			char *len = jabber_attr(range->atts, "length");
-			if (off) dcc_offset_set(D, atoi(off));
-			if (len) dcc_size_set(D, atoi(len));
+
+		if ((xmlnode_find_child(p, "range"))) {
+			/* file can be resumed */
 		}
-*/
+
 		print("dcc_get_offer", format_user(s, uid), filename, size, itoa(dcc_id_get(D))); 
 
 		xfree(uin);
 		xfree(uid);
 		xfree(filename);
 	}
-
-
 }
 
 JABBER_HANDLER_RESULT(jabber_handle_si_result) {
@@ -1062,6 +1175,63 @@
 	} else /* XXX */;
 }
 
+static watch_t *jabber_watch_find(int fd, watch_type_t type) {
+	list_t l;
+	
+	for (l = watches; l; l = l->next) {
+		watch_t *w = l->data;
+
+		if (w && w->plugin == &jabber_plugin && w->fd == fd && w->type == type)
+			return w;
+	}
+	return NULL;
+}
+
+JABBER_HANDLER_RESULT(jabber_handle_bytestreams_result) {
+	char *uid = jabber_unescape(from);		/* jid */
+	dcc_t *d = NULL;
+
+	xmlnode_t *used = xmlnode_find_child(n, "streamhost-used");
+	jabber_dcc_t *p;
+	jabber_dcc_bytestream_t *b;
+	list_t l;
+
+	if ((d = jabber_dcc_find(uid, id, NULL))) {
+		char *usedjid = (used) ? jabber_attr(used->atts, "jid") : NULL;
+		watch_t *w;
+
+		p = d->priv;
+		b = p->priv_data.bytestream;
+
+		for (l = b->streamlist; l; l = l->next) {
+			struct jabber_streamhost_item *item = l->data;
+			if (!xstrcmp(item->jid, usedjid)) {
+				b->streamhost = item;
+			}
+		}
+		debug_function("[STREAMHOST-USED] stream: 0x%x %d\n", b->streamhost, p->sfd);
+		d->active	= 1;
+
+		w = jabber_watch_find(p->sfd, WATCH_NONE);
+
+		if (w && /* w->handler == jabber_dcc_handle_send && */ w->data == d)
+			w->type = WATCH_WRITE;
+		else {
+			debug_error("[jabber] %s:%d WATCH BAD DATA/NOT FOUND, 0x%x 0x%x 0x%x\n", __FILE__, __LINE__, w, w ? w->handler : NULL, w ? w->data : NULL);
+			/* XXX, SHOW ERROR ON __STATUS */
+			dcc_close(d);
+			return;
+		}
+
+		{	/* activate stream */
+			char buf[1];
+			buf[0] = '\r';
+			write(p->sfd, &buf[0], 1);
+		}
+	}
+	debug_function("[FILE - BYTESTREAMS] 0x%x\n", d);
+}
+
 JABBER_HANDLER_RESULT(jabber_handle_bind) {
 	jabber_private_t *j = s->priv;
 	
@@ -1087,7 +1257,7 @@
 	{ "mailbox",	"google:mail:notify",				jabber_handle_gmail_result_mailbox },		/* not done */
 	{ "new-mail",	"google:mail:notify",				jabber_handle_iq_result_new_mail },		/* not done */
 
-	{ "si",		NULL,						jabber_handle_si_result },			/* not done */
+	{ "si",		"http://jabber.org/protocol/si",		jabber_handle_si_result },			/* not done */
 
 	{ "query",	"jabber:iq:last",				jabber_handle_iq_result_last },
 	{ NULL,		"jabber:iq:privacy",				jabber_handle_iq_result_privacy },		/* zaczete */
@@ -1102,6 +1272,8 @@
 	{ NULL,		"http://jabber.org/protocol/muc#owner",		jabber_handle_iq_muc_owner },			/* bez ERROR */
 	{ NULL,		"http://jabber.org/protocol/vacation",		jabber_handle_iq_result_vacation },		/* done, but not checked, without ERROR */
 
+	{ NULL,		"http://jabber.org/protocol/bytestreams",	jabber_handle_bytestreams_result },
+
 	{ "bind",	"urn:ietf:params:xml:ns:xmpp-bind",		jabber_handle_bind },				/* not done */
 
 	{ "",		NULL,						NULL }
@@ -1117,10 +1289,12 @@
 
 	{ "new-mail",	"google:mail:notify",				jabber_handle_iq_set_new_mail },
 
-	{ "si",		NULL,						jabber_handle_si_set },
+	{ "si",		"http://jabber.org/protocol/si",		jabber_handle_si_set },
 
 	{ "query",	"jabber:iq:privacy",				jabber_handle_iq_result_privacy },		/* XXX: przeniesc kod ktory przychodzi jako set do innej funkcji */
 	{ NULL,		"jabber:iq:roster",				jabber_handle_iq_roster },
+
+	{ NULL,		"http://jabber.org/protocol/bytestreams",	jabber_handle_bytestreams_set },
 	{ "",		NULL,						NULL }
 };
 
