source: branches/pex/libtransmission/upnp.c @ 1540

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

Implement Azureus peer protocol, including PEX message.
Implement extended messages, including uTorrent PEX.

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