[stunnel-users] Patch for XMPP in client mode

Philipp Hartwig philipp.hartwig at uni-due.de
Fri May 6 00:47:30 CEST 2011


I've attached a new version which adds some sanity checks and improves 
the XML "parsing".

Also the "\r\n" appended by fdputline to the starttls command actually 
violated the XMPP specification[1], Section 5.3.3:
> During STARTTLS negotiation, the entities MUST NOT send any whitespace 
> as separators between XML elements[...]
I have therefore replaced the fdputline with a write_blocking (although 
the servers seemed happy with it).



Let me explain the assumptions I'm operating under:

After connecting to the server the client sends
> <?xml version='1.0'?><stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>
where %s is replaced by the hostname of the server. The client then 
reads the response of the server, discarding all whitespace, until it 
finds
> </stream:features>
or
> <stream:features/>

It aborts with an error message in the second case because the server 
must advertise starttls in the features section, see Section 5.4.1 of 
the spec:
> The receiving entity then MUST send stream features to the initiating 
> entity. If the receiving entity supports TLS, the stream features MUST 
> include an advertisement for support of STARTTLS negotiation[...]

It then sends
> <starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

Section 5.4.2.1 of the spec states
> The receiving entity MUST reply with either a <proceed/> element 
> (proceed case) or a <failure/> element (failure case) qualified by the 
> 'urn:ietf:params:xml:ns:xmpp-tls' namespace. 
so the client continues to read the server stream, discarding all whitespace and 
replacing " by ', until it finds
> <proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'
or
> <failure xmlns='urn:ietf:params:xml:ns:xmpp-tls'

In the second case it aborts with an error message, while in the first 
case it continues to read until it finds
> </proceed>
or
> />

It then starts the TLS negotiation in accordance with 5.4.2.3 of the 
spec:
> The receiving entity MUST consider the TLS negotiation to have begun 
> immediately after sending the closing '>' character of the <proceed/> 
> element to the initiating entity. The initiating entity MUST consider 
> the TLS negotiation to have begun immediately after receiving the 
> closing '>' character of the <proceed/> element from the receiving 
> entity. 

Granted this is a very naive way of "parsing" XML but it is working with 
every server I've tried. Any comments?

Regards,
Philipp

[1] http://xmpp.org/rfcs/rfc6120.html
-------------- next part --------------
diff -u Desktop/stunnel-4.36/src/network.c Desktop/stunnel-4.36-new/src/network.c
--- Desktop/stunnel-4.36/src/network.c	2011-05-01 23:28:59.000000000 +0200
+++ Desktop/stunnel-4.36-new/src/network.c	2011-05-05 23:51:33.453643619 +0200
@@ -727,6 +727,64 @@
     return line;
 }
 
