/* $Id: miniupnpc.c,v 1.57 2008/12/18 17:46:36 nanard Exp $ */ /* Project : miniupnp * Author : Thomas BERNARD * copyright (c) 2005-2007 Thomas Bernard * This software is subjet to the conditions detailed in the * provided LICENCE file. */ #include #include #include #ifdef WIN32 /* Win32 Specific includes and defines */ #include #include #include #include #define snprintf _snprintf #if defined(_MSC_VER) && (_MSC_VER >= 1400) #define strncasecmp _memicmp #else #define strncasecmp memicmp #endif #define MAXHOSTNAMELEN 64 #else /* Standard POSIX includes */ #include #include #include #include #include #include #include #include #include #define closesocket close #endif #include "miniupnpc.h" #include "minissdpc.h" #include "miniwget.h" #include "minisoap.h" #include "minixml.h" #include "upnpcommands.h" #ifdef WIN32 #define PRINT_SOCKET_ERROR(x) printf("Socket error: %s, %d\n", x, WSAGetLastError()); #else #define PRINT_SOCKET_ERROR(x) perror(x) #endif #define SOAPPREFIX "s" #define SERVICEPREFIX "u" #define SERVICEPREFIX2 'u' /* root description parsing */ void parserootdesc (const char *buffer, int bufsize, struct IGDdatas *data) { struct xmlparser parser; /* xmlparser object */ parser.xmlstart = buffer; parser.xmlsize = bufsize; parser.data = data; parser.starteltfunc = IGDstartelt; parser.endeltfunc = IGDendelt; parser.datafunc = IGDdata; parser.attfunc = 0; parsexml (&parser); #ifdef DEBUG printIGD (data); #endif } /* Content-length: nnn */ static int getcontentlenfromline (const char *p, int n) { static const char contlenstr[] = "content-length"; const char *p2 = contlenstr; int a = 0; while (*p2) { if (n == 0) return -1; if (*p2 != *p && *p2 != (*p + 32)) return -1; p++; p2++; n--; } if (n == 0) return -1; if (*p != ':') return -1; p++; n--; while (*p == ' ') { if (n == 0) return -1; p++; n--; } while (*p >= '0' && *p <= '9') { if (n == 0) return -1; a = (a * 10) + (*p - '0'); p++; n--; } return a; } static void getContentLengthAndHeaderLength (char *p, int n, int *contentlen, int *headerlen) { char *line; int linelen; int r; line = p; while (line < p + n) { linelen = 0; while (line[linelen] != '\r' && line[linelen] != '\r') { if (line + linelen >= p + n) return; linelen++; } r = getcontentlenfromline (line, linelen); if (r > 0) *contentlen = r; line = line + linelen + 2; if (line[0] == '\r' && line[1] == '\n') { *headerlen = (line - p) + 2; return; } } } /* simpleUPnPcommand : * not so simple ! * return values : * 0 - OK * -1 - error */ int simpleUPnPcommand (int s, const char *url, const char *service, const char *action, struct UPNParg *args, char *buffer, int *bufsize) { struct sockaddr_in dest; struct sockaddr_in6 dest6; char hostname[MAXHOSTNAMELEN + 1]; unsigned short port = 0; char *path; char soapact[128]; char soapbody[2048]; char *buf; int buffree; int n; int err; int contentlen, headerlen; /* for the response */ snprintf (soapact, sizeof (soapact), "%s#%s", service, action); if (args == NULL) { /*soapbodylen = */ snprintf (soapbody, sizeof (soapbody), "\r\n" "<" SOAPPREFIX ":Envelope " "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<" SOAPPREFIX ":Body>" "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">" "" "" "\r\n", action, service, action); } else { char *p; const char *pe, *pv; int soapbodylen; soapbodylen = snprintf (soapbody, sizeof (soapbody), "\r\n" "<" SOAPPREFIX ":Envelope " "xmlns:" SOAPPREFIX "=\"http://schemas.xmlsoap.org/soap/envelope/\" " SOAPPREFIX ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<" SOAPPREFIX ":Body>" "<" SERVICEPREFIX ":%s xmlns:" SERVICEPREFIX "=\"%s\">", action, service); p = soapbody + soapbodylen; while (args->elt) { /* check that we are never overflowing the string... */ if (soapbody + sizeof (soapbody) <= p + 100) { /* we keep a margin of at least 100 bytes */ *bufsize = 0; return -1; } *(p++) = '<'; pe = args->elt; while (*pe) *(p++) = *(pe++); *(p++) = '>'; if ((pv = args->val)) { while (*pv) *(p++) = *(pv++); } *(p++) = '<'; *(p++) = '/'; pe = args->elt; while (*pe) *(p++) = *(pe++); *(p++) = '>'; args++; } *(p++) = '<'; *(p++) = '/'; *(p++) = SERVICEPREFIX2; *(p++) = ':'; pe = action; while (*pe) *(p++) = *(pe++); strncpy (p, ">\r\n", soapbody + sizeof (soapbody) - p); } if (!parseURL (url, hostname, &port, &path)) return -1; if (s < 0) { /* Test IPv4 address, else use IPv6 */ if (inet_pton (AF_INET, hostname, &dest.sin_addr) == 1) { memset (&dest, 0, sizeof (dest)); dest.sin_family = AF_INET; dest.sin_port = htons (port); #ifdef HAVE_SOCKADDR_IN_SIN_LEN dest.sin_len = sizeof (dest); #endif if ((s = socket (PF_INET, SOCK_STREAM, 0)) < 0) { PRINT_SOCKET_ERROR ("socket"); *bufsize = 0; return -1; } err = connect (s, (struct sockaddr *) &dest, sizeof (dest)); } else if (inet_pton (AF_INET6, hostname, &dest6.sin6_addr) == 1) { memset (&dest6, 0, sizeof (dest6)); dest6.sin6_family = AF_INET6; dest6.sin6_port = htons (port); #ifdef HAVE_SOCKADDR_IN_SIN_LEN dest6.sin6_len = sizeof (dest6); #endif if ((s = socket (PF_INET6, SOCK_STREAM, 0)) < 0) { PRINT_SOCKET_ERROR ("socket"); *bufsize = 0; return -1; } err = connect (s, (struct sockaddr *) &dest6, sizeof (dest6)); } else { PRINT_SOCKET_ERROR ("inet_pton"); if (s > 0) closesocket (s); *bufsize = 0; return -1; } if (err < 0) { PRINT_SOCKET_ERROR ("connect"); closesocket (s); *bufsize = 0; return -1; } } n = soapPostSubmit (s, path, hostname, port, soapact, soapbody); if (n <= 0) { #ifdef DEBUG printf ("Error sending SOAP request\n"); #endif closesocket (s); return -1; } contentlen = -1; headerlen = -1; buf = buffer; buffree = *bufsize; *bufsize = 0; while ((n = ReceiveData (s, buf, buffree, 5000)) > 0) { buffree -= n; buf += n; *bufsize += n; getContentLengthAndHeaderLength (buffer, *bufsize, &contentlen, &headerlen); #ifdef DEBUG printf ("received n=%dbytes bufsize=%d ContLen=%d HeadLen=%d\n", n, *bufsize, contentlen, headerlen); #endif /* break if we received everything */ if (contentlen > 0 && headerlen > 0 && *bufsize >= contentlen + headerlen) break; } closesocket (s); return 0; } /* parseMSEARCHReply() * the last 4 arguments are filled during the parsing : * - location/locationsize : "location:" field of the SSDP reply packet * - st/stsize : "st:" field of the SSDP reply packet. * The strings are NOT null terminated */ static void parseMSEARCHReply (const char *reply, int size, const char **location, int *locationsize, const char **st, int *stsize) { int a, b, i; i = 0; a = i; /* start of the line */ b = 0; while (i < size) { switch (reply[i]) { case ':': if (b == 0) { b = i; /* end of the "header" */ /*for(j=a; jsa_family == AF_INET) domain = PF_INET; else if (addr && addr->sa_family == AF_INET6) domain = PF_INET6; else if (addr) return NULL; /* fallback to direct discovery */ #ifdef WIN32 sudp = socket (domain, SOCK_DGRAM, IPPROTO_UDP); #else sudp = socket (domain, SOCK_DGRAM, 0); #endif if (sudp < 0) { PRINT_SOCKET_ERROR ("socket"); return NULL; } if (domain == PF_INET) { /* receive */ memset (&sockudp_r, 0, sizeof (struct sockaddr_in)); sockudp_r.sin_family = AF_INET; #ifdef HAVE_SOCKADDR_IN_SIN_LEN sockudp_r.sin_len = sizeof (struct sockaddr_in); #endif if (sameport) sockudp_r.sin_port = htons (PORT); sockudp_r.sin_addr.s_addr = INADDR_ANY; /* send */ memset (&sockudp_w, 0, sizeof (struct sockaddr_in)); sockudp_w.sin_family = AF_INET; sockudp_w.sin_port = htons (PORT); sockudp_w.sin_addr.s_addr = inet_addr (UPNP_MCAST_ADDR); #ifdef HAVE_SOCKADDR_IN_SIN_LEN sockudp_w.sin_len = sizeof (struct sockaddr_in); #endif } else { /* receive */ memcpy (&sockudp6_r, addr, sizeof (struct sockaddr_in6)); if (sameport) sockudp6_r.sin6_port = htons (PORT); else sockudp6_r.sin6_port = 0; sockudp6_r.sin6_addr = any_addr; #ifdef HAVE_SOCKADDR_IN_SIN_LEN sockudp6_r.sin6_len = sizeof (struct sockaddr_in6); #endif /* send */ memset (&sockudp6_w, 0, sizeof (struct sockaddr_in6)); sockudp6_w.sin6_family = AF_INET6; sockudp6_w.sin6_port = htons (PORT); if (inet_pton (AF_INET6, UPNP_MCAST_ADDR6, &sockudp6_w.sin6_addr) != 1) { PRINT_SOCKET_ERROR ("inet_pton"); return NULL; } #ifdef HAVE_SOCKADDR_IN_SIN_LEN sockudp6_w.sin6_len = sizeof (struct sockaddr_in6); #endif } #ifdef WIN32 if (setsockopt (sudp, SOL_SOCKET, SO_REUSEADDR, (const char *) &opt, sizeof (opt)) < 0) #else if (setsockopt (sudp, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)) < 0) #endif { PRINT_SOCKET_ERROR ("setsockopt"); return NULL; } if (addr) { if (domain == PF_INET) { sockudp_r.sin_addr.s_addr = ((struct sockaddr_in *) addr)->sin_addr.s_addr; if (setsockopt (sudp, IPPROTO_IP, IP_MULTICAST_IF, (const char *) &sockudp_r.sin_addr, sizeof (struct in_addr)) < 0) { PRINT_SOCKET_ERROR ("setsockopt"); } /* Bind to receive response before sending packet */ if (bind (sudp, (struct sockaddr *) &sockudp_r, sizeof (struct sockaddr_in)) != 0) { PRINT_SOCKET_ERROR ("bind"); closesocket (sudp); return NULL; } } else { if (multicastif) { if_index = if_nametoindex (multicastif); if (!if_index) PRINT_SOCKET_ERROR ("if_nametoindex"); if (setsockopt (sudp, IPPROTO_IPV6, IPV6_MULTICAST_IF, &if_index, sizeof (if_index)) < 0) { PRINT_SOCKET_ERROR ("setsockopt"); } } /* Bind to receive response before sending packet */ memcpy (&sockudp6_r.sin6_addr, &((struct sockaddr_in6 *) addr)->sin6_addr, sizeof (sockudp6_r.sin6_addr)); if (bind (sudp, (struct sockaddr *) &sockudp6_r, sizeof (struct sockaddr_in6)) != 0) { PRINT_SOCKET_ERROR ("bind"); closesocket (sudp); return NULL; } } } /* receiving SSDP response packet */ for (n = 0;;) { if (n == 0) { /* sending the SSDP M-SEARCH packet */ n = snprintf (bufr, sizeof (bufr), MSearchMsgFmt, deviceList[deviceIndex++]); /*printf("Sending %s", bufr); */ if (domain == PF_INET) n = sendto (sudp, bufr, n, 0, (struct sockaddr *) &sockudp_w, sizeof (struct sockaddr_in)); else n = sendto (sudp, bufr, n, 0, (struct sockaddr *) &sockudp6_w, sizeof (struct sockaddr_in6)); if (n < 0) { PRINT_SOCKET_ERROR ("sendto"); closesocket (sudp); return devlist; } } /* Waiting for SSDP REPLY packet to M-SEARCH */ n = ReceiveData (sudp, bufr, sizeof (bufr), delay); if (n < 0) { /* error */ closesocket (sudp); return devlist; } else if (n == 0) { /* no data or Time Out */ if (devlist || (deviceList[deviceIndex] == 0)) { /* no more device type to look for... */ closesocket (sudp); return devlist; } } else { const char *descURL = NULL; int urlsize = 0; const char *st = NULL; int stsize = 0; /*printf("%d byte(s) :\n%s\n", n, bufr); *//* affichage du message */ parseMSEARCHReply (bufr, n, &descURL, &urlsize, &st, &stsize); if (st && descURL) { /*printf("M-SEARCH Reply:\nST: %.*s\nLocation: %.*s\n", stsize, st, urlsize, descURL); */ tmp = (struct UPNPDev *) malloc (sizeof (struct UPNPDev) + urlsize + stsize); tmp->pNext = devlist; tmp->descURL = tmp->buffer; tmp->st = tmp->buffer + 1 + urlsize; memcpy (tmp->buffer, descURL, urlsize); tmp->buffer[urlsize] = '\0'; memcpy (tmp->buffer + urlsize + 1, st, stsize); tmp->buffer[urlsize + 1 + stsize] = '\0'; devlist = tmp; } } } } /* freeUPNPDevlist() should be used to * free the chained list returned by upnpDiscover() */ void freeUPNPDevlist (struct UPNPDev *devlist) { struct UPNPDev *next; while (devlist) { next = devlist->pNext; free (devlist); devlist = next; } } static void url_cpy_or_cat (char *dst, const char *src, int n) { if ((src[0] == 'h') && (src[1] == 't') && (src[2] == 't') && (src[3] == 'p') && (src[4] == ':') && (src[5] == '/') && (src[6] == '/')) { strncpy (dst, src, n); } else { int l = strlen (dst); if (src[0] != '/') dst[l++] = '/'; if (l <= n) strncpy (dst + l, src, n - l); } } /* Prepare the Urls for usage... */ void GetUPNPUrls (struct UPNPUrls *urls, struct IGDdatas *data, const char *descURL) { char *p; int n1, n2, n3; n1 = strlen (data->urlbase); if (n1 == 0) n1 = strlen (descURL); n1 += 2; /* 1 byte more for Null terminator, 1 byte for '/' if needed */ n2 = n1; n3 = n1; n1 += strlen (data->scpdurl); n2 += strlen (data->controlurl); n3 += strlen (data->controlurl_CIF); urls->ipcondescURL = (char *) malloc (n1); urls->controlURL = (char *) malloc (n2); urls->controlURL_CIF = (char *) malloc (n3); /* maintenant on chope la desc du WANIPConnection */ if (data->urlbase[0] != '\0') strncpy (urls->ipcondescURL, data->urlbase, n1); else strncpy (urls->ipcondescURL, descURL, n1); p = strchr (urls->ipcondescURL + 7, '/'); if (p) p[0] = '\0'; strncpy (urls->controlURL, urls->ipcondescURL, n2); strncpy (urls->controlURL_CIF, urls->ipcondescURL, n3); url_cpy_or_cat (urls->ipcondescURL, data->scpdurl, n1); url_cpy_or_cat (urls->controlURL, data->controlurl, n2); url_cpy_or_cat (urls->controlURL_CIF, data->controlurl_CIF, n3); #ifdef DEBUG printf ("urls->ipcondescURL='%s' %d n1=%d\n", urls->ipcondescURL, strlen (urls->ipcondescURL), n1); printf ("urls->controlURL='%s' %d n2=%d\n", urls->controlURL, strlen (urls->controlURL), n2); printf ("urls->controlURL_CIF='%s' %d n3=%d\n", urls->controlURL_CIF, strlen (urls->controlURL_CIF), n3); #endif } void FreeUPNPUrls (struct UPNPUrls *urls) { if (!urls) return; free (urls->controlURL); urls->controlURL = 0; free (urls->ipcondescURL); urls->ipcondescURL = 0; free (urls->controlURL_CIF); urls->controlURL_CIF = 0; } int ReceiveData (int socket, char *data, int length, int timeout) { int n; #ifndef WIN32 struct pollfd fds[1]; /* for the poll */ fds[0].fd = socket; fds[0].events = POLLIN; n = poll (fds, 1, timeout); if (n < 0) { PRINT_SOCKET_ERROR ("poll"); return -1; } else if (n == 0) { return 0; } #else fd_set socketSet; TIMEVAL timeval; FD_ZERO (&socketSet); FD_SET (socket, &socketSet); timeval.tv_sec = timeout / 1000; timeval.tv_usec = (timeout % 1000) * 1000; /*n = select(0, &socketSet, NULL, NULL, &timeval); */ n = select (FD_SETSIZE, &socketSet, NULL, NULL, &timeval); if (n < 0) { PRINT_SOCKET_ERROR ("select"); return -1; } else if (n == 0) { return 0; } #endif n = recv (socket, data, length, 0); if (n < 0) { PRINT_SOCKET_ERROR ("recv"); } return n; } int UPNPIGD_IsConnected (struct UPNPUrls *urls, struct IGDdatas *data) { char status[64]; unsigned int uptime; status[0] = '\0'; UPNP_GetStatusInfo (urls->controlURL, data->servicetype, status, &uptime, NULL); if (0 == strcmp ("Connected", status)) { return 1; } else return 0; } /* UPNP_GetValidIGD() : * return values : * 0 = NO IGD found * 1 = A valid connected IGD has been found * 2 = A valid IGD has been found but it reported as * not connected * 3 = an UPnP device has been found but was not recognized as an IGD * * In any non zero return case, the urls and data structures * passed as parameters are set. Donc forget to call FreeUPNPUrls(urls) to * free allocated memory. */ int UPNP_GetValidIGD (struct UPNPDev *devlist, struct UPNPUrls *urls, struct IGDdatas *data, char *lanaddr, int lanaddrlen) { char *descXML; int descXMLsize = 0; struct UPNPDev *dev; int ndev = 0; int state; /* state 1 : IGD connected. State 2 : IGD. State 3 : anything */ if (!devlist) { #ifdef DEBUG printf ("Empty devlist\n"); #endif return 0; } for (state = 1; state <= 3; state++) { for (dev = devlist; dev; dev = dev->pNext) { /* we should choose an internet gateway device. * with st == urn:schemas-upnp-org:device:InternetGatewayDevice:1 */ descXML = miniwget_getaddr (dev->descURL, &descXMLsize, lanaddr, lanaddrlen); if (descXML) { ndev++; memset (data, 0, sizeof (struct IGDdatas)); memset (urls, 0, sizeof (struct UPNPUrls)); parserootdesc (descXML, descXMLsize, data); free (descXML); descXML = NULL; if (0 == strcmp (data->servicetype_CIF, "urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1") || state >= 3) { GetUPNPUrls (urls, data, dev->descURL); #ifdef DEBUG printf ("UPNPIGD_IsConnected(%s) = %d\n", urls->controlURL, UPNPIGD_IsConnected (urls, data)); #endif if ((state >= 2) || UPNPIGD_IsConnected (urls, data)) return state; FreeUPNPUrls (urls); } memset (data, 0, sizeof (struct IGDdatas)); } #ifdef DEBUG else { printf ("error getting XML description %s\n", dev->descURL); } #endif } } return 0; } /* UPNP_GetIGDFromUrl() * Used when skipping the discovery process. * return value : * 0 - Not ok * 1 - OK */ int UPNP_GetIGDFromUrl (const char *rootdescurl, struct UPNPUrls *urls, struct IGDdatas *data, char *lanaddr, int lanaddrlen) { char *descXML; int descXMLsize = 0; descXML = miniwget_getaddr (rootdescurl, &descXMLsize, lanaddr, lanaddrlen); if (descXML) { memset (data, 0, sizeof (struct IGDdatas)); memset (urls, 0, sizeof (struct UPNPUrls)); parserootdesc (descXML, descXMLsize, data); free (descXML); descXML = NULL; GetUPNPUrls (urls, data, rootdescurl); return 1; } else { return 0; } }