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

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

fix OpenBSD build error.

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