diff options
Diffstat (limited to 'miranda-wine/src/modules/netlib/netlibupnp.c')
-rw-r--r-- | miranda-wine/src/modules/netlib/netlibupnp.c | 495 |
1 files changed, 495 insertions, 0 deletions
diff --git a/miranda-wine/src/modules/netlib/netlibupnp.c b/miranda-wine/src/modules/netlib/netlibupnp.c new file mode 100644 index 0000000..c91de6c --- /dev/null +++ b/miranda-wine/src/modules/netlib/netlibupnp.c @@ -0,0 +1,495 @@ +/* +UPnP plugin for Miranda IM +Copyright (C) 2006 borkra + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* Main file for the Weather Protocol, includes loading, unloading, + upgrading, support for plugin uninsaller, and anything that doesn't + belong to any other file. +*/ + +#include "commonheaders.h" +#include "netlib.h" + +static char search_request_msg[] = + "M-SEARCH * HTTP/1.1\r\n" + "MX: 2\r\n" + "HOST: 239.255.255.250:1900\r\n" + "MAN: \"ssdp:discover\"\r\n" + "ST: urn:schemas-upnp-org:service:%s\r\n" + "\r\n"; + +static char xml_get_hdr[] = + "GET %s HTTP/1.1\r\n" + "Connection: close\r\n" + "Host: %s:%s\r\n\r\n"; + +static char soap_post_hdr[] = + "POST %s HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CONTENT-LENGTH: %u\r\n" + "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" + "SOAPACTION: \"%s#%s\"\r\n\r\n" + "%s"; + +static char soap_post_hdr_m[] = + "M-POST %s URL HTTP/1.1\r\n" + "HOST: %s:%s\r\n" + "CONTENT-LENGTH: %u\r\n" + "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n" + "MAN: \"http://schemas.xmlsoap.org/soap/envelope/\"; ns=01\r\n" + "01-SOAPACTION: \"%s#%s\"\r\n\r\n" + "%s"; + +static char search_device[] = + "<serviceType>%s</serviceType>"; + +static char soap_action[] = + "<s:Envelope\r\n" + " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"\r\n" + " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" + " <s:Body>\r\n" + " <u:%s xmlns:u=\"%s\">\r\n" + "%s" + " </u:%s>\r\n" + " </s:Body>\r\n" + "</s:Envelope>\r\n"; + +static char add_port_mapping[] = + " <NewRemoteHost></NewRemoteHost>\r\n" + " <NewExternalPort>%i</NewExternalPort>\r\n" + " <NewProtocol>%s</NewProtocol>\r\n" + " <NewInternalPort>%i</NewInternalPort>\r\n" + " <NewInternalClient>%s</NewInternalClient>\r\n" + " <NewEnabled>1</NewEnabled>\r\n" + " <NewPortMappingDescription>Miranda</NewPortMappingDescription>\r\n" + " <NewLeaseDuration>0</NewLeaseDuration>\r\n"; + +static char delete_port_mapping[] = + " <NewRemoteHost></NewRemoteHost>\r\n" + " <NewExternalPort>%i</NewExternalPort>\r\n" + " <NewProtocol>%s</NewProtocol>\r\n"; + +static char default_http_port[] = "80"; + +static BOOL gatewayFound = FALSE; +static SOCKADDR_IN locIP; +static time_t lastDiscTime = 0; +static int expireTime = 120; + +static char szCtlUrl[256], szDev[256]; + + +static BOOL txtParseParam(char* szData, char* presearch, + char* start, char* finish, char* param, int size) +{ + char *cp, *cp1; + int len; + + *param = 0; + + if (presearch != NULL) + { + cp1 = strstr(szData, presearch); + if (cp1 == NULL) return FALSE; + } + else + cp1 = szData; + + cp = strstr(cp1, start); + if (cp == NULL) return FALSE; + cp += strlen(start); + while (*cp == ' ') ++cp; + + cp1 = strstr(cp, finish); + if (cp1 == NULL) return FALSE; + while (*(cp1-1) == ' ' && cp1 > cp) --cp1; + + len = min(cp1 - cp, size); + strncpy(param, cp, len); + param[len] = 0; + + return TRUE; +} + +static LongLog(char* szData) +{ + char* buf = szData; + int sz = strlen(szData); + + while ( sz > 1000) + { + char* nbuf = buf + 1000; + char t = *nbuf; + *nbuf = 0; + Netlib_Logf(NULL, buf); + *nbuf = t; + buf = nbuf; + sz -= 1000; + } + Netlib_Logf(NULL, buf); +} + + +static void discoverUPnP(char* szUrl, int sizeUrl) +{ + char* buf; + int buflen; + unsigned i, j, nip = 0; + char* szData = NULL; + unsigned* ips = NULL; + + static const unsigned any = INADDR_ANY; + fd_set readfd; + TIMEVAL tv = { 1, 0 }; + + char hostname[256]; + PHOSTENT he; + + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + + SOCKADDR_IN enetaddr; + enetaddr.sin_family = AF_INET; + enetaddr.sin_port = htons(1900); + enetaddr.sin_addr.s_addr = inet_addr("239.255.255.250"); + + FD_ZERO(&readfd); + FD_SET(sock, &readfd); + + szUrl[0] = 0; + + gethostname( hostname, sizeof( hostname )); + he = gethostbyname( hostname ); + + if (he) + { + while(he->h_addr_list[nip]) ++nip; + + ips = mir_alloc(nip * sizeof(unsigned)); + + for (j=0; j<nip; ++j) + ips[j] = *(unsigned*)he->h_addr_list[j]; + } + + buf = mir_alloc(1500); + + for(i = 3; --i && szUrl[0] == 0;) + { + for (j=0; j<nip; ++j) + { + if (ips) + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&ips[j], sizeof(unsigned)); + + buflen = mir_snprintf(buf, 1500, search_request_msg, "WANIPConnection:1"); + sendto(sock, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); + LongLog(buf); + + buflen = mir_snprintf(buf, 1500, search_request_msg, "WANPPPConnection:1"); + sendto(sock, buf, buflen, 0, (SOCKADDR*)&enetaddr, sizeof(enetaddr)); + LongLog(buf); + } + + while (select(0, &readfd, NULL, NULL, &tv) == 1) + { + buflen = recv(sock, buf, 1500, 0); + if (buflen != SOCKET_ERROR) + { + buf[buflen] = 0; + LongLog(buf); + + if (txtParseParam(buf, NULL, "LOCATION:", "\r", szUrl, sizeUrl) || + txtParseParam(buf, NULL, "Location:", "\r", szUrl, sizeUrl)) + { + char age[30]; + txtParseParam(szUrl, NULL, "http://", "/", szCtlUrl, sizeof(szCtlUrl)); + txtParseParam(buf, NULL, "ST:", "\r", szDev, sizeof(szDev)); + txtParseParam(buf, "max-age", "=", "\r", age, sizeof(age)); + expireTime = atoi(age); + break; + } + } + } + } + + mir_free(buf); + mir_free(ips); + setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&any, sizeof(unsigned)); + closesocket(sock); +} + + +static int httpTransact (char* szUrl, char* szResult, int resSize, char* szActionName) +{ + // Parse URL + char *ppath, *phost, *pport, *szHost, *szPort, szRes[6]; + int sz, res = 0; + + char* szPostHdr = soap_post_hdr; + char* szData = mir_alloc(4096); + char* szReq = szActionName ? mir_strdup(szResult) : NULL; + szResult[0] = 0; + + + phost = strstr(szUrl,"://"); + if (phost == NULL) phost = szUrl; + else phost += 3; + + ppath = strchr(phost,'/'); + if (ppath == NULL) ppath = phost + strlen(phost); + + pport = strchr(phost,':'); + if (pport == NULL) pport = ppath; + + sz = pport - phost + 1; + szHost = _alloca(sz); + strncpy(szHost, phost, sz); + szHost[sz-1] = 0; + + sz = ppath - pport; + if (sz > 1) + { + szPort = _alloca(sz); + strncpy(szPort, pport+1, sz); + szPort[sz-1] = 0; + } + else + szPort = default_http_port; + + for (;;) + { + if (szActionName == NULL) + sz = mir_snprintf (szData, 4096, + xml_get_hdr, ppath, szHost, szPort); + else + { + char szData1[1024]; + + sz = mir_snprintf (szData1, sizeof(szData1), + soap_action, szActionName, szDev, szReq, szActionName); + + sz = mir_snprintf (szData, 4096, + szPostHdr, ppath, szHost, szPort, + sz, szDev, szActionName, szData1); + } + + { + static TIMEVAL tv = { 3, 0 }; + static unsigned ttl = 4; + fd_set readfd; + + SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + SOCKADDR_IN enetaddr; + enetaddr.sin_family = AF_INET; + enetaddr.sin_port = htons((unsigned short)atol(szPort)); + enetaddr.sin_addr.s_addr = inet_addr(szHost); + + if (enetaddr.sin_addr.s_addr == INADDR_NONE) + { + PHOSTENT he = gethostbyname(szHost); + if (he) + enetaddr.sin_addr.s_addr = *(unsigned*)he->h_addr_list[0]; + } + + Netlib_Logf(NULL, "UPnP HTTP connection Host: %s Port: %s\n", szHost, szPort); + + FD_ZERO(&readfd); + FD_SET(sock, &readfd); + + setsockopt(sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof(unsigned)); + + if (connect(sock, (SOCKADDR*)&enetaddr, sizeof(enetaddr)) == 0) + { + if (send( sock, szData, sz, 0 ) != SOCKET_ERROR) + { + LongLog(szData); + sz = 0; + for(;;) + { + int bytesRecv; + char *hdrend; + + if (select(0, &readfd, NULL, NULL, &tv) != 1) + { + Netlib_Logf(NULL, "UPnP select timeout"); + break; + } + + bytesRecv = recv( sock, &szResult[sz], resSize-sz, 0 ); + if ( bytesRecv == 0 || bytesRecv == SOCKET_ERROR) + break; + else + sz += bytesRecv; + + if (sz >= (resSize-1)) + { + szResult[resSize-1] = 0; + break; + } + else + szResult[sz] = 0; + + hdrend = strstr(szResult, "\r\n\r\n"); + if (hdrend != NULL && + (txtParseParam(szResult, NULL, "Content-Length:", "\r", szRes, sizeof(szRes)) || + txtParseParam(szResult, NULL, "CONTENT-LENGTH:", "\r", szRes, sizeof(szRes)))) + { + int pktsz = atol(szRes) + (hdrend - szResult + 4); + if (sz >= pktsz) + { + szResult[pktsz] = 0; + break; + } + } + + } + LongLog(szResult); + } + else + Netlib_Logf(NULL, "UPnP send failed %d", WSAGetLastError()); + } + else + Netlib_Logf(NULL, "UPnP connect failed %d", WSAGetLastError()); + + if (szActionName == NULL) + { + int len = sizeof(locIP); + getsockname(sock, (SOCKADDR*)&locIP, &len); + } + + shutdown(sock, 2); + closesocket(sock); + } + txtParseParam(szResult, "HTTP", " ", " ", szRes, sizeof(szRes)); + res = atol(szRes); + if (szActionName != NULL && res == 405 && szPostHdr == soap_post_hdr) + szPostHdr = soap_post_hdr_m; + else + break; + } + + mir_free(szData); + mir_free(szReq); + return res; +} + + +static void findUPnPGateway(void) +{ + time_t curTime = time(NULL); + + if ((curTime - lastDiscTime) >= expireTime) + { + char szUrl[256]; + char* szData = mir_alloc(8192); + + lastDiscTime = curTime; + + discoverUPnP(szUrl, sizeof(szUrl)); + gatewayFound = szUrl[0] != 0 && httpTransact(szUrl, szData, 8192, NULL) == 200; + + if (gatewayFound) + { + char szTemp[256]; + size_t ctlLen; + + txtParseParam(szData, NULL, "<URLBase>", "</URLBase>", szTemp, sizeof(szTemp)); + if (szTemp[0] != 0) strcpy(szCtlUrl, szTemp); + ctlLen = strlen(szCtlUrl); + if (ctlLen > 0 && szCtlUrl[ctlLen-1] == '/') + szCtlUrl[--ctlLen] = 0; + + mir_snprintf(szTemp, sizeof(szTemp), search_device, szDev); + txtParseParam(szData, szTemp, "<controlURL>", "</controlURL>", szUrl, sizeof(szUrl)); + switch (szUrl[0]) + { + case 0: + gatewayFound = FALSE; + break; + + case '/': + strncat(szCtlUrl, szUrl, sizeof(szCtlUrl) - ctlLen); + szCtlUrl[sizeof(szCtlUrl)-1] = 0; + break; + + default: + strncpy(szCtlUrl, szUrl, sizeof(szCtlUrl)); + szCtlUrl[sizeof(szCtlUrl)-1] = 0; + break; + } + } + Netlib_Logf(NULL, "UPnP Gateway detected %d, Control URL: %s\n", gatewayFound, szCtlUrl); + mir_free(szData); + } +} + + +BOOL NetlibUPnPAddPortMapping(WORD intport, char *proto, + WORD *extport, DWORD *extip, BOOL search) +{ + int res = 0; + + findUPnPGateway(); + + if (gatewayFound) + { + char* szData = mir_alloc(4096); + char szExtIP[30]; + + *extport = intport - 1; + *extip = ntohl(locIP.sin_addr.S_un.S_addr); + + do { + ++*extport; + mir_snprintf(szData, 4096, add_port_mapping, + *extport, proto, intport, inet_ntoa(locIP.sin_addr)); + res = httpTransact(szCtlUrl, szData, 4096, "AddPortMapping"); + } while (search && res == 718); + + if (res == 200) + { + szData[0] = 0; + res = httpTransact(szCtlUrl, szData, 4096, "GetExternalIPAddress"); + if (res == 200 && txtParseParam(szData, "<NewExternalIPAddress", ">", "<", szExtIP, sizeof(szExtIP))) + *extip = ntohl(inet_addr(szExtIP)); + } + mir_free(szData); + } + + return res == 200; +} + + +void NetlibUPnPDeletePortMapping(WORD extport, char* proto) +{ + if (extport != 0) + { +// findUPnPGateway(); + + if (gatewayFound) + { + char* szData = mir_alloc(4096); + + mir_snprintf(szData, 4096, delete_port_mapping, + extport, proto); + httpTransact(szCtlUrl, szData, 4096, "DeletePortMapping"); + + mir_free(szData); + } + } +} + |