/*
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[] =
"%s";
static char soap_action[] =
"\r\n"
" \r\n"
" \r\n"
"%s"
" \r\n"
" \r\n"
"\r\n";
static char add_port_mapping[] =
" \r\n"
" %i\r\n"
" %s\r\n"
" %i\r\n"
" %s\r\n"
" 1\r\n"
" Miranda\r\n"
" 0\r\n";
static char delete_port_mapping[] =
" \r\n"
" %i\r\n"
" %s\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; jh_addr_list[j];
}
buf = mir_alloc(1500);
for(i = 3; --i && szUrl[0] == 0;)
{
for (j=0; j 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, "", "", 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, "", "", 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, "", "<", 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);
}
}
}