source: trunk/libtransmission/upnp.c @ 2343

Last change on this file since 2343 was 2343, checked in by joshe, 15 years ago

Change a couple functions to take an in_addr pointer instead of an in_addr.
Forward declare struct in_addr and include the relevant headers in the .c files where it's used.

  • Property svn:keywords set to Date Rev Author Id
File size: 42.8 KB
Line 
1/******************************************************************************
2 * $Id: upnp.c 2343 2007-07-14 16:29:21Z joshe $
3 *
4 * Copyright (c) 2006-2007 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <sys/types.h>
26#include <sys/socket.h>
27#include <netinet/in.h>
28#include <arpa/inet.h>
29#include <stdarg.h>
30
31#include "transmission.h"
32#include "http.h"
33#include "net.h"
34#include "upnp.h"
35#include "xml.h"
36
37/* uncomment this to log requests and responses to ~/transmission-upnp.log */
38/* #define VERBOSE_LOG */
39
40#define SSDP_ADDR               "239.255.255.250"
41#define SSDP_PORT               1900
42#define SSDP_TYPE               "upnp:rootdevice"
43#define SSDP_SUBTYPE            "ssdp:alive"
44#define SSDP_FIRST_DELAY        3750    /* 3 3/4 seconds */
45#define SSDP_MAX_DELAY          1800000 /* 30 minutes */
46#define SERVICE_TYPE_IP         "urn:schemas-upnp-org:service:WANIPConnection:1"
47#define SERVICE_TYPE_PPP        "urn:schemas-upnp-org:service:WANPPPConnection:1"
48#define SOAP_ENVELOPE           "http://schemas.xmlsoap.org/soap/envelope/"
49#define LOOP_DETECT_THRESHOLD   10 /* error on 10 add/get/del state changes */
50#define MAPPING_CHECK_INTERVAL  900000 /* 15 minutes */
51#define HTTP_REQUEST_INTERVAL   500 /* half a second */
52#define SOAP_METHOD_NOT_ALLOWED 405
53#define IGD_GENERIC_ERROR       500
54#define IGD_GENERIC_FAILED      501
55#define IGD_NO_MAPPING_EXISTS   714
56#define IGD_ADD_CONFLICT        718
57#define IGD_NO_DYNAMIC_MAPPING  725
58
59typedef struct tr_upnp_action_s
60{
61    char * name;
62    int    len;
63    struct { char * name; char * var; char dir; } * args;
64} tr_upnp_action_t;
65
66typedef struct tr_upnp_device_s
67{
68    char                    * id;
69    char                    * host;
70    char                    * root;
71    int                       port;
72    int                       ppp;
73    char                    * soap;
74    char                    * scpd;
75    int                       mappedport;
76    char                    * myaddr;
77#define UPNPDEV_STATE_ROOT              1
78#define UPNPDEV_STATE_SCPD              2
79#define UPNPDEV_STATE_READY             3
80#define UPNPDEV_STATE_ADD               4
81#define UPNPDEV_STATE_GET               5
82#define UPNPDEV_STATE_DEL               6
83#define UPNPDEV_STATE_MAPPED            7
84#define UPNPDEV_STATE_ERROR             8
85    uint8_t                   state;
86    uint8_t                   looping;
87    uint64_t                  lastrequest;
88    uint64_t                  lastcheck;
89    unsigned int              soapretry : 1;
90    tr_http_t               * http;
91    tr_upnp_action_t          getcmd;
92    tr_upnp_action_t          addcmd;
93    tr_upnp_action_t          delcmd;
94    struct tr_upnp_device_s * next;
95} tr_upnp_device_t;
96
97struct tr_upnp_s
98{
99    int                port;
100    int                infd;
101    int                outfd;
102    uint64_t           lastdiscover;
103    uint64_t           lastdelay;
104    unsigned int       active : 1;
105    unsigned int       discovering : 1;
106    tr_upnp_device_t * devices;
107};
108
109static int
110sendSSDP( int fd );
111static int
112mcastStart();
113static void
114killSock( int * sock );
115static void
116killHttp( tr_http_t ** http );
117static int
118watchSSDP( tr_upnp_device_t ** devices, int fd );
119static tr_tristate_t
120recvSSDP( int fd, char * buf, int * len );
121static int
122parseSSDP( char * buf, int len, tr_http_header_t * headers );
123static void
124deviceAdd( tr_upnp_device_t ** first, const char * id, int idLen,
125           const char * url, int urlLen );
126static void
127deviceRemove( tr_upnp_device_t ** prevptr );
128static int
129deviceStop( tr_upnp_device_t * dev );
130static int
131devicePulse( tr_upnp_device_t * dev, int port );
132static int
133devicePulseHttp( tr_upnp_device_t * dev,
134                 const char ** body, int * len );
135static tr_http_t *
136devicePulseGetHttp( tr_upnp_device_t * dev );
137static int
138parseRoot( const char * root, const char *buf, int len,
139           char ** soap, char ** scpd, int * ppp );
140static void
141addUrlbase( const char * base, char ** path );
142static int
143parseScpd( const char *buf, int len, tr_upnp_action_t * getcmd,
144           tr_upnp_action_t * addcmd, tr_upnp_action_t * delcmd );
145static int
146parseScpdArgs( const char * buf, const char * end,
147               tr_upnp_action_t * action, char dir );
148static int
149parseMapping( tr_upnp_device_t * dev, const char * buf, int len );
150static char *
151joinstrs( const char *, const char *, const char * );
152static tr_http_t *
153soapRequest( int retry, const char * host, int port, const char * path,
154             const char * type, tr_upnp_action_t * action, ... );
155static void
156actionSetup( tr_upnp_action_t * action, const char * name, int prealloc );
157static void
158actionFree( tr_upnp_action_t * action );
159static int
160actionAdd( tr_upnp_action_t * action, char * name, char * var,
161                      char dir );
162#define actionLookupVar( act, nam, len, dir ) \
163    ( actionLookup( (act), (nam), (len), (dir), 0 ) )
164#define actionLookupName( act, var, len, dir ) \
165    ( actionLookup( (act), (var), (len), (dir), 1 ) )
166static const char *
167actionLookup( tr_upnp_action_t * action, const char * key, int len,
168              char dir, int getname );
169
170#ifdef VERBOSE_LOG
171static FILE * vlog = NULL;
172#endif
173
174tr_upnp_t *
175tr_upnpInit()
176{
177    tr_upnp_t * upnp;
178
179    upnp = calloc( 1, sizeof( *upnp ) );
180    if( NULL == upnp )
181    {
182        return NULL;
183    }
184
185    upnp->infd     = -1;
186    upnp->outfd    = -1;
187
188#ifdef VERBOSE_LOG
189    if( NULL == vlog )
190    {
191        char path[MAX_PATH_LENGTH];
192        time_t stupid_api;
193        snprintf( path, sizeof path, "%s/transmission-upnp.log",
194                  tr_getHomeDirectory());
195        vlog = fopen( path, "a" );
196        stupid_api = time( NULL );
197        fprintf( vlog, "opened log at %s\n\n", ctime( &stupid_api ) );
198    }
199#endif
200
201    return upnp;
202}
203
204void
205tr_upnpStart( tr_upnp_t * upnp )
206{
207    if( !upnp->active )
208    {
209        tr_inf( "starting upnp" );
210        upnp->active = 1;
211        upnp->discovering = 1;
212        upnp->infd = mcastStart();
213        upnp->lastdiscover = 0;
214        upnp->lastdelay = SSDP_FIRST_DELAY / 2;
215    }
216}
217
218void
219tr_upnpStop( tr_upnp_t * upnp )
220{
221    if( upnp->active )
222    {
223        tr_inf( "stopping upnp" );
224        upnp->active = 0;
225        killSock( &upnp->infd );
226        killSock( &upnp->outfd );
227    }
228}
229
230int
231tr_upnpStatus( tr_upnp_t * upnp )
232{
233    tr_upnp_device_t * ii;
234    int                ret;
235
236    if( !upnp->active )
237    {
238        ret = ( NULL == upnp->devices ?
239                TR_NAT_TRAVERSAL_DISABLED : TR_NAT_TRAVERSAL_UNMAPPING );
240    }
241    else if( NULL == upnp->devices )
242    {
243        ret = TR_NAT_TRAVERSAL_NOTFOUND;
244    }
245    else
246    {
247        ret = TR_NAT_TRAVERSAL_MAPPING;
248        for( ii = upnp->devices; NULL != ii; ii = ii->next )
249        {
250            if( UPNPDEV_STATE_ERROR == ii->state )
251            {
252                ret = TR_NAT_TRAVERSAL_ERROR;
253            }
254            else if( 0 < ii->mappedport )
255            {
256                ret = TR_NAT_TRAVERSAL_MAPPED;
257                break;
258            }
259        }
260    }
261
262    return ret;
263}
264
265void
266tr_upnpForwardPort( tr_upnp_t * upnp, int port )
267{
268    tr_dbg( "upnp port changed from %i to %i", upnp->port, port );
269    upnp->port = port;
270}
271
272void
273tr_upnpRemoveForwarding( tr_upnp_t * upnp )
274{
275    tr_dbg( "upnp port unset" );
276    upnp->port = 0;
277}
278
279void
280tr_upnpClose( tr_upnp_t * upnp )
281{
282    tr_upnpStop( upnp );
283
284    while( NULL != upnp->devices )
285    {
286        deviceRemove( &upnp->devices );
287    }
288
289    free( upnp );
290
291#ifdef VERBOSE_LOG
292    if( NULL != vlog )
293    {
294        fflush( vlog );
295    }
296#endif
297
298}
299
300void
301tr_upnpPulse( tr_upnp_t * upnp )
302{
303    tr_upnp_device_t ** ii;
304
305    if( upnp->active )
306    {
307        /* pulse on all known devices */
308        upnp->discovering = 1;
309        for( ii = &upnp->devices; NULL != *ii; ii = &(*ii)->next )
310        {
311            if( devicePulse( *ii, upnp->port ) )
312            {
313                upnp->discovering = 0;
314            }
315        }
316
317        /* send an SSDP discover message */
318        if( upnp->discovering &&
319            upnp->lastdelay + upnp->lastdiscover < tr_date() )
320        {
321            upnp->outfd = sendSSDP( upnp->outfd );
322            upnp->lastdiscover = tr_date();
323            upnp->lastdelay = MIN( upnp->lastdelay * 2, SSDP_MAX_DELAY );
324        }
325
326        /* try to receive SSDP messages */
327        watchSSDP( &upnp->devices, upnp->infd );
328        if( watchSSDP( &upnp->devices, upnp->outfd ) )
329        {
330            killSock( &upnp->outfd );
331        }
332    }
333    else
334    {
335        /* delete all mappings then delete devices */
336        ii = &upnp->devices;
337        while( NULL != *ii )
338        {
339            if( deviceStop( *ii ) )
340            {
341                deviceRemove( ii );
342            }
343            else
344            {
345                devicePulse( *ii, 0 );
346                ii = &(*ii)->next;
347            }
348        }
349    }
350}
351
352static int
353sendSSDP( int fd )
354{
355    char buf[102];
356    int  len;
357    struct sockaddr_in sin;
358
359    if( 0 > fd )
360    {
361        fd = tr_netBindUDP( 0 );
362        if( 0 > fd )
363        {
364            return -1;
365        }
366    }
367
368    tr_dbg( "sending upnp ssdp discover message" );
369
370    len = snprintf( buf, sizeof( buf ),
371                    "M-SEARCH * HTTP/1.1\r\n"
372                    "Host: %s:%i\r\n"
373                    "Man: \"ssdp:discover\"\r\n"
374                    "ST: %s\r\n"
375                    "MX: 3\r\n"
376                    "\r\n",
377                    SSDP_ADDR, SSDP_PORT, SSDP_TYPE );
378
379    /* if this assertion ever fails then just increase the size of buf */
380    assert( (int) sizeof( buf ) > len );
381
382    memset( &sin, 0, sizeof( sin ) );
383    sin.sin_family      = AF_INET;
384    sin.sin_addr.s_addr = inet_addr( SSDP_ADDR );
385    sin.sin_port        = htons( SSDP_PORT );
386
387#ifdef VERBOSE_LOG
388    fprintf( vlog, "send ssdp message, %i bytes:\n", len );
389    fwrite( buf, 1, len, vlog );
390    fputs( "\n\n", vlog );
391#endif
392
393    if( 0 > sendto( fd, buf, len, 0,
394                    (struct sockaddr*) &sin, sizeof( sin ) ) )
395    {
396        if( EAGAIN != errno )
397        {
398            tr_err( "Could not send SSDP discover message (%s)",
399                    strerror( errno ) );
400        }
401        killSock( &fd );
402        return -1;
403    }
404
405    return fd;
406}
407
408static int
409mcastStart()
410{
411    int fd;
412    struct in_addr addr;
413
414    addr.s_addr = inet_addr( SSDP_ADDR );
415    fd = tr_netMcastOpen( SSDP_PORT, &addr );
416    if( 0 > fd )
417    {
418        return -1;
419    }
420
421    return fd;
422}
423
424static void
425killSock( int * sock )
426{
427    if( 0 <= *sock )
428    {
429        tr_netClose( *sock );
430        *sock = -1;
431    }
432}
433
434static void
435killHttp( tr_http_t ** http )
436{
437    tr_httpClose( *http );
438    *http = NULL;
439}
440
441static int
442watchSSDP( tr_upnp_device_t ** devices, int fd )
443{
444    /* XXX what if we get a huge SSDP packet? */
445    char buf[512];
446    int len;
447    tr_http_header_t hdr[] = {
448        /* first one must be type and second must be subtype */
449        { NULL,            NULL, 0 },
450        { "NTS",           NULL, 0 },
451        /* XXX should probably look at this
452           { "Cache-control", NULL, 0 }, */
453        { "Location",      NULL, 0 },
454        { "USN",           NULL, 0 },
455        { NULL,            NULL, 0 }
456    };
457    enum { OFF_TYPE = 0, OFF_SUBTYPE, OFF_LOC, OFF_ID };
458    int ret;
459
460    if( 0 > fd )
461    {
462        return 0;
463    }
464
465    ret = 0;
466    for(;;)
467    {
468        len = sizeof( buf );
469        switch( recvSSDP( fd, buf, &len ) )
470        {
471            case TR_NET_WAIT:
472                return ret;
473            case TR_NET_ERROR:
474                return 1;
475            case TR_NET_OK:
476                ret = 1;
477                if( parseSSDP( buf, len, hdr ) &&
478                    NULL != hdr[OFF_LOC].data &&
479                    NULL != hdr[OFF_ID].data )
480                {
481                    deviceAdd( devices, hdr[OFF_ID].data, hdr[OFF_ID].len,
482                               hdr[OFF_LOC].data, hdr[OFF_LOC].len );
483                }
484        }
485    }
486}
487
488static tr_tristate_t
489recvSSDP( int fd, char * buf, int * len )
490{
491    if( 0 > fd )
492    {
493        return TR_NET_ERROR;
494    }
495
496    *len = tr_netRecv( fd, ( uint8_t * ) buf, *len );
497    if( TR_NET_BLOCK & *len )
498    {
499        return TR_NET_WAIT;
500    }
501    else if( TR_NET_CLOSE & *len )
502    {
503        tr_err( "Could not receive SSDP message (%s)", strerror( errno ) );
504        return TR_NET_ERROR;
505    }
506    else
507    {
508#ifdef VERBOSE_LOG
509        fprintf( vlog, "receive ssdp message, %i bytes:\n", *len );
510        fwrite( buf, 1, *len, vlog );
511        fputs( "\n\n", vlog );
512#endif
513        return TR_NET_OK;
514    }
515}
516
517static int
518parseSSDP( char * buf, int len, tr_http_header_t * hdr )
519{
520    char *method, *uri, *body;
521    int code;
522
523    body = NULL;
524    /* check for an HTTP NOTIFY request */
525    if( 0 <= tr_httpRequestType( buf, len, &method, &uri ) )
526    {
527        if( 0 == tr_strcasecmp( method, "NOTIFY" ) && 0 == strcmp( uri, "*" ) )
528        {
529            hdr[0].name = "NT";
530            body = tr_httpParse( buf, len, hdr );
531            if( NULL == hdr[1].name ||
532                0 != tr_strncasecmp( SSDP_SUBTYPE, hdr[1].data, hdr[1].len ) )
533            {
534                body = NULL;
535            }
536            else
537            {
538                tr_dbg( "found upnp ssdp notify request" );
539            }
540        }
541        free( method );
542        free( uri );
543    }
544    else
545    {
546        /* check for a response to our HTTP M-SEARCH request */
547        code = tr_httpResponseCode( buf, len );
548        if( TR_HTTP_STATUS_OK( code ) )
549        {
550            hdr[0].name = "ST";
551            body = tr_httpParse( buf, len, hdr );
552            if( NULL != body )
553            {
554                tr_dbg( "found upnp ssdp m-search response" );
555            }
556        }
557    }
558
559    /* did we find enough information to be useful? */
560    if( NULL != body )
561    {
562        /* the first header is the type */
563        if( NULL != hdr[0].data &&
564            0 == tr_strncasecmp( SSDP_TYPE, hdr[0].data, hdr[0].len ) )
565        {
566            return 1;
567        }
568    }
569
570    return 0;
571}
572
573static void
574deviceAdd( tr_upnp_device_t ** first, const char * id, int idLen,
575           const char * url, int urlLen )
576{
577    tr_upnp_device_t * ii;
578
579    for( ii = *first; NULL != ii; ii = ii->next )
580    {
581        if( 0 == tr_strncasecmp( ii->id, id, idLen ) )
582        {
583            /* this device may have gone away and came back, recheck it */
584            ii->lastcheck = 0;
585            return;
586        }
587    }
588
589    ii = malloc( sizeof( *ii ) );
590    if( NULL == ii )
591    {
592        return;
593    }
594    memset( ii, 0, sizeof( *ii ) );
595    if( tr_httpParseUrl( url, urlLen, &ii->host, &ii->port, &ii->root ) )
596    {
597        tr_err( "Invalid HTTP URL from UPnP" );
598        free( ii );
599        return;
600    }
601    ii->id = tr_strndup( id, idLen );
602    ii->state = UPNPDEV_STATE_ROOT;
603    actionSetup( &ii->getcmd, "GetSpecificPortMappingEntry", 8 );
604    actionSetup( &ii->addcmd, "AddPortMapping", 8 );
605    actionSetup( &ii->delcmd, "DeletePortMapping", 3 );
606    ii->next = *first;
607    *first = ii;
608
609    tr_inf( "new upnp device %s, port %i, path %s",
610            ii->host, ii->port, ii->root );
611}
612
613static void
614deviceRemove( tr_upnp_device_t ** prevptr )
615{
616    tr_upnp_device_t * dead;
617
618    dead = *prevptr;
619    *prevptr = dead->next;
620
621    tr_inf( "forgetting upnp device %s", dead->host );
622
623    free( dead->id );
624    free( dead->host );
625    free( dead->root );
626    free( dead->soap );
627    free( dead->scpd );
628    free( dead->myaddr );
629    if( NULL != dead->http )
630    {
631        killHttp( &dead->http );
632    }
633    actionFree( &dead->getcmd );
634    actionFree( &dead->addcmd );
635    actionFree( &dead->delcmd );
636    free( dead );
637}
638
639static int
640deviceStop( tr_upnp_device_t * dev )
641{
642    switch( dev->state )
643    {
644        case UPNPDEV_STATE_READY:
645        case UPNPDEV_STATE_ERROR:
646            return 1;
647        case UPNPDEV_STATE_MAPPED:
648            tr_dbg( "upnp device %s: stopping upnp, state mapped -> delete",
649                dev->host );
650            dev->state = UPNPDEV_STATE_DEL;
651            return 0;
652        default:
653            return 0;
654    }
655}
656
657static int
658devicePulse( tr_upnp_device_t * dev, int port )
659{
660    const char * body;
661    int          len, code;
662    uint8_t      laststate;
663
664    switch( dev->state )
665    {
666        case UPNPDEV_STATE_READY:
667            if( 0 < port )
668            {
669                tr_dbg( "upnp device %s: want mapping, state ready -> get",
670                        dev->host );
671                dev->mappedport = port;
672                dev->state = UPNPDEV_STATE_GET;
673                break;
674            }
675            return 1;
676        case UPNPDEV_STATE_MAPPED:
677            if( port != dev->mappedport )
678            {
679                tr_dbg( "upnp device %s: change mapping, "
680                        "state mapped -> delete", dev->host );
681                dev->state = UPNPDEV_STATE_DEL;
682                break;
683            }
684            if( tr_date() > dev->lastcheck + MAPPING_CHECK_INTERVAL )
685            {
686                tr_dbg( "upnp device %s: check mapping, "
687                        "state mapped -> get", dev->host );
688                dev->state = UPNPDEV_STATE_GET;
689            }
690            return 1;
691        case UPNPDEV_STATE_ERROR:
692            return 0;
693    }
694
695    /* gcc can be pretty annoying about it's warnings sometimes */
696    len = 0;
697    body = NULL;
698
699    code = devicePulseHttp( dev, &body, &len );
700    if( 0 > code )
701    {
702        return 1;
703    }
704
705    if( LOOP_DETECT_THRESHOLD <= dev->looping )
706    {
707        tr_dbg( "upnp device %s: loop detected, state %hhu -> error",
708                dev->host, dev->state );
709        dev->state = UPNPDEV_STATE_ERROR;
710        dev->looping = 0;
711        killHttp( &dev->http );
712        return 1;
713    }
714
715    laststate = dev->state;
716    dev->state = UPNPDEV_STATE_ERROR;
717    switch( laststate ) 
718    {
719        case UPNPDEV_STATE_ROOT:
720            if( !TR_HTTP_STATUS_OK( code ) )
721            {
722                tr_dbg( "upnp device %s: fetch root failed with http code %i",
723                        dev->host, code );
724            }
725            else if( parseRoot( dev->root, body, len,
726                                &dev->soap, &dev->scpd, &dev->ppp ) )
727            {
728                tr_dbg( "upnp device %s: parse root failed", dev->host );
729            }
730            else
731            {
732                tr_dbg( "upnp device %s: found scpd \"%s\" and soap \"%s\"",
733                        dev->root, dev->scpd, dev->soap );
734                tr_dbg( "upnp device %s: parsed root, state root -> scpd",
735                        dev->host );
736                dev->state = UPNPDEV_STATE_SCPD;
737            }
738            break;
739
740        case UPNPDEV_STATE_SCPD:
741            if( !TR_HTTP_STATUS_OK( code ) )
742            {
743                tr_dbg( "upnp device %s: fetch scpd failed with http code %i",
744                        dev->host, code );
745            }
746            else if( parseScpd( body, len, &dev->getcmd,
747                                &dev->addcmd, &dev->delcmd ) )
748            {
749                tr_dbg( "upnp device %s: parse scpd failed", dev->host );
750            }
751            else
752            {
753                tr_dbg( "upnp device %s: parsed scpd, state scpd -> ready",
754                        dev->host );
755                dev->state = UPNPDEV_STATE_READY;
756                dev->looping = 0;
757            }
758            break;
759
760        case UPNPDEV_STATE_ADD:
761            dev->looping++;
762            if( IGD_ADD_CONFLICT == code )
763            {
764                tr_dbg( "upnp device %s: add conflict, state add -> delete",
765                        dev->host );
766                dev->state = UPNPDEV_STATE_DEL;
767            }
768            else if( TR_HTTP_STATUS_OK( code ) ||
769                     IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
770            {
771                tr_dbg( "upnp device %s: add attempt, state add -> get",
772                        dev->host );
773                dev->state = UPNPDEV_STATE_GET;
774            }
775            else
776            {
777                tr_dbg( "upnp device %s: add failed with http code %i",
778                        dev->host, code );
779            }
780            break;
781
782        case UPNPDEV_STATE_GET:
783            dev->looping++;
784            if( TR_HTTP_STATUS_OK( code ) )
785            {
786                switch( parseMapping( dev, body, len ) )
787                {
788                    case -1:
789                        break;
790                    case 0:
791                        tr_dbg( "upnp device %s: invalid mapping, "
792                                "state get -> delete", dev->host );
793                        dev->state = UPNPDEV_STATE_DEL;
794                        break;
795                    case 1:
796                        tr_dbg( "upnp device %s: good mapping, "
797                                "state get -> mapped", dev->host );
798                        dev->state = UPNPDEV_STATE_MAPPED;
799                        dev->looping = 0;
800                        dev->lastcheck = tr_date();
801                        tr_inf( "upnp successful for port %i",
802                                dev->mappedport );
803                        break;
804                    default:
805                        assert( 0 );
806                        break;
807                }
808            }
809            else if( IGD_NO_MAPPING_EXISTS == code ||
810                     IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
811            {
812                tr_dbg( "upnp device %s: no mapping, state get -> add",
813                        dev->host );
814                dev->state = UPNPDEV_STATE_ADD;
815            }
816            else
817            {
818                tr_dbg( "upnp device %s: get failed with http code %i",
819                        dev->host, code );
820            }
821            break;
822
823        case UPNPDEV_STATE_DEL:
824            dev->looping++;
825            if( TR_HTTP_STATUS_OK( code ) || IGD_NO_MAPPING_EXISTS == code ||
826                IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
827            {
828                tr_dbg( "upnp device %s: deleted, state delete -> ready",
829                        dev->host );
830                dev->state = UPNPDEV_STATE_READY;
831                dev->looping = 0;
832            }
833            else
834            {
835                tr_dbg( "upnp device %s: del failed with http code %i",
836                        dev->host, code );
837            }
838            break;
839
840        default:
841            assert( 0 );
842            break;
843    }
844
845    dev->lastrequest = tr_date();
846    killHttp( &dev->http );
847
848    if( UPNPDEV_STATE_ERROR == dev->state )
849    {
850        tr_dbg( "upnp device %s: error, state %hhu -> error",
851                dev->host, laststate );
852        return 0;
853    }
854
855    return 1;
856}
857
858static tr_http_t *
859makeHttp( int method, const char * host, int port, const char * path )
860{
861    if( tr_httpIsUrl( path, -1 ) )
862    {
863        return tr_httpClientUrl( method, "%s", path );
864    }
865    else
866    {
867        return tr_httpClient( method, host, port, "%s", path );
868    }
869}
870
871static tr_http_t *
872devicePulseGetHttp( tr_upnp_device_t * dev )
873{
874    tr_http_t  * ret;
875    char         numstr[6];
876    const char * type;
877#ifdef VERBOSE_LOG
878    const char * body;
879    int          len;
880#endif
881
882    ret = NULL;
883    switch( dev->state ) 
884    {
885        case UPNPDEV_STATE_ROOT:
886            if( !dev->soapretry )
887            {
888                ret = makeHttp( TR_HTTP_GET, dev->host, dev->port, dev->root );
889            }
890            break;
891        case UPNPDEV_STATE_SCPD:
892            if( !dev->soapretry )
893            {
894                ret = makeHttp( TR_HTTP_GET, dev->host, dev->port, dev->scpd );
895            }
896            break;
897        case UPNPDEV_STATE_ADD:
898            if( NULL == dev->myaddr )
899            {
900                ret = NULL;
901                break;
902            }
903            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
904            type = ( dev->ppp ? SERVICE_TYPE_PPP : SERVICE_TYPE_IP );
905            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
906                               type, &dev->addcmd,
907                               "PortMappingEnabled", "1",
908                               "PortMappingLeaseDuration", "0",
909                               "RemoteHost", "",
910                               "ExternalPort", numstr,
911                               "InternalPort", numstr,
912                               "PortMappingProtocol", "TCP",
913                               "InternalClient", dev->myaddr,
914                               "PortMappingDescription", "Added by " TR_NAME,
915                               NULL );
916            break;
917        case UPNPDEV_STATE_GET:
918            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
919            type = ( dev->ppp ? SERVICE_TYPE_PPP : SERVICE_TYPE_IP );
920            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
921                               type, &dev->getcmd,
922                               "RemoteHost", "",
923                               "ExternalPort", numstr,
924                               "PortMappingProtocol", "TCP",
925                               NULL );
926            break;
927        case UPNPDEV_STATE_DEL:
928            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
929            type = ( dev->ppp ? SERVICE_TYPE_PPP : SERVICE_TYPE_IP );
930            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
931                               type, &dev->delcmd,
932                               "RemoteHost", "",
933                               "ExternalPort", numstr,
934                               "PortMappingProtocol", "TCP",
935                               NULL );
936            break;
937        default:
938            assert( 0 );
939            break;
940    }
941
942#ifdef VERBOSE_LOG
943    if( NULL != ret )
944    {
945        tr_httpGetHeaders( ret, &body, &len );
946        fprintf( vlog, "send http message, %i bytes (headers):\n", len );
947        fwrite( body, 1, len, vlog );
948        tr_httpGetBody( ret, &body, &len );
949        fprintf( vlog, "\n\nsend http message, %i bytes (body):\n", len );
950        fwrite( body, 1, len, vlog );
951        fputs( "\n\n", vlog );
952    }
953#endif
954
955    return ret;
956}
957
958static int
959devicePulseHttp( tr_upnp_device_t * dev,
960                 const char ** body, int * len )
961{
962    const char * headers;
963    int          hlen, code;
964
965    if( NULL == dev->http )
966    {
967        if( tr_date() < dev->lastrequest + HTTP_REQUEST_INTERVAL )
968        {
969            return -1;
970        }
971        dev->lastrequest = tr_date();
972        dev->http = devicePulseGetHttp( dev );
973        if( NULL == dev->http )
974        {
975            tr_dbg( "upnp device %s: http init failed, state %hhu -> error",
976                    dev->host, dev->state );
977            dev->state = UPNPDEV_STATE_ERROR;
978            dev->soapretry = 0;
979            return -1;
980        }
981    }
982
983    if( NULL == dev->myaddr )
984    {
985        dev->myaddr = tr_httpWhatsMyAddress( dev->http );
986    }
987
988    switch( tr_httpPulse( dev->http, &headers, &hlen ) )
989    {
990        case TR_NET_OK:
991#ifdef VERBOSE_LOG
992    fprintf( vlog, "receive http message, %i bytes:\n", hlen );
993    fwrite( headers, 1, hlen, vlog );
994    fputs( "\n\n", vlog );
995#endif
996            code = tr_httpResponseCode( headers, hlen );
997            if( SOAP_METHOD_NOT_ALLOWED == code && !dev->soapretry )
998            {
999                dev->soapretry = 1;
1000                killHttp( &dev->http );
1001                break;
1002            }
1003            dev->soapretry = 0;
1004            *body = tr_httpParse( headers, hlen, NULL );
1005            *len = ( NULL == body ? 0 : hlen - ( *body - headers ) );
1006            return code;
1007        case TR_NET_ERROR:
1008            killHttp( &dev->http );
1009            if( dev->soapretry )
1010            {
1011                tr_dbg( "upnp device %s: http pulse failed, state %hhu -> error",
1012                        dev->host, dev->state );
1013                dev->state = UPNPDEV_STATE_ERROR;
1014                dev->soapretry = 0;
1015            }
1016            else
1017            {
1018                dev->soapretry = 1;
1019            }
1020            break;
1021        case TR_NET_WAIT:
1022            break;
1023    }
1024
1025    return -1;
1026}
1027
1028static int
1029parseRoot( const char * root, const char *buf, int len,
1030           char ** soap, char ** scpd, int * ppp )
1031{
1032    const char * end, * ii, * jj, * kk, * urlbase;
1033    char       * basedup;
1034
1035    *soap = NULL;
1036    *scpd = NULL;
1037    end = buf + len;
1038
1039    buf = tr_xmlFindTagContents( buf, end, "root" );
1040    urlbase = tr_xmlFindTag( buf, end, "urlBase" );
1041    urlbase = tr_xmlTagContents( urlbase, end );
1042    buf = tr_xmlFindTagContents( buf, end, "device" );
1043    if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
1044            "urn:schemas-upnp-org:device:InternetGatewayDevice:1", 1 ) )
1045    {
1046        return 1;
1047    }
1048    buf = tr_xmlFindTag( buf, end, "deviceList" );
1049    ii = tr_xmlTagContents( buf, end );
1050    for( ; NULL != ii; ii = tr_xmlSkipTag( ii, end ) )
1051    {
1052        ii = tr_xmlFindTag( ii, end, "device" );
1053        buf = tr_xmlTagContents( ii, end );
1054        if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
1055                "urn:schemas-upnp-org:device:WANDevice:1", 1 ) )
1056        {
1057            continue;
1058        }
1059        buf = tr_xmlFindTag( buf, end, "deviceList" );
1060        jj = tr_xmlTagContents( buf, end );
1061        for( ; NULL != jj; jj = tr_xmlSkipTag( jj, end ) )
1062        {
1063            jj = tr_xmlFindTag( jj, end, "device" );
1064            buf = tr_xmlTagContents( jj, end );
1065            if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
1066                    "urn:schemas-upnp-org:device:WANConnectionDevice:1", 1 ) )
1067            {
1068                continue;
1069            }
1070            buf = tr_xmlFindTag( buf, end, "serviceList" );
1071            kk = tr_xmlTagContents( buf, end );
1072            for( ; NULL != kk; kk = tr_xmlSkipTag( kk, end ) )
1073            {
1074                kk = tr_xmlFindTag( kk, end, "service" );
1075                buf = tr_xmlTagContents( kk, end );
1076                if( !tr_xmlFindTagVerifyContents( buf, end, "serviceType",
1077                                                  SERVICE_TYPE_IP, 1 ) )
1078                {
1079                    *soap = tr_xmlDupTagContents( buf, end, "controlURL");
1080                    *scpd = tr_xmlDupTagContents( buf, end, "SCPDURL");
1081                    *ppp  = 0;
1082                    break;
1083                }
1084                /* XXX we should save all services of both types and
1085                   try adding mappings for each in turn */
1086                else if( !tr_xmlFindTagVerifyContents( buf, end, "serviceType",
1087                                                       SERVICE_TYPE_PPP, 1 ) )
1088                {
1089                    *soap = tr_xmlDupTagContents( buf, end, "controlURL");
1090                    *scpd = tr_xmlDupTagContents( buf, end, "SCPDURL");
1091                    *ppp  = 1;
1092                    break;
1093                }
1094            }
1095        }
1096    }
1097
1098    if( NULL == urlbase )
1099    {
1100        basedup = strrchr( root, '/' );
1101        assert( NULL != basedup );
1102        basedup = tr_strndup( root, basedup - root + 1 );
1103    }
1104    else
1105    {
1106        basedup = tr_xmlDupContents( urlbase, end );
1107    }
1108    addUrlbase( basedup, soap );
1109    addUrlbase( basedup, scpd );
1110    free( basedup );
1111
1112    if( NULL != *soap && NULL != *scpd )
1113    {
1114        return 0;
1115    }
1116
1117    return 1;
1118}
1119
1120static void
1121addUrlbase( const char * base, char ** path )
1122{
1123    const char * middle;
1124    int          len;
1125    char       * joined;
1126
1127    if( NULL == base || NULL == *path ||
1128        '/' == **path || tr_httpIsUrl( *path, -1 ) )
1129    {
1130        return;
1131    }
1132
1133    len = strlen( base );
1134    middle = ( 0 >= len || '/' != base[len-1] ? "/" : "" );
1135    joined = joinstrs( base, middle, *path );
1136    free( *path );
1137    *path = joined;
1138}
1139
1140static int
1141parseScpd( const char * buf, int len, tr_upnp_action_t * getcmd,
1142           tr_upnp_action_t * addcmd, tr_upnp_action_t * delcmd )
1143{
1144    const char * end, * next, * sub, * name;
1145
1146    end = buf + len;
1147    next = buf;
1148
1149    next = tr_xmlFindTagContents( next, end, "scpd" );
1150    next = tr_xmlFindTagContents( next, end, "actionList" );
1151
1152    while( NULL != next )
1153    {
1154        next = tr_xmlFindTag( next, end, "action" );
1155        sub = tr_xmlTagContents( next, end );
1156        name = tr_xmlFindTagContents( sub, end, "name" );
1157        sub = tr_xmlFindTagContents( sub, end, "argumentList" );
1158        if( !tr_xmlVerifyContents( name, end, getcmd->name, 1 ) )
1159        {
1160            if( parseScpdArgs( sub, end, getcmd, 'i' ) ||
1161                parseScpdArgs( sub, end, getcmd, 'o' ) )
1162            {
1163                return 1;
1164            }
1165        }
1166        else if( !tr_xmlVerifyContents( name, end, addcmd->name, 1 ) )
1167        {
1168            if( parseScpdArgs( sub, end, addcmd, 'i' ) )
1169            {
1170                return 1;
1171            }
1172        }
1173        else if( !tr_xmlVerifyContents( name, end, delcmd->name, 1 ) )
1174        {
1175            if( parseScpdArgs( sub, end, delcmd, 'i' ) )
1176            {
1177                return 1;
1178            }
1179        }
1180        next = tr_xmlSkipTag( next, end );
1181    }
1182
1183    return 0;
1184}
1185
1186static int
1187parseScpdArgs( const char * buf, const char * end,
1188               tr_upnp_action_t * action, char dir )
1189{
1190    const char * sub, * which;
1191    char       * name, * var;
1192
1193    assert( 'i' == dir || 'o' == dir );
1194    which = ( 'i' == dir ? "in" : "out" );
1195
1196    while( NULL != buf )
1197    {
1198        sub = tr_xmlFindTagContents( buf, end, "argument" );
1199        if( !tr_xmlFindTagVerifyContents( sub, end, "direction", which, 1 ) )
1200        {
1201            name = tr_xmlDupTagContents( sub, end, "name" );
1202            var = tr_xmlDupTagContents( sub, end, "relatedStateVariable" );
1203            if( NULL == name || NULL == var )
1204            {
1205                free( name );
1206                free( var );
1207            }
1208            else if( actionAdd( action, name, var, dir ) )
1209            {
1210                return 1;
1211            }
1212        }
1213        buf = tr_xmlSkipTag( buf, end );
1214    }
1215
1216    return 0;
1217}
1218
1219static int
1220parseMapping( tr_upnp_device_t * dev, const char * buf, int len )
1221{
1222    const char * end, * down, * next, * var;
1223    int          varlen, pret, cret, eret;
1224    char       * val;
1225
1226    assert( 0 < dev->mappedport );
1227
1228    if( NULL == dev->myaddr )
1229    {
1230        return -1;
1231    }
1232
1233    pret = -1;
1234    cret = -1;
1235    eret = -1;
1236
1237    end = buf + len;
1238    down = buf;
1239    down = tr_xmlFindTagContents( down, end, "Envelope" );
1240    down = tr_xmlFindTagContents( down, end, "Body" );
1241    down = tr_xmlFindTagContents( down, end,
1242                                  "GetSpecificPortMappingEntryResponse" );
1243
1244    next = down;
1245    while( NULL != next )
1246    {
1247        var = tr_xmlTagName( next, end, &varlen );
1248        var = actionLookupVar( &dev->getcmd, var, varlen, 'o' );
1249        if( NULL != var )
1250        {
1251            val = tr_xmlDupContents( tr_xmlTagContents( next, end ), end );
1252            if( 0 == tr_strcasecmp( "InternalPort", var ) )
1253            {
1254                pret = ( strtol( val, NULL, 10 ) == dev->mappedport ? 1 : 0 );
1255            }
1256            else if( 0 == tr_strcasecmp( "InternalClient", var ) )
1257            {
1258                cret = ( 0 == strcmp( dev->myaddr, val ) ? 1 : 0 );
1259            }
1260            else if( 0 == tr_strcasecmp( "PortMappingEnabled", var ) )
1261            {
1262                eret = ( 0 == strcmp( "1", val ) ? 1 : 0 );
1263            }
1264            free( val );
1265        }
1266        next = tr_xmlSkipTag( next, end );
1267    }
1268
1269    return MIN( MIN( pret, cret), eret );
1270}
1271
1272static char *
1273joinstrs( const char * first, const char * delim, const char * second )
1274{
1275    char * ret;
1276    int    len1, len2, len3;
1277
1278    len1 = strlen( first );
1279    len2 = strlen( delim );
1280    len3 = strlen( second );
1281    ret = malloc( len1 + len2 + len3 + 1 );
1282    if( NULL == ret )
1283    {
1284        return NULL;
1285    }
1286
1287    memcpy( ret, first, len1 );
1288    memcpy( ret + len1, delim, len2 );
1289    memcpy( ret + len1 + len2, second, len3 );
1290    ret[len1 + len2 + len3] = '\0';
1291
1292    return ret;
1293}
1294
1295static tr_http_t *
1296soapRequest( int retry, const char * host, int port, const char * path,
1297             const char * type, tr_upnp_action_t * action, ... )
1298{
1299    tr_http_t  * http;
1300    va_list      ap;
1301    const char * name, * value;
1302    int          method;
1303    char       * actstr;
1304
1305    method = ( retry ? TR_HTTP_M_POST : TR_HTTP_POST );
1306    http = makeHttp( method, host, port, path );
1307    if( NULL != http )
1308    {
1309        tr_httpAddHeader( http, "Content-type",
1310                          "text/xml; encoding=\"utf-8\"" );
1311        actstr = NULL;
1312        asprintf( &actstr, "\"%s#%s\"", type, action->name );
1313        tr_httpAddHeader( http, "SOAPAction", actstr );
1314        free( actstr );
1315        if( retry )
1316        {
1317            tr_httpAddHeader( http, "Man", "\"" SOAP_ENVELOPE "\"" );
1318        }
1319        tr_httpAddBody( http, 
1320"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
1321"<s:Envelope"
1322"    xmlns:s=\"" SOAP_ENVELOPE "\""
1323"    s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
1324"  <s:Body>"
1325"    <u:%s xmlns:u=\"%s\">", action->name, type );
1326
1327        va_start( ap, action );
1328        do
1329        {
1330            name = va_arg( ap, const char * );
1331            value = NULL;
1332            name = actionLookupName( action, name, -1, 'i' );
1333            if( NULL != name )
1334            {
1335                value = va_arg( ap, const char * );
1336                if( NULL != value )
1337                {
1338                    tr_httpAddBody( http,
1339"      <%s>%s</%s>", name, value, name );
1340                }
1341                else 
1342                {
1343                    tr_httpAddBody( http,
1344"      <%s></%s>", name, name );
1345                }
1346            }
1347        }
1348        while( NULL != name && NULL != value );
1349        va_end( ap );
1350
1351        tr_httpAddBody( http,
1352"    </u:%s>"
1353"  </s:Body>"
1354"</s:Envelope>", action->name );
1355    }
1356
1357    return http;
1358}
1359
1360static void
1361actionSetup( tr_upnp_action_t * action, const char * name, int prealloc )
1362{
1363    action->name = strdup( name );
1364    assert( NULL == action->args );
1365    action->args = malloc( sizeof( *action->args ) * prealloc );
1366    memset( action->args, 0, sizeof( *action->args ) * prealloc );
1367    action->len = prealloc;
1368}
1369
1370static void
1371actionFree( tr_upnp_action_t * act )
1372{
1373    free( act->name );
1374    while( 0 < act->len )
1375    {
1376        act->len--;
1377        free( act->args[act->len].name );
1378        free( act->args[act->len].var );
1379    }
1380    free( act->args );
1381}
1382
1383static int
1384actionAdd( tr_upnp_action_t * act, char * name, char * var, char dir )
1385{
1386    int    ii;
1387    void * newbuf;
1388
1389    assert( 'i' == dir || 'o' == dir );
1390
1391    ii = 0;
1392    while( ii < act->len && NULL != act->args[ii].name )
1393    {
1394        ii++;
1395    }
1396
1397    if( ii == act->len )
1398    {
1399        newbuf = realloc( act->args, sizeof( *act->args ) * ( act->len + 1 ) );
1400        if( NULL == newbuf )
1401        {
1402            return 1;
1403        }
1404        act->args = newbuf;
1405        act->len++;
1406    }
1407
1408    act->args[ii].name = name;
1409    act->args[ii].var = var;
1410    act->args[ii].dir = dir;
1411
1412    return 0;
1413}
1414
1415static const char *
1416actionLookup( tr_upnp_action_t * act, const char * key, int len,
1417              char dir, int getname )
1418{
1419    int ii;
1420
1421    assert( 'i' == dir || 'o' == dir );
1422
1423    if( NULL == key || 0 == len )
1424    {
1425        return NULL;
1426    }
1427    if( 0 > len )
1428    {
1429        len = strlen( key );
1430    }
1431
1432    for( ii = 0; ii < act->len; ii++ )
1433    {
1434        if( NULL != act->args[ii].name && dir == act->args[ii].dir )
1435        {
1436            if( !getname &&
1437                0 == tr_strncasecmp( act->args[ii].name, key, len ) )
1438            {
1439                return act->args[ii].var;
1440            }
1441            else if( getname &&
1442                     0 == tr_strncasecmp( act->args[ii].var, key, len ) )
1443            {
1444                return act->args[ii].name;
1445            }
1446        }
1447    }
1448
1449    return NULL;
1450}
1451
1452#if 0
1453/* this code is used for standalone root parsing for debugging purposes */
1454/* cc -g -Wall -D__TRANSMISSION__ -o upnp upnp.c xml.c utils.c */
1455int
1456main( int argc, char * argv[] )
1457{
1458    struct stat sb;
1459    char      * data, * soap, * scpd;
1460    int         fd, ppp;
1461    ssize_t     res;
1462
1463    if( 3 != argc )
1464    {
1465        printf( "usage: %s root-url root-file\n", argv[0] );
1466        return 0;
1467    }
1468
1469    tr_msgInit();
1470    tr_setMessageLevel( 9 );
1471
1472    if( 0 > stat( argv[2], &sb ) )
1473    {
1474        tr_err( "failed to stat file %s: %s", argv[2], strerror( errno ) );
1475        return 1;
1476    }
1477
1478    data = malloc( sb.st_size );
1479    if( NULL == data )
1480    {
1481        tr_err( "failed to malloc %zd bytes", ( size_t )sb.st_size );
1482        return 1;
1483    }
1484
1485    fd = open( argv[2], O_RDONLY );
1486    if( 0 > fd )
1487    {
1488        tr_err( "failed to open file %s: %s", argv[2], strerror( errno ) );
1489        free( data );
1490        return 1;
1491    }
1492
1493    res = read( fd, data, sb.st_size );
1494    if( sb.st_size > res )
1495    {
1496        tr_err( "failed to read file %s: %s", argv[2],
1497                ( 0 > res ? strerror( errno ) : "short read count" ) );
1498        close( fd );
1499        free( data );
1500        return 1;
1501    }
1502
1503    close( fd );
1504
1505    if( parseRoot( argv[1], data, sb.st_size, &soap, &scpd, &ppp ) )
1506    {
1507        tr_err( "root parsing failed" );
1508    }
1509    else
1510    {
1511        tr_err( "soap=%s scpd=%s ppp=%s", soap, scpd, ( ppp ? "yes" : "no" ) );
1512        free( soap );
1513        free( scpd );
1514    }
1515    free( data );
1516
1517    return 0;
1518}
1519
1520int  tr_netMcastOpen( int port, struct in_addr addr ) { assert( 0 ); }
1521int  tr_netBind    ( int port, int type ) { assert( 0 ); }
1522void tr_netClose   ( int s ) { assert( 0 ); }
1523int  tr_netRecvFrom( int s, uint8_t * buf, int size, struct sockaddr_in * sin ) { assert( 0 ); }
1524int         tr_httpRequestType( const char * data, int len,
1525                                char ** method, char ** uri ) { assert( 0 ); }
1526int         tr_httpResponseCode( const char * data, int len ) { assert( 0 ); }
1527char *      tr_httpParse( const char * data, int len, tr_http_header_t *headers ) { assert( 0 ); }
1528int         tr_httpIsUrl( const char * u, int l ) { assert( 0 ); }
1529int         tr_httpParseUrl( const char * u, int l, char ** h, int * p, char ** q ) { assert( 0 ); }
1530tr_http_t   * tr_httpClient( int t, const char * h, int p, const char * u, ... ) { assert( 0 ); }
1531tr_http_t   * tr_httpClientUrl( int t, const char * u, ... ) { assert( 0 ); }
1532void          tr_httpAddHeader( tr_http_t * h, const char * n, const char * v ) { assert( 0 ); }
1533void          tr_httpAddBody( tr_http_t * h, const char * b, ... ) { assert( 0 ); }
1534tr_tristate_t tr_httpPulse( tr_http_t * h, const char ** b, int * l ) { assert( 0 ); }
1535char        * tr_httpWhatsMyAddress( tr_http_t * h ) { assert( 0 ); }
1536void          tr_httpClose( tr_http_t * h ) { assert( 0 ); }
1537
1538void tr_lockInit     ( tr_lock_t * l ) {}
1539int  pthread_mutex_lock( pthread_mutex_t * m ) { return 0; }
1540int  pthread_mutex_unlock( pthread_mutex_t * m ) { return 0; }
1541
1542#endif
Note: See TracBrowser for help on using the repository browser.