source: trunk/libtransmission/upnp.c @ 1425

Last change on this file since 1425 was 1425, checked in by titer, 15 years ago

More simplifications

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