source: branches/daemon/libtransmission/upnp.c @ 1712

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

Merge libT revs 1616:1711 from trunk to daemon branch.

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