source: trunk/libtransmission/upnp.c @ 930

Last change on this file since 930 was 930, checked in by joshe, 16 years ago

Properly exit the upnp device pulse when a loop is detected.

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