+/* shifts "str" to the left by 1, discarding the first character and appends "chr" as the last character */
+static void shiftstring(char *str, char chr) {
+	int i;
+	int length;
+	length=strlen(str);
+	for(i=0;i<length-1; i++){
+		str[i]=str[i+1];
+	}
+	str[length-1]=chr;
+}
+
+int read_until_alternatives(CLI *c, int fd, char *goalstring1, char *goalstring2) {
+	s_poll_set fds;
+	char line[2];
+	char string[256];
+	int length;
+	int i;
+	length=strlen(goalstring1);
+	for(i=0; i<length; i++){
+		string[i]='0';
+	}
+	string[length]='\0';
+	do {
+        s_poll_init(&fds);
+        s_poll_add(&fds, fd, 1, 0); /* read */
+        switch(s_poll_wait(&fds, c->opt->timeout_busy, 0)) {
+        case -1:
+            sockerror("read_until: s_poll_wait");
+            longjmp(c->err, 1); /* error */
+        case 0:
+            s_log(LOG_INFO, "read_until: s_poll_wait:"
+                " TIMEOUTbusy exceeded: sending reset");
+            longjmp(c->err, 1); /* timeout */
+        case 1:
+            break; /* OK */
+        default:
+            s_log(LOG_ERR, "read_until: s_poll_wait: unknown result");
+            longjmp(c->err, 1); /* error */
+        }
+        switch(readsocket(fd, line, 1)) {
+        case -1: /* error */
+            sockerror("readsocket (read_until)");
+            longjmp(c->err, 1);
+        case 0: /* EOF */
+            s_log(LOG_ERR, "Unexpected socket close (read_until)");
+            longjmp(c->err, 1);
+        }
+		if(line[0] == '"')
+			line[0]='\'';
+		if(!isspace(line[0])) 
+			shiftstring(string,line[0]);
+	} while(strcmp(string, goalstring1)!=0 && !strstr(string, goalstring2));
+	if(strcmp(string, goalstring1)==0)
+		return 0;
+	else
+		return 1;
+}
+
 int fdprintf(CLI *c, int fd, const char *format, ...) {
     va_list ap;
     char *line;
diff -u Desktop/stunnel-4.36/src/protocol.c Desktop/stunnel-4.36-new/src/protocol.c
--- Desktop/stunnel-4.36/src/protocol.c	2011-05-01 23:36:56.000000000 +0200
+++ Desktop/stunnel-4.36-new/src/protocol.c	2011-05-05 23:35:13.080782201 +0200
@@ -56,6 +56,7 @@
 static void nntp_client(CLI *);
 static void connect_client(CLI *);
 static void ntlm(CLI *);
+static void xmpp_client(CLI *);
 #ifndef OPENSSL_NO_MD4
 static char *ntlm1();
 static char *ntlm3(char *, char *, char *);
@@ -79,6 +80,8 @@
             pop3_client(c);
         else if(!strcmp(c->opt->protocol, "imap"))
             imap_client(c);
+        else if(!strcmp(c->opt->protocol, "xmpp"))
+            xmpp_client(c);
         else if(!strcmp(c->opt->protocol, "nntp"))
             nntp_client(c);
         else if(!strcmp(c->opt->protocol, "connect"))
@@ -180,6 +183,28 @@
     write_blocking(c, c->local_wfd.fd, ssl_ok, sizeof ssl_ok);
 }
 
+static void xmpp_client(CLI *c) {
+	char hello_end1[]="</stream:features>";
+	char hello_end2[]="<stream:features/>";
+	char tls_success[]="<proceedxmlns='urn:ietf:params:xml:ns:xmpp-tls'";
+	char tls_failure[]="<failurexmlns='urn:ietf:params:xml:ns:xmpp-tls'";
+	char tls_success_end1[]="</proceed>";
+	char tls_success_end2[]="/>";
+	char starttls_command[]="<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>";
+	fdprintf(c, c->remote_fd.fd, "<?xml version='1.0'?><stream:stream to='%s' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>",strtok(c->opt->remote_address,":"));
+	if(read_until_alternatives(c, c->remote_fd.fd, hello_end1, hello_end2)) {
+		s_log(LOG_ERR, "XMPP server didn't advertise any features");
+		longjmp(c->err, 1);
+	}
+	write_blocking(c, c->remote_fd.fd, starttls_command, strlen(starttls_command));
+    s_log(LOG_DEBUG, " -> %s", starttls_command);
+	if(read_until_alternatives(c, c->remote_fd.fd, tls_success,tls_failure)) {
+		s_log(LOG_ERR, "XMPP server rejected starttls");
+		longjmp(c->err, 1);
+	}
+	read_until_alternatives(c, c->remote_fd.fd, tls_success_end1,tls_success_end2);
+}
+
 static void smtp_client(CLI *c) {
     char *line;
 
diff -u Desktop/stunnel-4.36/src/prototypes.h Desktop/stunnel-4.36-new/src/prototypes.h
--- Desktop/stunnel-4.36/src/prototypes.h	2011-05-01 20:18:01.000000000 +0200
+++ Desktop/stunnel-4.36-new/src/prototypes.h	2011-05-05 23:54:14.770443545 +0200
@@ -363,6 +363,7 @@
 void read_blocking(CLI *, int fd, void *, int);
 void fdputline(CLI *, int, const char *);
 char *fdgetline(CLI *, int);
+int read_until_alternatives(CLI *, int, char *, char *);
 /* descriptor versions of fprintf/fscanf */
 int fdprintf(CLI *, int, const char *, ...)
 #ifdef __GNUC__
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
URL: <http://www.stunnel.org/pipermail/stunnel-users/attachments/20110506/5a30ac41/attachment.sig>


More information about the stunnel-users mailing list