source: protocols/jabber/s5bytestream.c @ af5764e

Last change on this file since af5764e was de26f3c, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-10-30T11:33:49Z

Killed careless use of strcpy(). Luckily these are only a risk on public
servers.

  • Property mode set to 100644
File size: 32.9 KB
RevLine 
[2ff2076]1/***************************************************************************\
2*                                                                           *
3*  BitlBee - An IRC to IM gateway                                           *
4*  Jabber module - SOCKS5 Bytestreams ( XEP-0065 )                          *
5*                                                                           *
6*  Copyright 2007 Uli Meis <a.sporto+bee@gmail.com>                         *
7*                                                                           *
8*  This program is free software; you can redistribute it and/or modify     *
9*  it under the terms of the GNU General Public License as published by     *
10*  the Free Software Foundation; either version 2 of the License, or        *
11*  (at your option) any later version.                                      *
12*                                                                           *
13*  This program is distributed in the hope that it will be useful,          *
14*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
15*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
16*  GNU General Public License for more details.                             *
17*                                                                           *
18*  You should have received a copy of the GNU General Public License along  *
19*  with this program; if not, write to the Free Software Foundation, Inc.,  *
20*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
21*                                                                           *
22\***************************************************************************/
23
24#include "jabber.h"
25#include "sha1.h"
[a02f34f]26#include "lib/ftutil.h"
[2ff2076]27#include <poll.h>
28
29struct bs_transfer {
30
31        struct jabber_transfer *tf;
32
[dc0ba9c]33        jabber_streamhost_t *sh;
34        GSList *streamhosts;
[2ff2076]35
36        enum 
37        { 
38                BS_PHASE_CONNECT, 
39                BS_PHASE_CONNECTED, 
40                BS_PHASE_REQUEST, 
41                BS_PHASE_REPLY
42        } phase;
43
44        /* SHA1( SID + Initiator JID + Target JID) */
45        char *pseudoadr;
46
47        gint connect_timeout;
[a81d679]48       
49        char peek_buf[64];
50        int peek_buf_len;
[2ff2076]51};
52
53struct socks5_message
54{
55        unsigned char ver;
56        union
57        {
58                unsigned char cmd;
59                unsigned char rep;
60        } cmdrep;
61        unsigned char rsv;
62        unsigned char atyp;
63        unsigned char addrlen;
64        unsigned char address[40];
65        in_port_t port;
66} __attribute__ ((packed)); 
67
[cce0450]68char *socks5_reply_code[] = {
69        "succeeded",
70        "general SOCKS server failure",
71        "connection not allowed by ruleset",
72        "Network unreachable",
73        "Host unreachable",
74        "Connection refused",
75        "TTL expired",
76        "Command not supported",
77        "Address type not supported",
78        "unassigned"};
79
[2ff2076]80/* connect() timeout in seconds. */
81#define JABBER_BS_CONTIMEOUT 15
82/* listen timeout */
83#define JABBER_BS_LISTEN_TIMEOUT  90
84
85/* very useful */
86#define ASSERTSOCKOP(op, msg) \
87        if( (op) == -1 ) \
88                return jabber_bs_abort( bt , msg ": %s", strerror( errno ) );
89
90gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... );
91void jabber_bs_canceled( file_transfer_t *ft , char *reason );
[dc0ba9c]92void jabber_bs_free_transfer( file_transfer_t *ft );
[2ff2076]93gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond );
94gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents );
95gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen );
96
97void jabber_bs_recv_answer_request( struct bs_transfer *bt );
98gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond );
[dce3903]99gboolean jabber_bs_recv_write_request( file_transfer_t *ft );
[2ff2076]100gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond );
101gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error );
[dc0ba9c]102int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode );
[2ff2076]103
104gboolean jabber_bs_send_handshake_abort( struct bs_transfer *bt, char *error );
[dc0ba9c]105gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts );
[2ff2076]106gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond );
[dc0ba9c]107static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig );
108void jabber_bs_send_activate( struct bs_transfer *bt );
[2ff2076]109
110/*
111 * Frees a bs_transfer struct and calls the SI free function
112 */
113void jabber_bs_free_transfer( file_transfer_t *ft) {
114        struct jabber_transfer *tf = ft->data;
115        struct bs_transfer *bt = tf->streamhandle;
[dc0ba9c]116        jabber_streamhost_t *sh;
[2ff2076]117
[2625d6d]118        if ( bt->connect_timeout )
119        {
120                b_event_remove( bt->connect_timeout );
121                bt->connect_timeout = 0;
122        }
123
[2ff2076]124        if ( tf->watch_in )
125                b_event_remove( tf->watch_in );
126       
127        if( tf->watch_out )
128                b_event_remove( tf->watch_out );
129       
130        g_free( bt->pseudoadr );
[dc0ba9c]131
132        while( bt->streamhosts )
133        {
134                sh = bt->streamhosts->data;
135                bt->streamhosts = g_slist_remove( bt->streamhosts, sh );
136                g_free( sh->jid );
137                g_free( sh->host );
138                g_free( sh );
139        }
140       
[2ff2076]141        g_free( bt );
[dce3903]142
[2ff2076]143        jabber_si_free_transfer( ft );
144}
145
[dc0ba9c]146/*
147 * Checks if buflen data is available on the socket and
148 * writes it to buffer if that's the case.
149 */
[2ff2076]150gboolean jabber_bs_peek( struct bs_transfer *bt, void *buffer, int buflen )
151{
152        int ret;
153        int fd = bt->tf->fd;
154
[a81d679]155        if( buflen > sizeof( bt->peek_buf ) )
156                return jabber_bs_abort( bt, "BUG: %d > sizeof(peek_buf)", buflen );
157
158        ASSERTSOCKOP( ret = recv( fd, bt->peek_buf + bt->peek_buf_len,
159                buflen - bt->peek_buf_len, 0 ), "recv() on SOCKS5 connection" );
[2ff2076]160
161        if( ret == 0 )
162                return jabber_bs_abort( bt, "Remote end closed connection" );
163       
[a81d679]164        bt->peek_buf_len += ret;
165        memcpy( buffer, bt->peek_buf, bt->peek_buf_len );
166       
167        if( bt->peek_buf_len == buflen )
168        {
169                /* If we have everything the caller wanted, reset the peek buffer. */
170                bt->peek_buf_len = 0;
171                return buflen;
172        }
173        else
174                return bt->peek_buf_len;
[2ff2076]175}
176
177
178/*
179 * This function is scheduled in bs_handshake via b_timeout_add after a (non-blocking) connect().
180 */
181gboolean jabber_bs_connect_timeout( gpointer data, gint fd, b_input_condition cond )
182{
183        struct bs_transfer *bt = data;
184
185        bt->connect_timeout = 0;
186
187        jabber_bs_abort( bt, "no connection after %d seconds", bt->tf->ft->sending ? JABBER_BS_LISTEN_TIMEOUT : JABBER_BS_CONTIMEOUT );
188
189        return FALSE;
190}
191
[dc0ba9c]192/*
193 * Polls the socket, checks for errors and removes a connect timer
194 * if there is one.
195 */
[2ff2076]196gboolean jabber_bs_poll( struct bs_transfer *bt, int fd, short *revents )
197{
198        struct pollfd pfd = { .fd = fd, .events = POLLHUP|POLLERR };
199       
200        if ( bt->connect_timeout )
201        {
202                b_event_remove( bt->connect_timeout );
203                bt->connect_timeout = 0;
204        }
205
206        ASSERTSOCKOP( poll( &pfd, 1, 0 ), "poll()" )
207
208        if( pfd.revents & POLLERR )
209        {
210                int sockerror;
211                socklen_t errlen = sizeof( sockerror );
212
213                if ( getsockopt( fd, SOL_SOCKET, SO_ERROR, &sockerror, &errlen ) )
214                        return jabber_bs_abort( bt, "getsockopt() failed, unknown socket error during SOCKS5 handshake (weird!)" );
215
216                if ( bt->phase == BS_PHASE_CONNECTED )
217                        return jabber_bs_abort( bt, "connect failed: %s", strerror( sockerror ) );
218
219                return jabber_bs_abort( bt, "Socket error during SOCKS5 handshake(weird!): %s", strerror( sockerror ) );
220        }
221
222        if( pfd.revents & POLLHUP )
223                return jabber_bs_abort( bt, "Remote end closed connection" );
224       
225        *revents = pfd.revents;
226       
227        return TRUE;
228}
229
[dc0ba9c]230/*
231 * Used for receive and send path.
232 */
[2ff2076]233gboolean jabber_bs_abort( struct bs_transfer *bt, char *format, ... )
234{
235        va_list params;
236        va_start( params, format );
237        char error[128];
238
239        if( vsnprintf( error, 128, format, params ) < 0 )
240                sprintf( error, "internal error parsing error string (BUG)" );
241        va_end( params );
242        if( bt->tf->ft->sending )
243                return jabber_bs_send_handshake_abort( bt, error );
[dc0ba9c]244        else
245                return jabber_bs_recv_handshake_abort( bt, error );
[2ff2076]246}
247
248/* Bad luck */
249void jabber_bs_canceled( file_transfer_t *ft , char *reason )
250{
251        struct jabber_transfer *tf = ft->data;
252
253        imcb_log( tf->ic, "File transfer aborted: %s", reason );
254}
255
256/*
257 * Parses an incoming bytestream request and calls jabber_bs_handshake on success.
258 */
259int jabber_bs_recv_request( struct im_connection *ic, struct xt_node *node, struct xt_node *qnode)
260{
261        char *sid, *ini_jid, *tgt_jid, *mode, *iq_id;
262        struct jabber_data *jd = ic->proto_data;
263        struct jabber_transfer *tf = NULL;
264        GSList *tflist;
265        struct bs_transfer *bt;
[dc0ba9c]266        GSList *shlist=NULL;
267        struct xt_node *shnode;
[2ff2076]268
269        sha1_state_t sha;
270        char hash_hex[41];
271        unsigned char hash[20];
272        int i;
273       
274        if( !(iq_id   = xt_find_attr( node, "id" ) ) ||
275            !(ini_jid = xt_find_attr( node, "from" ) ) ||
276            !(tgt_jid = xt_find_attr( node, "to" ) ) ||
277            !(sid     = xt_find_attr( qnode, "sid" ) ) )
278        {
279                imcb_log( ic, "WARNING: Received incomplete SI bytestream request");
280                return XT_HANDLED;
281        }
282
283        if( ( mode = xt_find_attr( qnode, "mode" ) ) &&
284              ( strcmp( mode, "tcp" ) != 0 ) ) 
285        {
286                imcb_log( ic, "WARNING: Received SI Request for unsupported bytestream mode %s", xt_find_attr( qnode, "mode" ) );
287                return XT_HANDLED;
288        }
289
[dc0ba9c]290        shnode = qnode->children;
291        while( ( shnode = xt_find_node( shnode, "streamhost" ) ) )
292        {
[78d254f1]293                char *jid, *host, *port_s;
[dc0ba9c]294                int port;
295                if( ( jid = xt_find_attr( shnode, "jid" ) ) &&
296                    ( host = xt_find_attr( shnode, "host" ) ) &&
[78d254f1]297                    ( port_s = xt_find_attr( shnode, "port" ) ) &&
298                    ( sscanf( port_s, "%d", &port ) == 1 ) )
[dc0ba9c]299                {
300                        jabber_streamhost_t *sh = g_new0( jabber_streamhost_t, 1 );
301                        sh->jid = g_strdup(jid);
302                        sh->host = g_strdup(host);
303                        sprintf( sh->port, "%u", port );
304                        shlist = g_slist_append( shlist, sh );
305                }
306                shnode = shnode->next;
307        }
308       
309        if( !shlist )
310        {
311                imcb_log( ic, "WARNING: Received incomplete SI bytestream request, no parseable streamhost entries");
312                return XT_HANDLED;
313        }
314
[2ff2076]315        /* Let's see if we can find out what this bytestream should be for... */
316
317        for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
318        {
319                struct jabber_transfer *tft = tflist->data;
320                if( ( strcmp( tft->sid, sid ) == 0 ) &&
321                    ( strcmp( tft->ini_jid, ini_jid ) == 0 ) &&
322                    ( strcmp( tft->tgt_jid, tgt_jid ) == 0 ) )
323                {
324                        tf = tft;
325                        break;
326                }
327        }
328
329        if (!tf) 
330        {
331                imcb_log( ic, "WARNING: Received bytestream request from %s that doesn't match an SI request", ini_jid );
332                return XT_HANDLED;
333        }
334
335        /* iq_id and canceled can be reused since SI is done */
336        g_free( tf->iq_id );
337        tf->iq_id = g_strdup( iq_id );
338
339        tf->ft->canceled = jabber_bs_canceled;
340
341        /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
342        sha1_init( &sha );
343        sha1_append( &sha, (unsigned char*) sid, strlen( sid ) );
344        sha1_append( &sha, (unsigned char*) ini_jid, strlen( ini_jid ) );
345        sha1_append( &sha, (unsigned char*) tgt_jid, strlen( tgt_jid ) );
346        sha1_finish( &sha, hash );
347       
348        for( i = 0; i < 20; i ++ )
349                sprintf( hash_hex + i * 2, "%02x", hash[i] );
350               
351        bt = g_new0( struct bs_transfer, 1 );
352        bt->tf = tf;
[dc0ba9c]353        bt->streamhosts = shlist;
354        bt->sh = shlist->data;
[2ff2076]355        bt->phase = BS_PHASE_CONNECT;
356        bt->pseudoadr = g_strdup( hash_hex );
357        tf->streamhandle = bt;
358        tf->ft->free = jabber_bs_free_transfer;
359
[8a90001]360        jabber_bs_recv_handshake( bt, -1, 0 ); 
[2ff2076]361
362        return XT_HANDLED;
363}
[dc0ba9c]364
[2ff2076]365/*
366 * This is what a protocol handshake can look like in cooperative multitasking :)
367 * Might be confusing at first because it's called from different places and is recursing.
368 * (places being the event thread, bs_request, bs_handshake_abort, and itself)
369 *
370 * All in all, it turned out quite nice :)
371 */
372gboolean jabber_bs_recv_handshake( gpointer data, gint fd, b_input_condition cond )
373{
374
375        struct bs_transfer *bt = data;
376        short revents;
[6cac643]377        int gret;
[2ff2076]378
[8a90001]379        if ( ( fd != -1 ) && !jabber_bs_poll( bt, fd, &revents ) )
[2ff2076]380                return FALSE;
381       
382        switch( bt->phase ) 
383        {
384        case BS_PHASE_CONNECT:
385                {
386                        struct addrinfo hints, *rp;
387
[dc0ba9c]388                        memset( &hints, 0, sizeof( struct addrinfo ) );
389                        hints.ai_socktype = SOCK_STREAM;
[2ff2076]390
[6cac643]391                        if ( ( gret = getaddrinfo( bt->sh->host, bt->sh->port, &hints, &rp ) ) != 0 )
392                                return jabber_bs_abort( bt, "getaddrinfo() failed: %s", gai_strerror( gret ) );
[2ff2076]393
[dc0ba9c]394                        ASSERTSOCKOP( bt->tf->fd = fd = socket( rp->ai_family, rp->ai_socktype, 0 ), "Opening socket" );
[2ff2076]395
[dc0ba9c]396                        sock_make_nonblocking( fd );
[2ff2076]397
[dc0ba9c]398                        imcb_log( bt->tf->ic, "File %s: Connecting to streamhost %s:%s", bt->tf->ft->file_name, bt->sh->host, bt->sh->port );
[2ff2076]399
[dc0ba9c]400                        if( ( connect( fd, rp->ai_addr, rp->ai_addrlen ) == -1 ) &&
401                            ( errno != EINPROGRESS ) )
402                                return jabber_bs_abort( bt , "connect() failed: %s", strerror( errno ) );
[2ff2076]403
[dc0ba9c]404                        freeaddrinfo( rp );
[2ff2076]405
[dc0ba9c]406                        bt->phase = BS_PHASE_CONNECTED;
407                       
[d25ebea]408                        bt->tf->watch_out = b_input_add( fd, B_EV_IO_WRITE, jabber_bs_recv_handshake, bt );
[2ff2076]409
[dc0ba9c]410                        /* since it takes forever(3mins?) till connect() fails on itself we schedule a timeout */
411                        bt->connect_timeout = b_timeout_add( JABBER_BS_CONTIMEOUT * 1000, jabber_bs_connect_timeout, bt );
[2ff2076]412
[dc0ba9c]413                        bt->tf->watch_in = 0;
414                        return FALSE;
[2ff2076]415                }
416        case BS_PHASE_CONNECTED:
417                {
418                        struct {
419                                unsigned char ver;
420                                unsigned char nmethods;
421                                unsigned char method;
422                        } socks5_hello = {
423                                .ver = 5,
424                                .nmethods = 1,
425                                .method = 0x00 /* no auth */
426                                /* one could also implement username/password. If you know
427                                 * a jabber client or proxy that actually does it, tell me.
428                                 */
429                        };
430                       
431                        ASSERTSOCKOP( send( fd, &socks5_hello, sizeof( socks5_hello ) , 0 ), "Sending auth request" );
432
433                        bt->phase = BS_PHASE_REQUEST;
434
[d25ebea]435                        bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_recv_handshake, bt );
[2ff2076]436
437                        bt->tf->watch_out = 0;
438                        return FALSE;
439                }
440        case BS_PHASE_REQUEST:
441                {
442                        struct socks5_message socks5_connect = 
443                        {
444                                .ver = 5,
445                                .cmdrep.cmd = 0x01,
446                                .rsv = 0,
447                                .atyp = 0x03,
448                                .addrlen = strlen( bt->pseudoadr ),
449                                .port = 0
450                        };
451                        int ret;
452                        char buf[2];
453
454                        /* If someone's trying to be funny and sends only one byte at a time we'll fail :) */
455                        ASSERTSOCKOP( ret = recv( fd, buf, 2, 0 ) , "Receiving auth reply" );
456
457                        if( !( ret == 2 ) ||
458                            !( buf[0] == 5 ) ||
459                            !( buf[1] == 0 ) )
460                                return jabber_bs_abort( bt, "Auth not accepted by streamhost (reply: len=%d, ver=%d, status=%d)",
461                                                                        ret, buf[0], buf[1] );
462
463                        /* copy hash into connect message */
464                        memcpy( socks5_connect.address, bt->pseudoadr, socks5_connect.addrlen );
465
466                        ASSERTSOCKOP( send( fd, &socks5_connect, sizeof( struct socks5_message ), 0 ) , "Sending SOCKS5 Connect" );
467
468                        bt->phase = BS_PHASE_REPLY;
469
470                        return TRUE;
471                }
472        case BS_PHASE_REPLY:
473                {
474                        struct socks5_message socks5_reply;
475                        int ret;
476
477                        if ( !( ret = jabber_bs_peek( bt, &socks5_reply, sizeof( struct socks5_message ) ) ) )
478                                return FALSE;
479
[29c1456]480                        if ( ret < 5 ) /* header up to address length */
[2ff2076]481                                return TRUE;
[29c1456]482                        else if( ret < sizeof( struct socks5_message ) )
483                        {
[3ab1d31]484                                /* Either a buggy proxy or just one that doesnt regard
485                                 * the SHOULD in XEP-0065 saying the reply SHOULD
486                                 * contain the address. We'll take it, so make sure the
487                                 * next jabber_bs_peek starts with an empty buffer. */
488                                bt->peek_buf_len = 0;
[29c1456]489                        }
[2ff2076]490
491                        if( !( socks5_reply.ver == 5 ) ||
[cce0450]492                            !( socks5_reply.cmdrep.rep == 0 ) ) {
493                                char errstr[128] = "";
494                                if( ( socks5_reply.ver == 5 ) && ( socks5_reply.cmdrep.rep < 
495                                    ( sizeof( socks5_reply_code ) / sizeof( socks5_reply_code[0] ) ) ) ) {
496                                        sprintf( errstr, "with \"%s\" ", socks5_reply_code[ socks5_reply.cmdrep.rep ] );
497                                }
498                                return jabber_bs_abort( bt, "SOCKS5 CONNECT failed %s(reply: ver=%d, rep=%d, atyp=%d, addrlen=%d)", 
499                                        errstr,
[2ff2076]500                                        socks5_reply.ver,
501                                        socks5_reply.cmdrep.rep,
502                                        socks5_reply.atyp,
503                                        socks5_reply.addrlen);
[cce0450]504                        }
[29c1456]505                       
506                        /* usually a proxy sends back the 40 bytes address but I encountered at least one (of jabber.cz)
507                         * that sends atyp=0 addrlen=0 and only 6 bytes (one less than one would expect).
508                         * Therefore I removed the wait for more bytes. Since we don't care about what else the proxy
509                         * is sending, it shouldnt matter */
[2ff2076]510
[dc0ba9c]511                        if( bt->tf->ft->sending )
512                                jabber_bs_send_activate( bt );
513                        else
514                                jabber_bs_recv_answer_request( bt );
[2ff2076]515
516                        return FALSE;
517                }
518        default:
519                /* BUG */
520                imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
521
522                bt->tf->watch_in = 0;
523                return FALSE;
524        }
525}
526
527/*
528 * If the handshake failed we can try the next streamhost, if there is one.
529 * An intelligent sender would probably specify himself as the first streamhost and
[dce3903]530 * a proxy as the second (Kopete and PSI are examples here). That way, a (potentially)
531 * slow proxy is only used if neccessary. This of course also means, that the timeout
532 * per streamhost should be kept short. If one or two firewalled adresses are specified,
533 * they have to timeout first before a proxy is tried.
[2ff2076]534 */
535gboolean jabber_bs_recv_handshake_abort( struct bs_transfer *bt, char *error )
536{
537        struct jabber_transfer *tf = bt->tf;
538        struct xt_node *reply, *iqnode;
[dc0ba9c]539        GSList *shlist;
540
541        imcb_log( tf->ic, "Transferring file %s: connection to streamhost %s:%s failed (%s)", 
542                  tf->ft->file_name, 
543                  bt->sh->host,
544                  bt->sh->port,
545                  error );
[2ff2076]546
[dc0ba9c]547        /* Alright, this streamhost failed, let's try the next... */
548        bt->phase = BS_PHASE_CONNECT;
549        shlist = g_slist_find( bt->streamhosts, bt->sh );
550        if( shlist && shlist->next )
[2ff2076]551        {
[dc0ba9c]552                bt->sh = shlist->next->data;
[8a90001]553                return jabber_bs_recv_handshake( bt, -1, 0 );
[2ff2076]554        }
555
[dc0ba9c]556
[2ff2076]557        /* out of stream hosts */
558
559        iqnode = jabber_make_packet( "iq", "result", tf->ini_jid, NULL );
560        reply = jabber_make_error_packet( iqnode, "item-not-found", "cancel" , "404" );
561        xt_free_node( iqnode );
562
563        xt_add_attr( reply, "id", tf->iq_id );
564               
565        if( !jabber_write_packet( tf->ic, reply ) )
566                imcb_log( tf->ic, "WARNING: Error transmitting bytestream response" );
567        xt_free_node( reply );
568
[17a6ee9]569        imcb_file_canceled( tf->ic, tf->ft, "couldn't connect to any streamhosts" );
[2ff2076]570
[a81d679]571        /* MUST always return FALSE! */
[2ff2076]572        return FALSE;
573}
574
575/*
576 * After the SOCKS5 handshake succeeds we need to inform the initiator which streamhost we chose.
577 * If he is the streamhost himself, he might already know that. However, if it's a proxy,
578 * the initiator will have to make a connection himself.
579 */
580void jabber_bs_recv_answer_request( struct bs_transfer *bt )
581{
582        struct jabber_transfer *tf = bt->tf;
583        struct xt_node *reply;
584
[dce3903]585        imcb_log( tf->ic, "File %s: established SOCKS5 connection to %s:%s", 
[2ff2076]586                  tf->ft->file_name, 
[dc0ba9c]587                  bt->sh->host,
588                  bt->sh->port );
[2ff2076]589
590        tf->ft->data = tf;
[d25ebea]591        tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
[dce3903]592        tf->ft->write_request = jabber_bs_recv_write_request;
[2ff2076]593
594        reply = xt_new_node( "streamhost-used", NULL, NULL );
[dc0ba9c]595        xt_add_attr( reply, "jid", bt->sh->jid );
[2ff2076]596
597        reply = xt_new_node( "query", NULL, reply );
598        xt_add_attr( reply, "xmlns", XMLNS_BYTESTREAMS );
599
600        reply = jabber_make_packet( "iq", "result", tf->ini_jid, reply );
601
602        xt_add_attr( reply, "id", tf->iq_id );
603               
604        if( !jabber_write_packet( tf->ic, reply ) )
[17a6ee9]605                imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream response" );
[2ff2076]606        xt_free_node( reply );
607}
608
[dce3903]609/*
610 * This function is called from write_request directly. If no data is available, it will install itself
611 * as a watcher for input on fd and once that happens, deliver the data and unschedule itself again.
612 */
[2ff2076]613gboolean jabber_bs_recv_read( gpointer data, gint fd, b_input_condition cond )
614{
615        int ret;
[dce3903]616        struct bs_transfer *bt = data;
617        struct jabber_transfer *tf = bt->tf;
[2ff2076]618
[8a90001]619        if( fd != -1 ) /* called via event thread */
[dce3903]620        {
621                tf->watch_in = 0;
622                ASSERTSOCKOP( ret = recv( fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) , "Receiving" );
623        }
624        else
[2ff2076]625        {
[dce3903]626                /* called directly. There might not be any data available. */
627                if( ( ( ret = recv( tf->fd, tf->ft->buffer, sizeof( tf->ft->buffer ), 0 ) ) == -1 ) &&
628                    ( errno != EAGAIN ) )
629                    return jabber_bs_abort( bt, "Receiving: %s", strerror( errno ) );
630
631                if( ( ret == -1 ) && ( errno == EAGAIN ) )
[2ff2076]632                {
[d25ebea]633                        tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_recv_read, bt );
[2ff2076]634                        return FALSE;
635                }
636        }
637
[dce3903]638        /* shouldn't happen since we know the file size */
[2ff2076]639        if( ret == 0 )
[dce3903]640                return jabber_bs_abort( bt, "Remote end closed connection" );
[2ff2076]641       
642        tf->bytesread += ret;
643
[4ac647d]644        if( tf->bytesread >= tf->ft->file_size )
[17a6ee9]645                imcb_file_finished( tf->ic, tf->ft );
[4ac647d]646
[dce3903]647        tf->ft->write( tf->ft, tf->ft->buffer, ret );   
[2ff2076]648
[dce3903]649        return FALSE;
[2ff2076]650}
651
[dce3903]652/*
653 * imc callback that is invoked when it is ready to receive some data.
654 */
655gboolean jabber_bs_recv_write_request( file_transfer_t *ft )
[2ff2076]656{
657        struct jabber_transfer *tf = ft->data;
658
[dce3903]659        if( tf->watch_in )
660        {
[17a6ee9]661                imcb_file_canceled( tf->ic, ft, "BUG in jabber file transfer: write_request called when already watching for input" );
[dce3903]662                return FALSE;
663        }
664       
[8a90001]665        jabber_bs_recv_read( tf->streamhandle, -1 , 0 );
[2ff2076]666
[dce3903]667        return TRUE;
[2ff2076]668}
669
[dce3903]670/*
671 * Issues a write_request to imc.
672 * */
[2ff2076]673gboolean jabber_bs_send_can_write( gpointer data, gint fd, b_input_condition cond )
674{
675        struct bs_transfer *bt = data;
676
677        bt->tf->watch_out = 0;
[dce3903]678
679        bt->tf->ft->write_request( bt->tf->ft );
680
[2ff2076]681        return FALSE;
682}
683
[dce3903]684/*
685 * This should only be called if we can write, so just do it.
686 * Add a write watch so we can write more during the next cycle (if possible).
687 */
688gboolean jabber_bs_send_write( file_transfer_t *ft, char *buffer, unsigned int len )
[2ff2076]689{
690        struct jabber_transfer *tf = ft->data;
691        struct bs_transfer *bt = tf->streamhandle;
692        int ret;
693
[dce3903]694        if( tf->watch_out )
695                return jabber_bs_abort( bt, "BUG: write() called while watching " );
[2ff2076]696       
[dc0ba9c]697        /* TODO: catch broken pipe */
[dce3903]698        ASSERTSOCKOP( ret = send( tf->fd, buffer, len, 0 ), "Sending" );
699
700        tf->byteswritten += ret;
[2ff2076]701       
[dce3903]702        /* TODO: this should really not be fatal */
703        if( ret < len )
704                return jabber_bs_abort( bt, "send() sent %d instead of %d (send buffer too big!)", ret, len );
705
[4ac647d]706        if( tf->byteswritten >= ft->file_size )
[17a6ee9]707                imcb_file_finished( tf->ic, ft );
[4ac647d]708        else
[d25ebea]709                bt->tf->watch_out = b_input_add( tf->fd, B_EV_IO_WRITE, jabber_bs_send_can_write, bt );
[2ff2076]710               
711        return TRUE;
712}
713
[dce3903]714/*
715 * Handles the reply by the receiver containing the used streamhost.
716 */
[2ff2076]717static xt_status jabber_bs_send_handle_reply(struct im_connection *ic, struct xt_node *node, struct xt_node *orig ) {
718        struct jabber_transfer *tf = NULL;
719        struct jabber_data *jd = ic->proto_data;
720        struct bs_transfer *bt;
721        GSList *tflist;
722        struct xt_node *c;
723        char *sid, *jid;
724
725        if( !( c = xt_find_node( node->children, "query" ) ) ||
726            !( c = xt_find_node( c->children, "streamhost-used" ) ) ||
727            !( jid = xt_find_attr( c, "jid" ) ) )
728
729        {
730                imcb_log( ic, "WARNING: Received incomplete bytestream reply" );
731                return XT_HANDLED;
732        }
733       
734        if( !( c = xt_find_node( orig->children, "query" ) ) ||
735            !( sid = xt_find_attr( c, "sid" ) ) )
736        {
737                imcb_log( ic, "WARNING: Error parsing request corresponding to the incoming bytestream reply" );
738                return XT_HANDLED;
739        }
740
741        /* Let's see if we can find out what this bytestream should be for... */
742
743        for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
744        {
745                struct jabber_transfer *tft = tflist->data;
746                if( ( strcmp( tft->sid, sid ) == 0 ) )
747                {
748                        tf = tft;
749                        break;
750                }
751        }
752
753        if( !tf )
754        {
755                imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply to unknown request" );
756                return XT_HANDLED;
757        }
758
759        bt = tf->streamhandle;
760
761        tf->accepted = TRUE;
762
[dc0ba9c]763        if( strcmp( jid, tf->ini_jid ) == 0 )
[2ff2076]764        {
[dc0ba9c]765                /* we're streamhost and target */
766                if( bt->phase == BS_PHASE_REPLY )
767                {
768                        /* handshake went through, let's start transferring */
769                        tf->ft->write_request( tf->ft );
770                }
771        } else
772        {
[2625d6d]773                /* using a proxy, abort listen */
774
[8a90001]775                if( tf->watch_in )
[29c1456]776                {
777                        b_event_remove( tf->watch_in );
778                        tf->watch_in = 0;
779                }
780               
[8a90001]781                if( tf->fd != -1 ) {
782                        closesocket( tf->fd );
783                        tf->fd = -1;
784                }
[2625d6d]785
786                if ( bt->connect_timeout )
787                {
788                        b_event_remove( bt->connect_timeout );
789                        bt->connect_timeout = 0;
790                }
791
[dc0ba9c]792                GSList *shlist;
793                for( shlist = jd->streamhosts ; shlist ; shlist = g_slist_next( shlist ) )
794                {
795                        jabber_streamhost_t *sh = shlist->data;
796                        if( strcmp( sh->jid, jid ) == 0 )
797                        {
798                                bt->sh = sh;
[8a90001]799                                jabber_bs_recv_handshake( bt, -1, 0 );
[dc0ba9c]800                                return XT_HANDLED;
801                        }
802                }
803
804                imcb_log( ic, "WARNING: Received SOCKS5 bytestream reply with unknown streamhost %s", jid );
[2ff2076]805        }
806
807        return XT_HANDLED;
808}
809
[dc0ba9c]810/*
811 * Tell the proxy to activate the stream. Looks like this:
812 *
813 * <iq type=set>
814 *      <query xmlns=bs sid=sid>
815 *              <activate>tgt_jid</activate>
816 *      </query>
817 * </iq>
818 */
819void jabber_bs_send_activate( struct bs_transfer *bt )
820{
821        struct xt_node *node;
822
823        node = xt_new_node( "activate", bt->tf->tgt_jid, NULL );
824        node = xt_new_node( "query", NULL, node );
825        xt_add_attr( node, "xmlns", XMLNS_BYTESTREAMS );
826        xt_add_attr( node, "sid", bt->tf->sid );
827        node = jabber_make_packet( "iq", "set", bt->sh->jid, node );
828
829        jabber_cache_add( bt->tf->ic, node, jabber_bs_send_handle_activate );
830
831        jabber_write_packet( bt->tf->ic, node );
832}
833
834/*
835 * The proxy has activated the bytestream.
836 * We can finally start pushing some data out.
837 */
838static xt_status jabber_bs_send_handle_activate( struct im_connection *ic, struct xt_node *node, struct xt_node *orig )
839{
840        char *sid;
841        GSList *tflist;
[b8a491d]842        struct jabber_transfer *tf = NULL;
[dc0ba9c]843        struct xt_node *query;
844        struct jabber_data *jd = ic->proto_data;
845
846        query = xt_find_node( orig->children, "query" );
847        sid = xt_find_attr( query, "sid" );
848
849        for( tflist = jd->filetransfers ; tflist; tflist = g_slist_next(tflist) )
850        {
851                struct jabber_transfer *tft = tflist->data;
852                if( ( strcmp( tft->sid, sid ) == 0 ) )
853                {
854                        tf = tft;
855                        break;
856                }
857        }
858
859        if( !tf )
860        {
861                imcb_log( ic, "WARNING: Received SOCKS5 bytestream activation for unknown stream" );
862                return XT_HANDLED;
863        }
864
[29c1456]865        imcb_log( tf->ic, "File %s: SOCKS5 handshake and activation successful! Transfer about to start...", tf->ft->file_name );
866
[dc0ba9c]867        /* handshake went through, let's start transferring */
868        tf->ft->write_request( tf->ft );
869
870        return XT_HANDLED;
871}
872
[8a90001]873jabber_streamhost_t *jabber_si_parse_proxy( struct im_connection *ic, char *proxy )
874{
875        char *host, *port, *jid;
876        jabber_streamhost_t *sh;
877
878        if( ( ( host = strchr( proxy, ',' ) ) == 0 ) ||
[de26f3c]879            ( ( port = strchr( host+1, ',' ) ) == 0 ) )
880        {
[8a90001]881                imcb_log( ic, "Error parsing proxy setting: \"%s\" (ignored)", proxy );
882                return NULL;
883        }
884       
885        jid = proxy;
886        *host++ = '\0';
887        *port++ = '\0';
888
889        sh = g_new0( jabber_streamhost_t, 1 );
890        sh->jid = g_strdup( jid );
891        sh->host = g_strdup( host );
[de26f3c]892        g_snprintf( sh->port, sizeof( sh->port ), "%s", port );
[8a90001]893
894        return sh;
895}
896
897void jabber_si_set_proxies( struct bs_transfer *bt )
898{
899        struct jabber_transfer *tf = bt->tf;
900        struct jabber_data *jd = tf->ic->proto_data;
901        char *proxysetting = g_strdup ( set_getstr( &tf->ic->acc->set, "proxy" ) );
[a02f34f]902        char *proxy, *next, *errmsg = NULL;
[8a90001]903        char port[6];
[60e4df3]904        char host[HOST_NAME_MAX+1];
[8a90001]905        jabber_streamhost_t *sh, *sh2;
906        GSList *streamhosts = jd->streamhosts;
907
908        proxy = proxysetting;
909        while ( proxy && ( *proxy!='\0' ) ) {
910                if( ( next = strchr( proxy, ';' ) ) )
911                        *next++ = '\0'; 
912               
[a02f34f]913                if( strcmp( proxy, "<local>" ) == 0 ) {
[f1f7b5e]914                        if( ( tf->fd = ft_listen( &tf->saddr, host, port, jd->fd, FALSE, &errmsg ) ) != -1 ) {
[a02f34f]915                                sh = g_new0( jabber_streamhost_t, 1 );
916                                sh->jid = g_strdup( tf->ini_jid );
917                                sh->host = g_strdup( host );
[de26f3c]918                                g_snprintf( sh->port, sizeof( sh->port ), "%s", port );
[a02f34f]919                                bt->streamhosts = g_slist_append( bt->streamhosts, sh );
[8a90001]920
[d25ebea]921                                bt->tf->watch_in = b_input_add( tf->fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
[a02f34f]922                                bt->connect_timeout = b_timeout_add( JABBER_BS_LISTEN_TIMEOUT * 1000, jabber_bs_connect_timeout, bt );
923                        } else {
924                                imcb_log( tf->ic, "Transferring file %s: couldn't listen locally(non fatal, check your ft_listen setting in bitlbee.conf): %s",
925                                          tf->ft->file_name,
926                                          errmsg );
927                        }
[8a90001]928                } else if( strcmp( proxy, "<auto>" ) == 0 ) {
929                        while ( streamhosts ) {
930                                sh = g_new0( jabber_streamhost_t, 1 );
931                                sh2 = streamhosts->data;
932                                sh->jid = g_strdup( sh2->jid );
933                                sh->host = g_strdup( sh2->host );
934                                strcpy( sh->port, sh2->port );
935                                bt->streamhosts = g_slist_append( bt->streamhosts, sh );
936                                streamhosts = g_slist_next( streamhosts );
937                        }
938                } else if( ( sh = jabber_si_parse_proxy( tf->ic, proxy ) ) )
939                        bt->streamhosts = g_slist_append( bt->streamhosts, sh );
940                proxy = next;
941        }
942}
943
[dc0ba9c]944/*
945 * Starts a bytestream.
946 */
[2ff2076]947gboolean jabber_bs_send_start( struct jabber_transfer *tf )
948{
949        struct bs_transfer *bt;
950        sha1_state_t sha;
951        char hash_hex[41];
952        unsigned char hash[20];
[dc0ba9c]953        int i,ret;
[2ff2076]954
955        /* SHA1( SID + Initiator JID + Target JID ) is given to the streamhost which it will match against the initiator's value */
956        sha1_init( &sha );
957        sha1_append( &sha, (unsigned char*) tf->sid, strlen( tf->sid ) );
958        sha1_append( &sha, (unsigned char*) tf->ini_jid, strlen( tf->ini_jid ) );
959        sha1_append( &sha, (unsigned char*) tf->tgt_jid, strlen( tf->tgt_jid ) );
960        sha1_finish( &sha, hash );
961       
962        for( i = 0; i < 20; i ++ )
963                sprintf( hash_hex + i * 2, "%02x", hash[i] );
964               
965        bt = g_new0( struct bs_transfer, 1 );
966        bt->tf = tf;
967        bt->phase = BS_PHASE_CONNECT;
968        bt->pseudoadr = g_strdup( hash_hex );
969        tf->streamhandle = bt;
970        tf->ft->free = jabber_bs_free_transfer;
971        tf->ft->canceled = jabber_bs_canceled;
972
[8a90001]973        jabber_si_set_proxies( bt );
[dc0ba9c]974
[8a90001]975        ret = jabber_bs_send_request( tf, bt->streamhosts);
[dc0ba9c]976
977        return ret;
[2ff2076]978}
979
[dc0ba9c]980gboolean jabber_bs_send_request( struct jabber_transfer *tf, GSList *streamhosts )
[2ff2076]981{
[dc0ba9c]982        struct xt_node *shnode, *query, *iq;
[2ff2076]983
984        query = xt_new_node( "query", NULL, NULL );
985        xt_add_attr( query, "xmlns", XMLNS_BYTESTREAMS );
986        xt_add_attr( query, "sid", tf->sid );
987        xt_add_attr( query, "mode", "tcp" );
[dc0ba9c]988
989        while( streamhosts ) {
990                jabber_streamhost_t *sh = streamhosts->data;
991                shnode = xt_new_node( "streamhost", NULL, NULL );
992                xt_add_attr( shnode, "jid", sh->jid );
993                xt_add_attr( shnode, "host", sh->host );
994                xt_add_attr( shnode, "port", sh->port );
995
996                xt_add_child( query, shnode );
997
998                streamhosts = g_slist_next( streamhosts );
999        }
1000
[2ff2076]1001
1002        iq = jabber_make_packet( "iq", "set", tf->tgt_jid, query );
1003        xt_add_attr( iq, "from", tf->ini_jid );
1004
1005        jabber_cache_add( tf->ic, iq, jabber_bs_send_handle_reply );
1006
1007        if( !jabber_write_packet( tf->ic, iq ) )
[17a6ee9]1008                imcb_file_canceled( tf->ic, tf->ft, "Error transmitting bytestream request" );
[2ff2076]1009        return TRUE;
1010}
1011
1012gboolean jabber_bs_send_handshake_abort(struct bs_transfer *bt, char *error )
1013{
1014        struct jabber_transfer *tf = bt->tf;
[2625d6d]1015        struct jabber_data *jd = tf->ic->proto_data;
[2ff2076]1016
[dc0ba9c]1017        /* TODO: did the receiver get here somehow??? */
[2ff2076]1018        imcb_log( tf->ic, "Transferring file %s: SOCKS5 handshake failed: %s", 
1019                  tf->ft->file_name, 
1020                  error );
1021
[2625d6d]1022        if( jd->streamhosts==NULL ) /* we're done here unless we have a proxy to try */
[17a6ee9]1023                imcb_file_canceled( tf->ic, tf->ft, error );
[2ff2076]1024
[a81d679]1025        /* MUST always return FALSE! */
[2ff2076]1026        return FALSE;
1027}
1028
1029/*
1030 * SOCKS5BYTESTREAM protocol for the sender
1031 */
1032gboolean jabber_bs_send_handshake( gpointer data, gint fd, b_input_condition cond )
1033{
1034        struct bs_transfer *bt = data;
1035        struct jabber_transfer *tf = bt->tf;
1036        short revents;
1037
1038        if ( !jabber_bs_poll( bt, fd, &revents ) )
1039                return FALSE;
1040       
1041        switch( bt->phase ) 
1042        {
1043        case BS_PHASE_CONNECT:
1044                {
1045                        struct sockaddr_storage clt_addr;
1046                        socklen_t ssize = sizeof( clt_addr );
1047                       
1048                        /* Connect */
1049
1050                        ASSERTSOCKOP( tf->fd = accept( fd, (struct sockaddr *) &clt_addr, &ssize ), "Accepting connection" );
1051
1052                        closesocket( fd );
1053                        fd = tf->fd;
1054                        sock_make_nonblocking( fd );
1055                       
1056                        bt->phase = BS_PHASE_CONNECTED;
1057
[d25ebea]1058                        bt->tf->watch_in = b_input_add( fd, B_EV_IO_READ, jabber_bs_send_handshake, bt );
[2ff2076]1059                        return FALSE;
1060                }
1061        case BS_PHASE_CONNECTED:
1062                {
1063                        int ret, have_noauth=FALSE;
1064                        struct {
1065                                unsigned char ver;
1066                                unsigned char method;
1067                        } socks5_auth_reply = { .ver = 5, .method = 0 };
1068                        struct {
1069                                unsigned char ver;
1070                                unsigned char nmethods;
1071                                unsigned char method;
1072                        } socks5_hello;
1073
1074                        if( !( ret = jabber_bs_peek( bt, &socks5_hello, sizeof( socks5_hello ) ) ) )
1075                                return FALSE;
1076
1077                        if( ret < sizeof( socks5_hello ) )
1078                                return TRUE;
1079
1080                        if( !( socks5_hello.ver == 5 ) ||
1081                            !( socks5_hello.nmethods >= 1 ) ||
1082                            !( socks5_hello.nmethods < 32 ) )
1083                                return jabber_bs_abort( bt, "Invalid auth request ver=%d nmethods=%d method=%d", socks5_hello.ver, socks5_hello.nmethods, socks5_hello.method );
1084
1085                        have_noauth = socks5_hello.method == 0;
1086
1087                        if( socks5_hello.nmethods > 1 )
1088                        {
1089                                char mbuf[32];
1090                                int i;
1091                                ASSERTSOCKOP( ret = recv( fd, mbuf, socks5_hello.nmethods - 1, 0 ) , "Receiving auth methods" );
1092                                if( ret < ( socks5_hello.nmethods - 1 ) )
1093                                        return jabber_bs_abort( bt, "Partial auth request");
1094                                for( i = 0 ; !have_noauth && ( i < socks5_hello.nmethods - 1 ) ; i ++ )
1095                                        if( mbuf[i] == 0 )
1096                                                have_noauth = TRUE;
1097                        }
1098                       
1099                        if( !have_noauth )
1100                                return jabber_bs_abort( bt, "Auth request didn't include no authentication" );
1101
1102                        ASSERTSOCKOP( send( fd, &socks5_auth_reply, sizeof( socks5_auth_reply ) , 0 ), "Sending auth reply" );
1103
1104                        bt->phase = BS_PHASE_REQUEST;
1105
1106                        return TRUE;
1107                }
1108        case BS_PHASE_REQUEST:
1109                {
1110                        struct socks5_message socks5_connect;
1111                        int msgsize = sizeof( struct socks5_message );
[4358b10]1112                        int ret;
[2ff2076]1113
[4358b10]1114                        if( !( ret = jabber_bs_peek( bt, &socks5_connect, msgsize ) ) )
[2ff2076]1115                                return FALSE;
1116
[4358b10]1117                        if( ret < msgsize )
1118                                return TRUE;
1119
[2ff2076]1120                        if( !( socks5_connect.ver == 5) ||
1121                            !( socks5_connect.cmdrep.cmd == 1 ) ||
1122                            !( socks5_connect.atyp == 3 ) ||
1123                            !(socks5_connect.addrlen == 40 ) )
1124                                return jabber_bs_abort( bt, "Invalid SOCKS5 Connect message (addrlen=%d, ver=%d, cmd=%d, atyp=%d)", socks5_connect.addrlen, socks5_connect.ver, socks5_connect.cmdrep.cmd, socks5_connect.atyp );
1125                        if( !( memcmp( socks5_connect.address, bt->pseudoadr, 40 ) == 0 ) )
1126                                return jabber_bs_abort( bt, "SOCKS5 Connect message contained wrong digest");
1127
1128                        socks5_connect.cmdrep.rep = 0;
1129
1130                        ASSERTSOCKOP( send( fd, &socks5_connect, msgsize, 0 ), "Sending connect reply" );
1131
1132                        bt->phase = BS_PHASE_REPLY;
1133
[dce3903]1134                        imcb_log( tf->ic, "File %s: SOCKS5 handshake successful! Transfer about to start...", tf->ft->file_name );
1135
[2ff2076]1136                        if( tf->accepted )
1137                        {
[dce3903]1138                                /* streamhost-used message came already in(possible?), let's start sending */
1139                                tf->ft->write_request( tf->ft );
[2ff2076]1140                        }
1141
1142                        tf->watch_in = 0;
1143                        return FALSE;
1144
1145                }
1146        default:
1147                /* BUG */
1148                imcb_log( bt->tf->ic, "BUG in file transfer code: undefined handshake phase" );
1149
1150                bt->tf->watch_in = 0;
1151                return FALSE;
1152        }
1153}
1154#undef ASSERTSOCKOP
Note: See TracBrowser for help on using the repository browser.