source: branches/0.7x/libtransmission/upnp.c @ 1783

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

Merge r1762 from trunk.

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