source: trunk/libtransmission/upnp.c @ 1761

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

Add some debugging code that's handy for parsing upnp root files.

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