source: trunk/libtransmission/upnp.c @ 1292

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

Whoops, read UPnP root urlbase correctly.

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