source: trunk/libtransmission/upnp.c @ 939

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

Don't verify part of the UPnP root descript that apparently isn't standard.

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