source: trunk/libtransmission/upnp.c @ 1356

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

Merge io branch into trunk

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