source: branches/0.9x/libtransmission/upnp.c @ 3706

Last change on this file since 3706 was 3706, checked in by charles, 15 years ago

maybe fix portmapping

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