source: trunk/libtransmission/upnp.c @ 920

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

Merge nat-traversal branch to trunk.

  • Property svn:keywords set to Date Rev Author Id
File size: 36.6 KB
Line 
1/******************************************************************************
2 * $Id: upnp.c 920 2006-09-25 18:37:45Z 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", ii->host );
586}
587
588static void
589deviceRemove( tr_upnp_device_t ** prevptr, tr_fd_t * fdlimit )
590{
591    tr_upnp_device_t * dead;
592
593    dead = *prevptr;
594    *prevptr = dead->next;
595
596    tr_inf( "forgetting upnp device %s", dead->host );
597
598    free( dead->id );
599    free( dead->host );
600    free( dead->root );
601    free( dead->soap );
602    free( dead->scpd );
603    free( dead->myaddr );
604    if( NULL != dead->http )
605    {
606        killHttp( fdlimit, &dead->http );
607    }
608    actionFree( &dead->getcmd );
609    actionFree( &dead->addcmd );
610    actionFree( &dead->delcmd );
611    free( dead );
612}
613
614static int
615deviceStop( tr_upnp_device_t * dev )
616{
617    switch( dev->state )
618    {
619        case UPNPDEV_STATE_READY:
620        case UPNPDEV_STATE_ERROR:
621            return 1;
622        case UPNPDEV_STATE_MAPPED:
623            tr_dbg( "upnp device %s: stopping upnp, state mapped -> delete",
624                dev->host );
625            dev->state = UPNPDEV_STATE_DEL;
626            return 0;
627        default:
628            return 0;
629    }
630}
631
632static int
633devicePulse( tr_upnp_device_t * dev, tr_fd_t * fdlimit, int port )
634{
635    const char * body;
636    int          len, code;
637    uint8_t      laststate;
638
639    switch( dev->state )
640    {
641        case UPNPDEV_STATE_READY:
642            if( 0 < port )
643            {
644                tr_dbg( "upnp device %s: want mapping, state ready -> get",
645                        dev->host );
646                dev->mappedport = port;
647                dev->state = UPNPDEV_STATE_GET;
648                break;
649            }
650            return 1;
651        case UPNPDEV_STATE_MAPPED:
652            if( port != dev->mappedport )
653            {
654                tr_dbg( "upnp device %s: change mapping, "
655                        "state mapped -> delete", dev->host );
656                dev->state = UPNPDEV_STATE_DEL;
657                break;
658            }
659            if( tr_date() > dev->lastcheck + MAPPING_CHECK_INTERVAL )
660            {
661                tr_dbg( "upnp device %s: check mapping, "
662                        "state mapped -> get", dev->host );
663                dev->state = UPNPDEV_STATE_GET;
664            }
665            return 1;
666        case UPNPDEV_STATE_ERROR:
667            return 0;
668    }
669
670    code = devicePulseHttp( dev, fdlimit, &body, &len );
671    if( 0 > code )
672    {
673        return 1;
674    }
675
676    if( LOOP_DETECT_THRESHOLD <= dev->looping )
677    {
678        tr_dbg( "upnp device %s: loop detected, state %hhu -> error",
679                dev->host, dev->state );
680        dev->state = UPNPDEV_STATE_ERROR;
681        dev->looping = 0;
682    }
683
684    laststate = dev->state;
685    dev->state = UPNPDEV_STATE_ERROR;
686    switch( laststate ) 
687    {
688        case UPNPDEV_STATE_ROOT:
689            if( TR_HTTP_STATUS_OK( code ) &&
690                !parseRoot( body, len, &dev->soap, &dev->scpd ) )
691            {
692                tr_dbg( "upnp device %s: parsed root, state root -> scpd",
693                        dev->host );
694                dev->state = UPNPDEV_STATE_SCPD;
695            }
696            break;
697
698        case UPNPDEV_STATE_SCPD:
699            if( TR_HTTP_STATUS_OK( code ) &&
700                !parseScpd( body, len, &dev->getcmd,
701                            &dev->addcmd, &dev->delcmd ) )
702            {
703                tr_dbg( "upnp device %s: parsed scpd, state scpd -> ready",
704                        dev->host );
705                dev->state = UPNPDEV_STATE_READY;
706                dev->looping = 0;
707            }
708            break;
709
710        case UPNPDEV_STATE_ADD:
711            dev->looping++;
712            if( IGD_ADD_CONFLICT == code )
713            {
714                tr_dbg( "upnp device %s: add conflict, state add -> delete",
715                        dev->host );
716                dev->state = UPNPDEV_STATE_DEL;
717            }
718            else if( TR_HTTP_STATUS_OK( code ) ||
719                     IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
720            {
721                tr_dbg( "upnp device %s: add attempt, state add -> get",
722                        dev->host );
723                dev->state = UPNPDEV_STATE_GET;
724            }
725            break;
726
727        case UPNPDEV_STATE_GET:
728            dev->looping++;
729            if( TR_HTTP_STATUS_OK( code ) )
730            {
731                switch( parseMapping( dev, body, len ) )
732                {
733                    case -1:
734                        break;
735                    case 0:
736                        tr_dbg( "upnp device %s: invalid mapping, "
737                                "state get -> delete", dev->host );
738                        dev->state = UPNPDEV_STATE_DEL;
739                        break;
740                    case 1:
741                        tr_dbg( "upnp device %s: good mapping, "
742                                "state get -> mapped", dev->host );
743                        dev->state = UPNPDEV_STATE_MAPPED;
744                        dev->looping = 0;
745                        dev->lastcheck = tr_date();
746                        tr_inf( "upnp successful for port %i",
747                                dev->mappedport );
748                        break;
749                    default:
750                        assert( 0 );
751                        break;
752                }
753            }
754            else if( IGD_NO_MAPPING_EXISTS == code ||
755                     IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
756            {
757                tr_dbg( "upnp device %s: no mapping, state get -> add",
758                        dev->host );
759                dev->state = UPNPDEV_STATE_ADD;
760            }
761            break;
762
763        case UPNPDEV_STATE_DEL:
764            dev->looping++;
765            if( TR_HTTP_STATUS_OK( code ) || IGD_NO_MAPPING_EXISTS == code ||
766                IGD_GENERIC_ERROR == code || IGD_GENERIC_FAILED == code )
767            {
768                tr_dbg( "upnp device %s: deleted, state delete -> ready",
769                        dev->host );
770                dev->state = UPNPDEV_STATE_READY;
771                dev->looping = 0;
772            }
773            break;
774
775        default:
776            assert( 0 );
777            break;
778    }
779
780    dev->lastrequest = tr_date();
781    killHttp( fdlimit, &dev->http );
782
783    if( UPNPDEV_STATE_ERROR == dev->state )
784    {
785        tr_dbg( "upnp device %s: error, state %hhu -> error",
786                dev->host, laststate );
787        return 0;
788    }
789
790    return 1;
791}
792
793static tr_http_t *
794devicePulseGetHttp( tr_upnp_device_t * dev, tr_fd_t * fdlimit )
795{
796    tr_http_t  * ret;
797    char numstr[6];
798
799    if( tr_fdSocketWillCreate( fdlimit, 0 ) )
800    {
801        return NULL;
802    }
803
804    switch( dev->state ) 
805    {
806        case UPNPDEV_STATE_ROOT:
807            if( !dev->soapretry )
808            {
809                ret = tr_httpClient( TR_HTTP_GET, dev->host,
810                                     dev->port, "%s", dev->root );
811            }
812            break;
813        case UPNPDEV_STATE_SCPD:
814            if( !dev->soapretry )
815            {
816                ret = tr_httpClient( TR_HTTP_GET, dev->host,
817                                     dev->port, "%s", dev->scpd );
818            }
819            break;
820        case UPNPDEV_STATE_ADD:
821            if( NULL == dev->myaddr )
822            {
823                ret = NULL;
824                break;
825            }
826            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
827            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
828                               &dev->addcmd,
829                               "PortMappingEnabled", "1",
830                               "PortMappingLeaseDuration", "0",
831                               "RemoteHost", "",
832                               "ExternalPort", numstr,
833                               "InternalPort", numstr,
834                               "PortMappingProtocol", "TCP",
835                               "InternalClient", dev->myaddr,
836                               "PortMappingDescription",
837                                   "Added by Transmission",
838                               NULL );
839            break;
840        case UPNPDEV_STATE_GET:
841            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
842            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
843                               &dev->getcmd,
844                               "RemoteHost", "",
845                               "ExternalPort", numstr,
846                               "PortMappingProtocol", "TCP",
847                               NULL );
848            break;
849        case UPNPDEV_STATE_DEL:
850            snprintf( numstr, sizeof( numstr ), "%i", dev->mappedport );
851            ret = soapRequest( dev->soapretry, dev->host, dev->port, dev->soap,
852                               &dev->delcmd,
853                               "RemoteHost", "",
854                               "ExternalPort", numstr,
855                               "PortMappingProtocol", "TCP",
856                               NULL );
857            break;
858        default:
859            assert( 0 );
860            break;
861    }
862
863    if( NULL == ret )
864    {
865        tr_fdSocketClosed( fdlimit, 0 );
866    }
867
868    return ret;
869}
870
871static int
872devicePulseHttp( tr_upnp_device_t * dev, tr_fd_t * fdlimit,
873                 const char ** body, int * len )
874{
875    const char * headers;
876    int          hlen, code;
877
878    if( NULL == dev->http )
879    {
880        if( tr_date() < dev->lastrequest + HTTP_REQUEST_INTERVAL )
881        {
882            return -1;
883        }
884        dev->lastrequest = tr_date();
885        dev->http = devicePulseGetHttp( dev, fdlimit );
886        if( NULL == dev->http )
887        {
888            tr_dbg( "upnp device %s: http init failed, state ? -> error",
889                    dev->host );
890            dev->state = UPNPDEV_STATE_ERROR;
891            dev->soapretry = 0;
892            return -1;
893        }
894    }
895
896    if( NULL == dev->myaddr )
897    {
898        dev->myaddr = tr_httpWhatsMyAddress( dev->http );
899    }
900
901    switch( tr_httpPulse( dev->http, &headers, &hlen ) )
902    {
903        case TR_OK:
904            code = tr_httpResponseCode( headers, hlen );
905            if( TR_HTTP_STATUS_FAIL( code ) && !dev->soapretry )
906            {
907                dev->soapretry = 1;
908                killHttp( fdlimit, &dev->http );
909                break;
910            }
911            dev->soapretry = 0;
912            *body = tr_httpParse( headers, hlen, NULL );
913            *len = ( NULL == body ? 0 : hlen - ( *body - headers ) );
914            return code;
915        case TR_ERROR:
916            killHttp( fdlimit, &dev->http );
917            if( dev->soapretry )
918            {
919                tr_dbg( "upnp device %s: http pulse failed, state ? -> error",
920                        dev->host );
921                dev->state = UPNPDEV_STATE_ERROR;
922                dev->soapretry = 0;
923            }
924            else
925            {
926                dev->soapretry = 1;
927            }
928            break;
929        case TR_WAIT:
930            break;
931    }
932
933    return -1;
934}
935
936static int
937parseRoot(const char *buf, int len, char ** soap, char ** scpd )
938{
939    const char * end, * ii, * jj, * kk, * urlbase;
940    char       * joined;
941
942    *soap = NULL;
943    *scpd = NULL;
944    end = buf + len;
945
946    buf = tr_xmlFindTagContents( buf, end, "root" );
947    urlbase = tr_xmlFindTag( buf, end, "urlBase" );
948    buf = tr_xmlFindTagContents( buf, end, "device" );
949    if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
950            "urn:schemas-upnp-org:device:InternetGatewayDevice:1", 1 ) )
951    {
952        return 1;
953    }
954    buf = tr_xmlFindTag( buf, end, "deviceList" );
955    ii = tr_xmlTagContents( buf, end );
956    for( ; NULL != ii; ii = tr_xmlSkipTag( ii, end ) )
957    {
958        ii = tr_xmlFindTag( ii, end, "device" );
959        buf = tr_xmlTagContents( ii, end );
960        if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
961                "urn:schemas-upnp-org:device:WANDevice:1", 1 ) )
962        {
963            continue;
964        }
965        buf = tr_xmlFindTag( buf, end, "deviceList" );
966        jj = tr_xmlTagContents( buf, end );
967        for( ; NULL != jj; jj = tr_xmlSkipTag( jj, end ) )
968        {
969            jj = tr_xmlFindTag( jj, end, "device" );
970            buf = tr_xmlTagContents( jj, end );
971            if( tr_xmlFindTagVerifyContents( buf, end, "deviceType",
972                    "urn:schemas-upnp-org:device:WANConnectionDevice:1", 1 ) )
973            {
974                continue;
975            }
976            buf = tr_xmlFindTag( buf, end, "serviceList" );
977            kk = tr_xmlTagContents( buf, end );
978            for( ; NULL != kk; kk = tr_xmlSkipTag( kk, end ) )
979            {
980                kk = tr_xmlFindTag( kk, end, "service" );
981                buf = tr_xmlTagContents( kk, end );
982                if( !tr_xmlFindTagVerifyContents( buf, end, "serviceType",
983                                                  UPNP_SERVICE_TYPE, 1 ) &&
984                    !tr_xmlFindTagVerifyContents( buf, end, "serviceId",
985                        "urn:upnp-org:serviceId:WANIPConn1", 1 ) )
986                {
987                    *soap = tr_xmlDupTagContents( buf, end, "controlURL");
988                    *scpd = tr_xmlDupTagContents( buf, end, "SCPDURL");
989                    break;
990                }
991            }
992        }
993    }
994
995    if( NULL != *soap && NULL != *scpd )
996    {
997        if( '/' != **soap || '/' != **scpd )
998        {
999            urlbase = tr_xmlDupContents( urlbase, end );
1000            if( NULL != urlbase )
1001            {
1002                if( '/' != **soap )
1003                {
1004                    joined = joinstrs( urlbase, "/", *soap );
1005                    free( *soap );
1006                    *soap = joined;
1007                }
1008                if( '/' != **scpd )
1009                {
1010                    joined = joinstrs( urlbase, "/", *scpd );
1011                    free( *scpd );
1012                    *scpd = joined;
1013                }
1014                free( (char*)urlbase );
1015            }
1016        }
1017        return 0;
1018    }
1019
1020    return 1;
1021}
1022
1023static int
1024parseScpd( const char * buf, int len, tr_upnp_action_t * getcmd,
1025           tr_upnp_action_t * addcmd, tr_upnp_action_t * delcmd )
1026{
1027    const char * end, * next, * sub, * name;
1028
1029    end = buf + len;
1030    next = buf;
1031
1032    next = tr_xmlFindTagContents( next, end, "scpd" );
1033    next = tr_xmlFindTagContents( next, end, "actionList" );
1034
1035    while( NULL != next )
1036    {
1037        next = tr_xmlFindTag( next, end, "action" );
1038        sub = tr_xmlTagContents( next, end );
1039        name = tr_xmlFindTagContents( sub, end, "name" );
1040        sub = tr_xmlFindTagContents( sub, end, "argumentList" );
1041        if( !tr_xmlVerifyContents( name, end, getcmd->name, 1 ) )
1042        {
1043            if( parseScpdArgs( sub, end, getcmd, 'i' ) ||
1044                parseScpdArgs( sub, end, getcmd, 'o' ) )
1045            {
1046                return 1;
1047            }
1048        }
1049        else if( !tr_xmlVerifyContents( name, end, addcmd->name, 1 ) )
1050        {
1051            if( parseScpdArgs( sub, end, addcmd, 'i' ) )
1052            {
1053                return 1;
1054            }
1055        }
1056        else if( !tr_xmlVerifyContents( name, end, delcmd->name, 1 ) )
1057        {
1058            if( parseScpdArgs( sub, end, delcmd, 'i' ) )
1059            {
1060                return 1;
1061            }
1062        }
1063        next = tr_xmlSkipTag( next, end );
1064    }
1065
1066    return 0;
1067}
1068
1069static int
1070parseScpdArgs( const char * buf, const char * end,
1071               tr_upnp_action_t * action, char dir )
1072{
1073    const char * sub, * which;
1074    char       * name, * var;
1075
1076    assert( 'i' == dir || 'o' == dir );
1077    which = ( 'i' == dir ? "in" : "out" );
1078
1079    while( NULL != buf )
1080    {
1081        sub = tr_xmlFindTagContents( buf, end, "argument" );
1082        if( !tr_xmlFindTagVerifyContents( sub, end, "direction", which, 1 ) )
1083        {
1084            name = tr_xmlDupTagContents( sub, end, "name" );
1085            var = tr_xmlDupTagContents( sub, end, "relatedStateVariable" );
1086            if( NULL == name || NULL == var )
1087            {
1088                free( name );
1089                free( var );
1090            }
1091            else if( actionAdd( action, name, var, dir ) )
1092            {
1093                return 1;
1094            }
1095        }
1096        buf = tr_xmlSkipTag( buf, end );
1097    }
1098
1099    return 0;
1100}
1101
1102static int
1103parseMapping( tr_upnp_device_t * dev, const char * buf, int len )
1104{
1105    const char * end, * down, * next, * var;
1106    int          varlen, pret, cret, eret;
1107    char       * val;
1108
1109    assert( 0 < dev->mappedport );
1110
1111    if( NULL == dev->myaddr )
1112    {
1113        return -1;
1114    }
1115
1116    pret = -1;
1117    cret = -1;
1118    eret = -1;
1119
1120    end = buf + len;
1121    down = buf;
1122    down = tr_xmlFindTagContents( down, end, "Envelope" );
1123    down = tr_xmlFindTagContents( down, end, "Body" );
1124    down = tr_xmlFindTagContents( down, end,
1125                                  "GetSpecificPortMappingEntryResponse" );
1126
1127    next = down;
1128    while( NULL != next )
1129    {
1130        var = tr_xmlTagName( next, end, &varlen );
1131        var = actionLookupVar( &dev->getcmd, var, varlen, 'o' );
1132        if( NULL != var )
1133        {
1134            val = tr_xmlDupContents( tr_xmlTagContents( next, end ), end );
1135            if( 0 == tr_strcasecmp( "InternalPort", var ) )
1136            {
1137                pret = ( strtol( val, NULL, 10 ) == dev->mappedport ? 1 : 0 );
1138            }
1139            else if( 0 == tr_strcasecmp( "InternalClient", var ) )
1140            {
1141                cret = ( 0 == strcmp( dev->myaddr, val ) ? 1 : 0 );
1142            }
1143            else if( 0 == tr_strcasecmp( "PortMappingEnabled", var ) )
1144            {
1145                eret = ( 0 == strcmp( "1", val ) ? 1 : 0 );
1146            }
1147            free( val );
1148        }
1149        next = tr_xmlSkipTag( next, end );
1150    }
1151
1152    return MIN( MIN( pret, cret), eret );
1153}
1154
1155static char *
1156joinstrs( const char * first, const char * delim, const char * second )
1157{
1158    char * ret;
1159    int    len1, len2, len3;
1160
1161    len1 = strlen( first );
1162    len2 = strlen( delim );
1163    len3 = strlen( second );
1164    ret = malloc( len1 + len2 + len3 + 1 );
1165    if( NULL == ret )
1166    {
1167        return NULL;
1168    }
1169
1170    memcpy( ret, first, len1 );
1171    memcpy( ret + len1, delim, len2 );
1172    memcpy( ret + len1 + len2, second, len3 );
1173    ret[len1 + len2 + len3] = '\0';
1174
1175    return ret;
1176}
1177
1178static tr_http_t *
1179soapRequest( int retry, const char * host, int port, const char * path,
1180             tr_upnp_action_t * action, ... )
1181{
1182    tr_http_t  * http;
1183    va_list      ap;
1184    const char * name, * value;
1185    int          method;
1186
1187    method = ( retry ? TR_HTTP_M_POST : TR_HTTP_POST );
1188    http = tr_httpClient( method, host, port, "%s", path );
1189    if( NULL != http )
1190    {
1191        tr_httpAddHeader( http, "Content-type", "text/xml" );
1192        tr_httpAddHeader( http, "SOAPAction", action->action );
1193        if( retry )
1194        {
1195            tr_httpAddHeader( http, "Man", "\"" SOAP_ENVELOPE "\"" );
1196        }
1197        tr_httpAddBody( http, 
1198"<s:Envelope"
1199"    xmlns:s='" SOAP_ENVELOPE "'"
1200"    s:encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>"
1201"  <s:Body>"
1202"    <u:%s xmlns:u='" UPNP_SERVICE_TYPE "'>", action->name );
1203
1204        va_start( ap, action );
1205        do
1206        {
1207            name = va_arg( ap, const char * );
1208            name = actionLookupName( action, name, -1, 'i' );
1209            if( NULL != name )
1210            {
1211                value = va_arg( ap, const char * );
1212                if( NULL != value )
1213                {
1214                    tr_httpAddBody( http,
1215"      <u:%s>%s</u:%s>", name, value, name );
1216                }
1217                else 
1218                {
1219                    tr_httpAddBody( http,
1220"      <u:%s></u:%s>", name, name );
1221                }
1222            }
1223        }
1224        while( NULL != name && NULL != value );
1225        va_end( ap );
1226
1227        tr_httpAddBody( http,
1228"    </u:%s>"
1229"  </s:Body>"
1230"</s:Envelope>", action->name );
1231    }
1232
1233    return http;
1234}
1235
1236static void
1237actionSetup( tr_upnp_action_t * action, const char * name, int prealloc )
1238{
1239    action->name = strdup( name );
1240    action->action = joinstrs( UPNP_SERVICE_TYPE, "#", name );
1241    assert( NULL == action->args );
1242    action->args = malloc( sizeof( *action->args ) * prealloc );
1243    memset( action->args, 0, sizeof( *action->args ) * prealloc );
1244    action->len = prealloc;
1245}
1246
1247static void
1248actionFree( tr_upnp_action_t * act )
1249{
1250    free( act->name );
1251    free( act->action );
1252    while( 0 < act->len )
1253    {
1254        act->len--;
1255        free( act->args[act->len].name );
1256        free( act->args[act->len].var );
1257    }
1258    free( act->args );
1259}
1260
1261static int
1262actionAdd( tr_upnp_action_t * act, char * name, char * var, char dir )
1263{
1264    int    ii;
1265    void * newbuf;
1266
1267    assert( 'i' == dir || 'o' == dir );
1268
1269    ii = 0;
1270    while( ii < act->len && NULL != act->args[ii].name )
1271    {
1272        ii++;
1273    }
1274
1275    if( ii == act->len )
1276    {
1277        newbuf = realloc( act->args, sizeof( *act->args ) * ( act->len + 1 ) );
1278        if( NULL == newbuf )
1279        {
1280            return 1;
1281        }
1282        act->args = newbuf;
1283        act->len++;
1284    }
1285
1286    act->args[ii].name = name;
1287    act->args[ii].var = var;
1288    act->args[ii].dir = dir;
1289
1290    return 0;
1291}
1292
1293static const char *
1294actionLookup( tr_upnp_action_t * act, const char * key, int len,
1295              char dir, int getname )
1296{
1297    int ii;
1298
1299    assert( 'i' == dir || 'o' == dir );
1300
1301    if( NULL == key || 0 == len )
1302    {
1303        return NULL;
1304    }
1305    if( 0 > len )
1306    {
1307        len = strlen( key );
1308    }
1309
1310    for( ii = 0; ii < act->len; ii++ )
1311    {
1312        if( NULL != act->args[ii].name && dir == act->args[ii].dir )
1313        {
1314            if( !getname &&
1315                0 == tr_strncasecmp( act->args[ii].name, key, len ) )
1316            {
1317                return act->args[ii].var;
1318            }
1319            else if( getname &&
1320                     0 == tr_strncasecmp( act->args[ii].var, key, len ) )
1321            {
1322                return act->args[ii].name;
1323            }
1324        }
1325    }
1326
1327    return NULL;
1328}
Note: See TracBrowser for help on using the repository browser.