Annotation of embedaddon/miniupnpd/miniupnpc-async/miniupnpc-async.c, revision 1.1.1.1

1.1       misho       1: /* $Id: miniupnpc-async.c,v 1.19 2014/11/07 12:05:40 nanard Exp $ */
                      2: /* miniupnpc-async
                      3:  * Copyright (c) 2008-2017, Thomas BERNARD <miniupnp@free.fr>
                      4:  * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
                      5:  *
                      6:  * Permission to use, copy, modify, and/or distribute this software for any
                      7:  * purpose with or without fee is hereby granted, provided that the above
                      8:  * copyright notice and this permission notice appear in all copies.
                      9:  *
                     10:  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
                     11:  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
                     12:  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
                     13:  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
                     14:  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
                     15:  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
                     16:  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
                     17: #include <stdlib.h>
                     18: #include <string.h>
                     19: #include <sys/types.h>
                     20: #include <sys/socket.h>
                     21: #include <netinet/in.h>
                     22: #include <arpa/inet.h>
                     23: #include <net/if.h>
                     24: #include <stdio.h>
                     25: #ifdef WIN32
                     26: #include <winsock2.h>
                     27: #include <ws2tcpip.h>
                     28: #include <io.h>
                     29: #define PRINT_SOCKET_ERROR printf
                     30: #define SOCKET_ERROR GetWSALastError()
                     31: #define WOULDBLOCK(err) (err == WSAEWOULDBLOCK)
                     32: #else
                     33: #include <unistd.h>
                     34: #include <errno.h>
                     35: #define closesocket close
                     36: #define PRINT_SOCKET_ERROR perror
                     37: #define SOCKET_ERROR errno
                     38: #define WOULDBLOCK(err) (err == EAGAIN || err == EWOULDBLOCK)
                     39: #endif
                     40: #include "miniupnpc-async.h"
                     41: #include "parsessdpreply.h"
                     42: #include "upnputils.h"
                     43: #include "minixml.h"
                     44: #include "igd_desc_parse.h"
                     45: #include "upnpreplyparse.h"
                     46: 
                     47: #ifndef MIN
                     48: #define MIN(x,y) (((x)<(y))?(x):(y))
                     49: #endif /* MIN */
                     50: 
                     51: #ifndef MAXHOSTNAMELEN
                     52: #define MAXHOSTNAMELEN 64
                     53: #endif /* MAXHOSTNAMELEN */
                     54: 
                     55: #define SSDP_PORT 1900
                     56: #define SSDP_MCAST_ADDR "239.255.255.250"
                     57: #define XSTR(s) STR(s)
                     58: #define STR(s) #s
                     59: 
                     60: #ifdef DEBUG
                     61: #define debug_printf(...) fprintf(stderr, __VA_ARGS__)
                     62: #else
                     63: #define debug_printf(...)
                     64: #endif
                     65: 
                     66: /* stuctures */
                     67: 
                     68: struct upnp_args {
                     69:        const char * elt;
                     70:        const char * val;
                     71: };
                     72: 
                     73: /* private functions */
                     74: 
                     75: static int upnpc_connect(upnpc_device_t * p, const char * url);
                     76: static int upnpc_send_request(upnpc_device_t * p);
                     77: 
                     78: 
                     79: /* parse_msearch_reply()
                     80:  * the last 4 arguments are filled during the parsing :
                     81:  *    - location/locationsize : "location:" field of the SSDP reply packet
                     82:  *    - st/stsize : "st:" field of the SSDP reply packet.
                     83:  * The strings are NOT null terminated */
                     84: static void
                     85: parse_msearch_reply(const char * reply, int size,
                     86:                     const char * * location, unsigned int * locationsize,
                     87:                     const char * * st, unsigned int * stsize)
                     88: {
                     89:        int a, b, i;
                     90:        i = 0;  /* current character index */
                     91:        a = i;  /* start of the line */
                     92:        b = 0;  /* end of the "header" (position of the colon) */
                     93:        while(i<size) {
                     94:                switch(reply[i]) {
                     95:                case ':':
                     96:                        if(b==0) {
                     97:                                b = i; /* end of the "header" */
                     98:                        }
                     99:                        break;
                    100:                case '\x0a':
                    101:                case '\x0d':
                    102:                        if(b!=0) {
                    103:                                /* skip the colon and white spaces */
                    104:                                do { b++; } while(reply[b]==' ' && b<size);
                    105:                                if(0==strncasecmp(reply+a, "location:", 9)) {
                    106:                                        *location = reply+b;
                    107:                                        *locationsize = i-b;
                    108:                                } else if(0==strncasecmp(reply+a, "st:", 3)) {
                    109:                                        *st = reply+b;
                    110:                                        *stsize = i-b;
                    111:                                }
                    112:                                b = 0;
                    113:                        }
                    114:                        a = i+1;
                    115:                        break;
                    116:                default:
                    117:                        break;
                    118:                }
                    119:                i++;
                    120:        }
                    121: }
                    122: 
                    123: static int upnpc_send_ssdp_msearch(upnpc_t * p, const char * device, unsigned int mx)
                    124: {
                    125:        /* envoyer les packets de M-SEARCH discovery sur le socket ssdp */
                    126:        int n;
                    127:        char bufr[1024];
                    128:        struct sockaddr_in addr;
                    129:        static const char MSearchMsgFmt[] = 
                    130:        "M-SEARCH * HTTP/1.1\r\n"
                    131:        "HOST: " SSDP_MCAST_ADDR ":" XSTR(SSDP_PORT) "\r\n"
                    132:        "ST: %s\r\n"
                    133:        "MAN: \"ssdp:discover\"\r\n"
                    134:        "MX: %u\r\n"
                    135:        "\r\n";
                    136: 
                    137:        memset(&addr, 0, sizeof(struct sockaddr_in));
                    138:        addr.sin_family = AF_INET;
                    139:     addr.sin_port = htons(SSDP_PORT);
                    140:     addr.sin_addr.s_addr = inet_addr(SSDP_MCAST_ADDR);
                    141:        n = snprintf(bufr, sizeof(bufr),
                    142:                     MSearchMsgFmt, device, mx);
                    143:        debug_printf("upnpc_send_ssdp_msearch: %s", bufr);
                    144:        n = sendto(p->ssdp_socket, bufr, n, 0,
                    145:                   (struct sockaddr *)&addr, sizeof(struct sockaddr_in));
                    146:        if (n < 0) {
                    147:                int err = SOCKET_ERROR;
                    148:                if(err == EINTR || WOULDBLOCK(err)) {
                    149:                        debug_printf("upnpc_send_ssdp_msearch: should try again");
                    150:                        p->state = EUPnPSendSSDP;
                    151:                        return 0;
                    152:                }
                    153:                PRINT_SOCKET_ERROR("sendto");
                    154:                return -1;
                    155:        }
                    156:        p->state = EUPnPReceiveSSDP;
                    157:        return 0;
                    158: }
                    159: 
                    160: static int upnpc_set_root_desc_location(upnpc_device_t * d, const char * location, int locationsize)
                    161: {
                    162:        char * tmp;
                    163:        tmp = realloc(d->root_desc_location, locationsize + 1);
                    164:        if(tmp == 0) {
                    165:                return -1;
                    166:        }
                    167:        memcpy(tmp, location, locationsize);
                    168:        tmp[locationsize] = '\0';
                    169:        d->root_desc_location = tmp;
                    170:        return 0;
                    171: }
                    172: 
                    173: static int upnpc_receive_and_parse_ssdp(upnpc_t * p)
                    174: {
                    175:        int n;
                    176:        char bufr[1024];
                    177:        n = recv(p->ssdp_socket, bufr, sizeof(bufr), 0);
                    178:        if (n<0) {
                    179:                PRINT_SOCKET_ERROR("recv");
                    180:        } else if (n==0) {
                    181:                debug_printf("empty packet received\n");
                    182:        } else {
                    183:                const char * location = NULL;
                    184:                unsigned int locationsize;
                    185:                const char * st = NULL;
                    186:                unsigned int stsize;
                    187:                debug_printf("%.*s", n, bufr);
                    188:                parse_msearch_reply(bufr, n, &location, &locationsize, &st, &stsize);
                    189:                debug_printf("location = '%.*s'\n", locationsize, location);
                    190:                debug_printf("st = '%.*s'\n", stsize, st);
                    191:                if(location != NULL) {
                    192:                        upnpc_device_t * dev = p->device_list;
                    193:                        while(dev != NULL) {
                    194:                                if(dev->root_desc_location != NULL
                    195:                                   && strlen(dev->root_desc_location) == locationsize
                    196:                               && memcmp(dev->root_desc_location, location, locationsize) == 0) {
                    197:                                        debug_printf("device already in list (location='%s')\n", dev->root_desc_location);
                    198:                                        return -1;
                    199:                                }
                    200:                                dev = dev->next;
                    201:                        }
                    202:                        dev = calloc(1, sizeof(upnpc_device_t));
                    203:                        if(dev == NULL) {
                    204:                                p->state = EUPnPError;
                    205:                                return -1;
                    206:                        }
                    207:                        if(upnpc_set_root_desc_location(dev, location, locationsize) < 0) {
                    208:                                free(dev);
                    209:                                p->state = EUPnPError;
                    210:                                return -1;
                    211:                        }
                    212:                        dev->next = p->device_list;
                    213:                        p->device_list = dev;
                    214:                        dev->state = EDevGetDescConnect;
                    215:                        upnpc_connect(dev, dev->root_desc_location);
                    216:                } else {
                    217:                        /* or do nothing ? */
                    218:                        p->state = EUPnPError;
                    219:                }
                    220:        }
                    221:        return 0;
                    222: }
                    223: 
                    224: static int
                    225: parseURL(const char * url,
                    226:          char * hostname, unsigned short * port,
                    227:          char * * path, unsigned int * scope_id)
                    228: {
                    229:        char * p1, *p2, *p3;
                    230:        if(!url)
                    231:                return 0;
                    232:        p1 = strstr(url, "://");
                    233:        if(!p1)
                    234:                return 0;
                    235:        p1 += 3;
                    236:        if(  (url[0]!='h') || (url[1]!='t')
                    237:           ||(url[2]!='t') || (url[3]!='p'))
                    238:                return 0;
                    239:        memset(hostname, 0, MAXHOSTNAMELEN + 1);
                    240:        if(*p1 == '[') {
                    241:                /* IP v6 : http://[2a00:1450:8002::6a]/path/abc */
                    242:                char * scope;
                    243:                scope = strchr(p1, '%');
                    244:                p2 = strchr(p1, ']');
                    245:                if(p2 && scope && scope < p2 && scope_id) {
                    246:                        /* parse scope */
                    247: #ifdef IF_NAMESIZE
                    248:                        char tmp[IF_NAMESIZE];
                    249:                        int l;
                    250:                        scope++;
                    251:                        /* "%25" is just '%' in URL encoding */
                    252:                        if(scope[0] == '2' && scope[1] == '5')
                    253:                                scope += 2;     /* skip "25" */
                    254:                        l = p2 - scope;
                    255:                        if(l >= IF_NAMESIZE)
                    256:                                l = IF_NAMESIZE - 1;
                    257:                        memcpy(tmp, scope, l);
                    258:                        tmp[l] = '\0';
                    259:                        *scope_id = if_nametoindex(tmp);
                    260:                        if(*scope_id == 0) {
                    261:                                *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
                    262:                        }
                    263: #else
                    264:                        /* under windows, scope is numerical */
                    265:                        char tmp[8];
                    266:                        int l;
                    267:                        scope++;
                    268:                        /* "%25" is just '%' in URL encoding */
                    269:                        if(scope[0] == '2' && scope[1] == '5')
                    270:                                scope += 2;     /* skip "25" */
                    271:                        l = p2 - scope;
                    272:                        if(l >= (int)sizeof(tmp))
                    273:                                l = sizeof(tmp) - 1;
                    274:                        memcpy(tmp, scope, l);
                    275:                        tmp[l] = '\0';
                    276:                        *scope_id = (unsigned int)strtoul(tmp, NULL, 10);
                    277: #endif
                    278:                }
                    279:                p3 = strchr(p1, '/');
                    280:                if(p2 && p3) {
                    281:                        p2++;
                    282:                        strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
                    283:                        if(*p2 == ':') {
                    284:                                *port = 0;
                    285:                                p2++;
                    286:                                while( (*p2 >= '0') && (*p2 <= '9')) {
                    287:                                        *port *= 10;
                    288:                                        *port += (unsigned short)(*p2 - '0');
                    289:                                        p2++;
                    290:                                }
                    291:                        } else {
                    292:                                *port = 80;
                    293:                        }
                    294:                        *path = p3;
                    295:                        return 1;
                    296:                }
                    297:        }
                    298:        p2 = strchr(p1, ':');
                    299:        p3 = strchr(p1, '/');
                    300:        if(!p3)
                    301:                return 0;
                    302:        if(!p2 || (p2>p3)) {
                    303:                strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p3-p1)));
                    304:                *port = 80;
                    305:        } else {
                    306:                strncpy(hostname, p1, MIN(MAXHOSTNAMELEN, (int)(p2-p1)));
                    307:                *port = 0;
                    308:                p2++;
                    309:                while( (*p2 >= '0') && (*p2 <= '9')) {
                    310:                        *port *= 10;
                    311:                        *port += (unsigned short)(*p2 - '0');
                    312:                        p2++;
                    313:                }
                    314:        }
                    315:        *path = p3;
                    316:        return 1;
                    317: }
                    318: 
                    319: static int upnpc_connect(upnpc_device_t * p, const char * url)
                    320: {
                    321:        int r;
                    322:        char hostname[MAXHOSTNAMELEN+1];
                    323:        unsigned short port;
                    324:        char * path;
                    325:        unsigned int scope_id;
                    326:        struct sockaddr_in addr;
                    327:        socklen_t addrlen;
                    328: 
                    329:        /*if(p->root_desc_location == 0) {
                    330:                p->state = EError;
                    331:                return -1;
                    332:        }*/
                    333:        if(!parseURL(url/*p->root_desc_location*/, hostname, &port,
                    334:                     &path, &scope_id)) {
                    335:                p->state = EDevError;
                    336:                return -1;
                    337:        }
                    338:        p->http_socket = socket(PF_INET, SOCK_STREAM, 0);
                    339:        if(p->http_socket < 0) {
                    340:                PRINT_SOCKET_ERROR("socket");
                    341:                p->state = EDevError;
                    342:                return -1;
                    343:        }
                    344:        if(!set_non_blocking(p->http_socket)) {
                    345:                /* TODO : ERROR */
                    346:        }
                    347:        memset(&addr, 0, sizeof(struct sockaddr_in));
                    348:        addr.sin_family = AF_INET;
                    349:        inet_pton(AF_INET, hostname, &(addr.sin_addr));
                    350:        addr.sin_port = htons(port);
                    351:        addrlen = sizeof(struct sockaddr_in);
                    352:        do {
                    353:                r = connect(p->http_socket, (struct sockaddr *)&addr, addrlen);
                    354:                if(r < 0) {
                    355:                        if(errno == EINPROGRESS) {
                    356:                                /*p->state = EGetDescConnect;*/
                    357:                                return 0;
                    358:                        } else if(errno != EINTR) {
                    359:                                PRINT_SOCKET_ERROR("connect");
                    360:                                p->state = EDevError;
                    361:                                return -1;
                    362:                        }
                    363:                }
                    364:        } while(r < 0 && errno == EINTR);
                    365:        if(p->state == EDevGetDescConnect) {
                    366:                p->state = EDevGetDescRequest;
                    367:        } else {
                    368:                p->state = EDevSoapRequest;
                    369:        }
                    370:        upnpc_send_request(p);
                    371:        return 0;
                    372: }
                    373: 
                    374: static int upnpc_complete_connect(upnpc_device_t * p)
                    375: {
                    376:        socklen_t len;
                    377:        int err;
                    378:        len = sizeof(err);
                    379:        if(getsockopt(p->http_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
                    380:                PRINT_SOCKET_ERROR("getsockopt");
                    381:                p->state = EDevError;
                    382:                return -1;
                    383:        }
                    384:        if(err != 0) {
                    385:                debug_printf("connect failed %d\n", err);
                    386:                p->state = EDevError;
                    387:                return -1;
                    388:        }
                    389:        if(p->state == EDevGetDescConnect)
                    390:                p->state = EDevGetDescRequest;
                    391:        else
                    392:                p->state = EDevSoapRequest;
                    393:        upnpc_send_request(p);
                    394:        return 0;
                    395: }
                    396: 
                    397: static int upnpc_send_request(upnpc_device_t * p)
                    398: {
                    399:        ssize_t n;
                    400:        static const char reqfmt[] = "GET %s HTTP/1.1\r\n"
                    401:                "Host: %s:%hu\r\n"
                    402:                "Connection: Close\r\n"
                    403:                "User-Agent: MiniUPnPc-async\r\n"
                    404:                "\r\n";
                    405: 
                    406:        /* retrieve "our" IP address used to connect to the UPnP device */
                    407:        p->selfaddrlen = sizeof(struct sockaddr_storage);
                    408:        if(getsockname(p->http_socket, (struct sockaddr *)&p->selfaddr, &p->selfaddrlen) < 0) {
                    409:                PRINT_SOCKET_ERROR("getsockname()");
                    410:        }
                    411: 
                    412:        if(p->http_request == NULL) {
                    413:                char hostname[MAXHOSTNAMELEN+1];
                    414:                unsigned short port;
                    415:                char * path;
                    416:                unsigned int scope_id;
                    417:                int len;
                    418:                if(!parseURL(p->root_desc_location, hostname, &port,
                    419:                         &path, &scope_id)) {
                    420:                        p->state = EDevError;
                    421:                        return -1;
                    422:                }
                    423:                len = snprintf(NULL, 0, reqfmt, path, hostname, port);
                    424:                p->http_request = malloc(len + 1);
                    425:                if(p->http_request == NULL) {
                    426:                        p->state = EDevError;
                    427:                        return -1;
                    428:                }
                    429:                p->http_request_len = snprintf(p->http_request, len + 1,
                    430:                                               reqfmt, path, hostname, port);
                    431:                p->http_request_sent = 0;
                    432:        }
                    433:        n = send(p->http_socket, p->http_request + p->http_request_sent,
                    434:                 p->http_request_len - p->http_request_sent, 0/* flags */);
                    435:        if(n < 0) {
                    436:                PRINT_SOCKET_ERROR("send");
                    437:                p->state = EDevError;
                    438:                return -1;
                    439:        } else {
                    440:                debug_printf("sent %d bytes\n", (int)n);
                    441:                /*if(n == 0) {
                    442:                        p->state = EError;
                    443:                        return -1;
                    444:                }*/
                    445:                p->http_request_sent += n;
                    446:                if(p->http_request_sent >= p->http_request_len) {
                    447:                        /* all bytes sent */
                    448: #if 0
                    449:                        shutdown(p->http_socket, SHUT_WR);      /* some routers don't like that */
                    450: #endif
                    451:                        free(p->http_request);
                    452:                        p->http_request = NULL;
                    453:                        p->http_request_len = 0;
                    454:                        if(p->state == EDevGetDescRequest)
                    455:                                p->state = EDevGetDescResponse;
                    456:                        else
                    457:                                p->state = EDevSoapResponse;
                    458:                        free(p->http_response);
                    459:                        p->http_response = NULL;
                    460:                        p->http_response_received = 0;
                    461:                        p->http_response_end_of_headers = 0;
                    462:                        /* get response */
                    463:                }
                    464:        }
                    465:        return 0;
                    466: }
                    467: 
                    468: static int upnpc_parse_headers(upnpc_device_t * p)
                    469: {
                    470:        /* search for CR LF CR LF (end of headers)
                    471:         * recognize also LF LF */
                    472:        int i = 0;
                    473:        while(i < (p->http_response_received-1) &&
                    474:              p->http_response_end_of_headers == 0) {
                    475:                if(p->http_response[i] == '\r') {
                    476:                        i++;
                    477:                        if(p->http_response[i] == '\n') {
                    478:                                i++;
                    479:                                if(i < p->http_response_received && p->http_response[i] == '\r') {
                    480:                                        i++;
                    481:                                        if(i < p->http_response_received && p->http_response[i] == '\n') {
                    482:                                                p->http_response_end_of_headers = i + 1;
                    483:                                        }
                    484:                                }
                    485:                        }
                    486:                } else if(p->http_response[i] == '\n') {
                    487:                        i++;
                    488:                        if(p->http_response[i] == '\n') {
                    489:                                p->http_response_end_of_headers = i + 1;
                    490:                        }
                    491:                }
                    492:                i++;
                    493:        }
                    494:        if(p->http_response_end_of_headers != 0) {
                    495:                int colon = 0;
                    496:                int linestart = 0;
                    497:                int valuestart = 0;
                    498:                p->http_response_code = -1;
                    499:                for(i = 0; i < p->http_response_end_of_headers - 1; i++) {
                    500:                        if(linestart == 0) {
                    501:                                /* reading HTTP response code on the 1st line */
                    502:                                if(p->http_response[i] == ' ' && p->http_response_code < 0)
                    503:                                        p->http_response_code = 0;
                    504:                                else if(p->http_response[i] >= '0' && p->http_response[i] <= '9') {
                    505:                                        p->http_response_code = p->http_response_code * 10 + (p->http_response[i] - '0');
                    506:                                } else if(p->http_response[i] == ' ')
                    507:                                        linestart = 1;
                    508:                        }
                    509:                        if(colon <= linestart && p->http_response[i] == ':') {
                    510:                                colon = i;
                    511:                                while(i < p->http_response_end_of_headers - 1 &&
                    512:                                      (p->http_response[i+1] == ' ' || p->http_response[i+1] == '\t'))
                    513:                                        i++;
                    514:                                valuestart = i + 1;
                    515:                        } else if(p->http_response[i + 1] == '\r' ||
                    516:                                  p->http_response[i + 1] == '\n') {
                    517:                                if(colon > linestart && valuestart > colon) {
                    518:                                        debug_printf("header='%.*s', value='%.*s'\n",
                    519:                                               colon-linestart, p->http_response+linestart,
                    520:                                               i+1-valuestart, p->http_response+valuestart);
                    521:                                        if(0==strncasecmp(p->http_response+linestart, "content-length", colon-linestart)) {
                    522:                                                p->http_response_content_length = atoi(p->http_response + valuestart);
                    523:                                                debug_printf("Content-Length: %d\n", p->http_response_content_length);
                    524:                                                if(p->http_response_content_length < 0) {
                    525:                                                        debug_printf("Content-Length overflow ? setting to 0\n");
                    526:                                                        p->http_response_content_length = 0;
                    527:                                                }
                    528:                                        } else if(0==strncasecmp(p->http_response+linestart, "transfer-encoding", colon-linestart)
                    529:                                                   && 0==strncasecmp(p->http_response+valuestart, "chunked", 7)) {
                    530:                                                debug_printf("Chunked transfer-encoding !\n");
                    531:                                                p->http_response_chunked = 1;
                    532:                                        }
                    533:                                }
                    534:                                /* find next line */
                    535:                                while((i < p->http_response_received) &&
                    536:                                      (p->http_response[i]=='\r' || p->http_response[i] == '\n'))
                    537:                                        i++;
                    538:                                linestart = i;
                    539:                                colon = linestart;
                    540:                                valuestart = 0;
                    541:                        }
                    542:                }
                    543:        }
                    544:        return 0;
                    545: }
                    546: 
                    547: static char * build_url_string(const char * urlbase, const char * root_desc_url, const char * controlurl)
                    548: {
                    549:        int l, n;
                    550:        char * s;
                    551:        const char * base;
                    552:        char * p;
                    553:        /* if controlurl is an absolute url, return it */
                    554:        if(0 == memcmp("http://", controlurl, 7))
                    555:                return strdup(controlurl);
                    556:        base = (urlbase[0] == '\0') ? root_desc_url : urlbase;
                    557:        n = strlen(base);
                    558:        if(n > 7) {
                    559:                p = strchr(base + 7, '/');
                    560:                if(p)
                    561:                        n = p - base;
                    562:        }
                    563:        l = n + strlen(controlurl) + 1;
                    564:        if(controlurl[0] != '/')
                    565:                l++;
                    566:        s = malloc(l);
                    567:        if(s == NULL) return NULL;
                    568:        memcpy(s, base, n);
                    569:        if(controlurl[0] != '/')
                    570:                s[n++] = '/';
                    571:        memcpy(s + n, controlurl, l - n);
                    572:        return s;
                    573: }
                    574: 
                    575: static int upnpc_get_response(upnpc_device_t * p)
                    576: {
                    577:        ssize_t n;
                    578:        ssize_t count;
                    579:        char buffer[2048];
                    580:        if(p->http_response_content_length > 0) {
                    581:                count = p->http_response_content_length
                    582:                      + p->http_response_end_of_headers
                    583:                      - p->http_response_received;
                    584:                if(count > (ssize_t)sizeof(buffer)) count = sizeof(buffer);
                    585:        } else {
                    586:                count = sizeof(buffer);
                    587:        }
                    588:        debug_printf("recv(..., %d)\n", (int)count);
                    589:        n = recv(p->http_socket, buffer, count, 0/* flags */);
                    590:        if(n < 0) {
                    591:                if(errno == EINTR || WOULDBLOCK(errno))
                    592:                        return 0;       /* try again later */
                    593:                PRINT_SOCKET_ERROR("read");
                    594:                p->state = EDevError;
                    595:                return -1;
                    596:        } else if(n == 0) {
                    597:                /* receiving finished */
                    598:                debug_printf("%.*s\n", p->http_response_received, p->http_response);
                    599:                close(p->http_socket);
                    600:                p->http_socket = -1;
                    601:                /* parse */
                    602:                if(p->http_response_end_of_headers == 0) {
                    603:                        upnpc_parse_headers(p);
                    604:                }
                    605:                /* TODO : decode chunked transfer-encoding */
                    606:                /* parse xml */
                    607:                if(p->state == EDevGetDescResponse) {
                    608:                        struct IGDdatas igd;
                    609:                        struct xmlparser parser;
                    610:                        memset(&igd, 0, sizeof(struct IGDdatas));
                    611:                        memset(&parser, 0, sizeof(struct xmlparser));
                    612:                        parser.xmlstart = p->http_response + p->http_response_end_of_headers;
                    613:                        parser.xmlsize = p->http_response_received - p->http_response_end_of_headers;
                    614:                        parser.data = &igd;
                    615:                        parser.starteltfunc = IGDstartelt;
                    616:                        parser.endeltfunc = IGDendelt;
                    617:                        parser.datafunc = IGDdata;
                    618:                        parsexml(&parser);
                    619: #ifdef DEBUG
                    620:                        printIGD(&igd);
                    621: #endif /* DEBUG */
                    622:                        p->control_conn_url = build_url_string(igd.urlbase, p->root_desc_location, igd.first.controlurl);
                    623:                        p->control_cif_url = build_url_string(igd.urlbase, p->root_desc_location, igd.CIF.controlurl);
                    624:                        debug_printf("control_conn_url='%s'\n", p->control_conn_url);
                    625:                        debug_printf("control_cif_url='%s'\n", p->control_cif_url);
                    626:                } else {
                    627:                        ClearNameValueList(&p->soap_response_data);
                    628:                        ParseNameValue(p->http_response + p->http_response_end_of_headers,
                    629:                                       p->http_response_received - p->http_response_end_of_headers,
                    630:                                       &p->soap_response_data);
                    631:                }
                    632:                free(p->http_response);
                    633:                p->http_response = NULL;
                    634:                p->http_response_received = 0;
                    635:                p->http_response_end_of_headers = 0;
                    636:                p->state = EDevReady;
                    637:        } else {
                    638:                /* receiving in progress */
                    639:                debug_printf("received %d bytes:\n%.*s\n", (int)n, (int)n, buffer);
                    640:                if(p->http_response == NULL) {
                    641:                        p->http_response = malloc(n);
                    642:                        if(p->http_response == NULL) {
                    643:                                debug_printf("failed to malloc %d bytes\n", (int)n);
                    644:                                p->state = EDevError;
                    645:                                return -1;
                    646:                        }
                    647:                        p->http_response_received = n;
                    648:                        memcpy(p->http_response, buffer, n);
                    649:                } else {
                    650:                        char * tmp = realloc(p->http_response, p->http_response_received + n);
                    651:                        if(tmp == NULL) {
                    652:                                debug_printf("failed to realloc %d bytes\n", (int)(p->http_response_received + n));
                    653:                                p->state = EDevError;
                    654:                                return -1;
                    655:                        }
                    656:                        p->http_response = tmp;
                    657:                        memcpy(p->http_response + p->http_response_received, buffer, n);
                    658:                        p->http_response_received += n;
                    659:                }
                    660:                if(p->http_response_end_of_headers == 0) {
                    661:                        upnpc_parse_headers(p);
                    662:                }
                    663:        }
                    664:        return 0;
                    665: }
                    666: 
                    667: #define SOAPPREFIX "s"
                    668: #define SERVICEPREFIX "u"
                    669: #define SERVICEPREFIX2 'u'
                    670: 
                    671: static int upnpc_build_soap_request(upnpc_device_t * p, const char * url,
                    672:                                     const char * service,
                    673:                                     const char * action,
                    674:                                     const struct upnp_args * args, int arg_count)
                    675: {
                    676:        char * body;
                    677:        const char fmt_soap[] = 
                    678:                "<?xml version=\"1.0\"?>\r\n"
                    679:                "<" SOAPPREFIX ":Envelope "
                    680:                "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" "
                    681:                SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
                    682:                "<" SOAPPREFIX ":Body>"
                    683:                "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">"
                    684:                "%s"
                    685:                "</" SERVICEPREFIX ":%s>"
                    686:                "</" SOAPPREFIX ":Body></" SOAPPREFIX ":Envelope>"
                    687:                "\r\n";
                    688:        int body_len;
                    689:        const char fmt_http[] =
                    690:                "POST %s HTTP/1.1\r\n"
                    691:                "Host: %s%s\r\n"
                    692:                "User-Agent: MiniUPnPc-async\r\n"
                    693:                "Content-Length: %d\r\n"
                    694:                "Content-Type: text/xml\r\n"
                    695:                "SOAPAction: \"%s#%s\"\r\n"
                    696:                "Connection: Close\r\n"
                    697:                "Cache-Control: no-cache\r\n"   /* ??? */
                    698:                "Pragma: no-cache\r\n"
                    699:                "\r\n"
                    700:                "%s";
                    701:        char hostname[MAXHOSTNAMELEN+1];
                    702:        unsigned short port;
                    703:        char * path;
                    704:        unsigned int scope_id;
                    705:        char portstr[8];
                    706:        char * args_xml = NULL;
                    707: 
                    708:        if(arg_count > 0) {
                    709:                int i;
                    710:                size_t l, n;
                    711:                for(i = 0, l = 0; i < arg_count; i++) {
                    712:                        /* <ELT>VAL</ELT> */
                    713:                        l += strlen(args[i].elt) * 2 + strlen(args[i].val) + 5;
                    714:                }
                    715:                args_xml = malloc(++l);
                    716:                if(args_xml == NULL) {
                    717:                        p->state = EDevError;
                    718:                        return -1;
                    719:                }
                    720:                for(i = 0, n = 0; i < arg_count && n < l; i++) {
                    721:                        /* <ELT>VAL</ELT> */
                    722:                        n += snprintf(args_xml + n, l - n, "<%s>%s</%s>",
                    723:                                      args[i].elt, args[i].val, args[i].elt);
                    724:                }
                    725:        }
                    726: 
                    727:        body_len = snprintf(NULL, 0, fmt_soap, action, service, args_xml?args_xml:"", action);
                    728:        body = malloc(body_len + 1);
                    729:        if(body == NULL) {
                    730:                p->state = EDevError;
                    731:                free(args_xml);
                    732:                return -1;
                    733:        }
                    734:        if(snprintf(body, body_len + 1, fmt_soap, action, service, args_xml?args_xml:"", action) != body_len) {
                    735:                debug_printf("snprintf() returned strange value...\n");
                    736:        }
                    737:        free(args_xml);
                    738:        args_xml = NULL;
                    739:        if(!parseURL(url, hostname, &port, &path, &scope_id)) {
                    740:                p->state = EDevError;
                    741:                free(body);
                    742:                return -1;
                    743:        }
                    744:        if(port != 80)
                    745:                snprintf(portstr, sizeof(portstr), ":%hu", port);
                    746:        else
                    747:                portstr[0] = '\0';
                    748:        p->http_request_len = snprintf(NULL, 0, fmt_http,
                    749:                                       path/*url*/, hostname, portstr, body_len, service, action, body);
                    750:        free(p->http_request);
                    751:        p->http_request = malloc(p->http_request_len + 1);
                    752:        if(snprintf(p->http_request, p->http_request_len + 1, fmt_http,
                    753:                    path/*url*/, hostname, portstr, body_len, service, action, body) != p->http_request_len) {
                    754:                debug_printf("snprintf() returned strange value...\n");
                    755:        }
                    756:        free(body);
                    757:        debug_printf("%s", p->http_request);
                    758:        p->http_request_sent = 0;
                    759:        return 0;
                    760: }
                    761: 
                    762: /* public functions */
                    763: int upnpc_init(upnpc_t * p, const char * multicastif)
                    764: {
                    765:        int opt = 1;
                    766:        struct sockaddr_in addr;
                    767:        if(!p)
                    768:                return UPNPC_ERR_INVALID_ARGS;
                    769:        p->state = EUPnPError;
                    770:        memset(p, 0, sizeof(upnpc_t)); /* clean everything */
                    771:        /* open the socket for SSDP */
                    772:        p->ssdp_socket = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
                    773:        if(p->ssdp_socket < 0) {
                    774:                return UPNPC_ERR_SOCKET_FAILED;
                    775:        }
                    776:        /* set REUSEADDR */
                    777: #ifdef WIN32
                    778:        if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt)) < 0) {
                    779: #else
                    780:        if(setsockopt(p->ssdp_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
                    781: #endif
                    782:                /* non fatal error ! */
                    783:        }
                    784:        if(!set_non_blocking(p->ssdp_socket)) {
                    785:                /* TODO log error */
                    786:        }
                    787: 
                    788:        /* receive address */
                    789:        memset(&addr, 0, sizeof(struct sockaddr_in));
                    790:        addr.sin_family = AF_INET;
                    791:        addr.sin_addr.s_addr = INADDR_ANY;
                    792:        /*addr.sin_port = htons(SSDP_PORT);*/
                    793: 
                    794:        if(multicastif) {
                    795:                struct in_addr mc_if;
                    796:                mc_if.s_addr = inet_addr(multicastif);
                    797:        addr.sin_addr.s_addr = mc_if.s_addr;
                    798:                if(setsockopt(p->ssdp_socket, IPPROTO_IP, IP_MULTICAST_IF, (const char *)&mc_if, sizeof(mc_if)) < 0) {
                    799:                        PRINT_SOCKET_ERROR("setsockopt");
                    800:                        /* non fatal error ! */
                    801:                }
                    802:        }
                    803: 
                    804:        /* bind the socket to the ssdp address in order to receive responses */
                    805:        if(bind(p->ssdp_socket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) != 0) {
                    806:                close(p->ssdp_socket);
                    807:                return UPNPC_ERR_BIND_FAILED;
                    808:        }
                    809: 
                    810:        p->state = EUPnPInit;
                    811:        return UPNPC_OK;
                    812: }
                    813: 
                    814: int upnpc_finalize(upnpc_t * p)
                    815: {
                    816:        if(!p) return UPNPC_ERR_INVALID_ARGS;
                    817:        if(p->ssdp_socket >= 0) {
                    818:                close(p->ssdp_socket);
                    819:                p->ssdp_socket = -1;
                    820:        }
                    821:        while(p->device_list) {
                    822:                upnpc_device_t * next = p->device_list->next;
                    823:                free(p->device_list->root_desc_location);
                    824:                p->device_list->root_desc_location = NULL;
                    825:                free(p->device_list->http_request);
                    826:                p->device_list->http_request = NULL;
                    827:                free(p->device_list->http_response);
                    828:                p->device_list->http_response = NULL;
                    829:                free(p->device_list->control_cif_url);
                    830:                p->device_list->control_cif_url = NULL;
                    831:                free(p->device_list->control_conn_url);
                    832:                p->device_list->control_conn_url = NULL;
                    833:                if(p->device_list->http_socket >= 0) {
                    834:                        close(p->device_list->http_socket);
                    835:                        p->device_list->http_socket = -1;
                    836:                }
                    837:                ClearNameValueList(&p->device_list->soap_response_data);
                    838:                free(p->device_list);
                    839:                p->device_list = next;
                    840:        }
                    841:        p->state = EUPnPFinalized;
                    842:        return UPNPC_OK;
                    843: }
                    844: 
                    845: int upnpc_get_external_ip_address(upnpc_device_t * p)
                    846: {
                    847:        upnpc_build_soap_request(p, p->control_conn_url,
                    848:                                 "urn:schemas-upnp-org:service:WANIPConnection:1",
                    849:                                 "GetExternalIPAddress", NULL, 0);
                    850:        p->state = EDevSoapConnect;
                    851:        upnpc_connect(p, p->control_conn_url);
                    852:        return 0;
                    853: }
                    854: 
                    855: int upnpc_get_link_layer_max_rate(upnpc_device_t * p)
                    856: {
                    857:        upnpc_build_soap_request(p, p->control_cif_url,
                    858:                                 "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1",
                    859:                                 "GetCommonLinkProperties", NULL, 0);
                    860:        p->state = EDevSoapConnect;
                    861:        upnpc_connect(p, p->control_conn_url);
                    862:        return 0;
                    863: }
                    864: 
                    865: int upnpc_add_port_mapping(upnpc_device_t * p,
                    866:                            const char * remote_host, unsigned short ext_port,
                    867:                            unsigned short int_port, const char * int_client,
                    868:                            const char * proto, const char * description,
                    869:                            unsigned int lease_duration)
                    870: {
                    871:        struct upnp_args args[8];
                    872:        char lease_duration_str[16];
                    873:        char int_port_str[8];
                    874:        char ext_port_str[8];
                    875: 
                    876:        if(int_client == NULL || int_port == 0 || ext_port == 0 || proto == NULL)
                    877:                return UPNPC_ERR_INVALID_ARGS;
                    878:        snprintf(lease_duration_str, sizeof(lease_duration_str), "%u", lease_duration);
                    879:        snprintf(int_port_str, sizeof(int_port_str), "%hu", int_port);
                    880:        snprintf(ext_port_str, sizeof(ext_port_str), "%hu", ext_port);
                    881:        args[0].elt = "NewRemoteHost";
                    882:        args[0].val = remote_host?remote_host:"";
                    883:        args[1].elt = "NewExternalPort";
                    884:        args[1].val = ext_port_str;
                    885:        args[2].elt = "NewProtocol";
                    886:        args[2].val = proto;
                    887:        args[3].elt = "NewInternalPort";
                    888:        args[3].val = int_port_str;
                    889:        args[4].elt = "NewInternalClient";
                    890:        args[4].val = int_client;
                    891:        args[5].elt = "NewEnabled";
                    892:        args[5].val = "1";
                    893:        args[6].elt = "NewPortMappingDescription";
                    894:        args[6].val = description?description:"miniupnpc-async";
                    895:        args[7].elt = "NewLeaseDuration";
                    896:        args[7].val = lease_duration_str;
                    897:        upnpc_build_soap_request(p, p->control_conn_url,
                    898:                                 "urn:schemas-upnp-org:service:WANIPConnection:1",
                    899:                                 "AddPortMapping",
                    900:                                 args, 8);
                    901:        p->state = EDevSoapConnect;
                    902:        upnpc_connect(p, p->control_conn_url);
                    903:        return 0;
                    904: }
                    905: 
                    906: #ifdef UPNPC_USE_SELECT
                    907: int upnpc_select_fds(upnpc_t * p, int * nfds, fd_set * readfds, fd_set * writefds)
                    908: {
                    909:        upnpc_device_t * d;
                    910:        int n = 0;
                    911:        if(!p) return UPNPC_ERR_INVALID_ARGS;
                    912:        for(d = p->device_list; d != NULL; d = d->next) {
                    913:                switch(d->state) {
                    914:                case EDevGetDescConnect:
                    915:                case EDevGetDescRequest:
                    916:                case EDevSoapConnect:
                    917:                case EDevSoapRequest:
                    918:                        FD_SET(d->http_socket, writefds);
                    919:                        if(*nfds < d->http_socket)
                    920:                                *nfds = d->http_socket;
                    921:                        n++;
                    922:                        break;
                    923:                case EDevGetDescResponse:
                    924:                case EDevSoapResponse:
                    925:                        FD_SET(d->http_socket, readfds);
                    926:                        if(*nfds < d->http_socket)
                    927:                                *nfds = d->http_socket;
                    928:                        n++;
                    929:                        break;
                    930:                default:
                    931:                        break;
                    932:                }
                    933:        }
                    934: 
                    935:        switch(p->state) {
                    936:        case EUPnPSendSSDP:
                    937:                FD_SET(p->ssdp_socket, writefds);
                    938:                if(*nfds < p->ssdp_socket)
                    939:                        *nfds = p->ssdp_socket;
                    940:                n++;
                    941:                break;
                    942:        case EUPnPReceiveSSDP:
                    943:        default:
                    944:                /* still receive SSDP responses when processing Description, etc. */
                    945:                FD_SET(p->ssdp_socket, readfds);
                    946:                if(*nfds < p->ssdp_socket)
                    947:                        *nfds = p->ssdp_socket;
                    948:                n++;
                    949:                break;
                    950:        }
                    951:        return n;
                    952: }
                    953: 
                    954: void upnpc_check_select_fds(upnpc_t * p, const fd_set * readfds, const fd_set * writefds)
                    955: {
                    956:        upnpc_device_t * d;
                    957: 
                    958:        p->socket_flags = 0;
                    959:        if(FD_ISSET(p->ssdp_socket, readfds))
                    960:                p->socket_flags = UPNPC_SSDP_READABLE;
                    961:        if(FD_ISSET(p->ssdp_socket, writefds))
                    962:                p->socket_flags = UPNPC_SSDP_WRITEABLE;
                    963: 
                    964:        for(d = p->device_list; d != NULL; d = d->next) {
                    965:                d->socket_flags = 0;
                    966:                if(FD_ISSET(d->http_socket, readfds))
                    967:                        d->socket_flags = UPNPC_HTTP_READABLE;
                    968:                if(FD_ISSET(d->http_socket, writefds))
                    969:                        d->socket_flags = UPNPC_HTTP_WRITEABLE;
                    970:        }
                    971: }
                    972: #endif
                    973: 
                    974: static const char * devices_to_search[] = {
                    975:        "urn:schemas-upnp-org:device:InternetGatewayDevice:1",
                    976:        "urn:schemas-upnp-org:service:WANIPConnection:1",
                    977:        "urn:schemas-upnp-org:service:WANPPPConnection:1",
                    978:        "upnp:rootdevice",
                    979:        0
                    980: };
                    981: 
                    982: int upnpc_process(upnpc_t * p)
                    983: {
                    984:        upnpc_device_t * d;
                    985: /*
                    986: 1)     Envoyer les paquets de discovery SSDP
                    987: 2)     Recevoir et traiter les reponses
                    988: 3)     recup les descriptions
                    989: 4)     tester les etats
                    990: TODO : translate comments to English
                    991: */
                    992:        if(!p) return UPNPC_ERR_INVALID_ARGS;
                    993:        debug_printf("state=%d   socket_flags=0x%04x\n", (int)p->state, p->socket_flags);
                    994: 
                    995:        for(d = p->device_list; d != NULL; d = d->next) {
                    996:                switch(d->state) {
                    997:                case EDevGetDescConnect:
                    998:                case EDevSoapConnect:
                    999:                        upnpc_complete_connect(d);
                   1000:                        break;
                   1001:                case EDevGetDescRequest:
                   1002:                case EDevSoapRequest:
                   1003:                        upnpc_send_request(d);
                   1004:                        break;
                   1005:                case EDevGetDescResponse:
                   1006:                case EDevSoapResponse:
                   1007:                        upnpc_get_response(d);
                   1008:                        break;
                   1009:                default:
                   1010:                        break;
                   1011:                }
                   1012:        }
                   1013:        /* all devices ready => ready */
                   1014:        if(p->device_list != NULL) {
                   1015:                d = p->device_list;
                   1016:                while(d && d->state == EDevReady) d = d->next;
                   1017:                p->state = (d == NULL) ? EUPnPReady : EUPnPProcessing;
                   1018:        }
                   1019: 
                   1020:        if(p->socket_flags & UPNPC_SSDP_READABLE) {
                   1021:                upnpc_receive_and_parse_ssdp(p);
                   1022:        }
                   1023:        switch(p->state) {
                   1024:        case EUPnPInit:
                   1025:                upnpc_send_ssdp_msearch(p, devices_to_search[0], 2);
                   1026:                break;
                   1027:        case EUPnPSendSSDP:
                   1028:                upnpc_send_ssdp_msearch(p, devices_to_search[0], 2);
                   1029:                break;
                   1030:        case EUPnPReceiveSSDP:
                   1031:                /*upnpc_receive_and_parse_ssdp(p);*/
                   1032:                break;
                   1033:        /*case EGetDesc:
                   1034:                upnpc_connect(p);
                   1035:                break;*/
                   1036:        case EUPnPReady:
                   1037:        case EUPnPProcessing:
                   1038:                break;
                   1039:        default:
                   1040:                return UPNPC_ERR_UNKNOWN_STATE;
                   1041:        }
                   1042:        return UPNPC_OK;
                   1043: }
                   1044: 

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>