# HG changeset patch # User Adam Warrington # Date 1123437259 0 # Node ID 3aeb85cc9cda5e9debade38b1b48c7e3f7bfff5b # Parent 4c1f45ac00e9d4d806fd25bcd42ed539c80d78f8 [gaim-migrate @ 13319] *** empty log message *** committer: Tailor Script diff -r 4c1f45ac00e9 -r 3aeb85cc9cda src/Makefile.am --- a/src/Makefile.am Sat Aug 06 12:25:15 2005 +0000 +++ b/src/Makefile.am Sun Aug 07 17:54:19 2005 +0000 @@ -96,6 +96,7 @@ stringref.c \ sound.c \ sslconn.c \ + upnp.c \ util.c \ value.c \ xmlnode.c @@ -136,6 +137,7 @@ stringref.h \ sound.h \ sslconn.h \ + upnp.h \ util.h \ value.h \ version.h \ diff -r 4c1f45ac00e9 -r 3aeb85cc9cda src/network.c --- a/src/network.c Sat Aug 06 12:25:15 2005 +0000 +++ b/src/network.c Sun Aug 07 17:54:19 2005 +0000 @@ -29,6 +29,7 @@ #include "account.h" #include "network.h" #include "prefs.h" +#include "upnp.h" const unsigned char * gaim_network_ip_atoi(const char *ip) @@ -134,6 +135,7 @@ gaim_network_get_my_ip(int fd) { const char *ip = NULL; + const char *controlURL = NULL; /* Check if the user specified an IP manually */ if (!gaim_prefs_get_bool("/core/network/auto_ip")) { @@ -142,6 +144,13 @@ return ip; } + /* attempt to get the ip from a NAT device */ + if ((controlURL = gaim_upnp_discover()) != NULL) { + ip = gaim_upnp_get_public_ip(controlURL); + if (ip != NULL) + return ip; + } + /* Just fetch the IP of the local system */ return gaim_network_get_local_system_ip(fd); } @@ -151,6 +160,7 @@ { int listenfd = -1; const int on = 1; + const char *controlURL = NULL; #if HAVE_GETADDRINFO int errnum; struct addrinfo hints, *res, *next; @@ -166,13 +176,9 @@ hints.ai_socktype = SOCK_STREAM; errnum = getaddrinfo(NULL /* any IP */, serv, &hints, &res); if (errnum != 0) { -#ifndef _WIN32 gaim_debug_warning("network", "getaddrinfo: %s\n", gai_strerror(errnum)); if (errnum == EAI_SYSTEM) gaim_debug_warning("network", "getaddrinfo: system error: %s\n", strerror(errno)); -#else - gaim_debug_warning("network", "getaddrinfo: Error Code = %d\n", errnum); -#endif return -1; } @@ -225,6 +231,13 @@ } fcntl(listenfd, F_SETFL, O_NONBLOCK); + if((controlURL = gaim_upnp_discover()) != NULL) { + if(!gaim_upnp_set_port_mapping(controlURL, port, "TCP")) { + gaim_upnp_remove_port_mapping(controlURL, port, "TCP"); + gaim_upnp_set_port_mapping(controlURL, port, "TCP"); + } + } + gaim_debug_info("network", "Listening on port: %hu\n", gaim_network_get_port_from_fd(listenfd)); return listenfd; } @@ -279,8 +292,6 @@ void gaim_network_init(void) { - gaim_debug_register_category("network"); - gaim_prefs_add_none ("/core/network"); gaim_prefs_add_bool ("/core/network/auto_ip", TRUE); gaim_prefs_add_string("/core/network/public_ip", ""); diff -r 4c1f45ac00e9 -r 3aeb85cc9cda src/upnp.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/upnp.c Sun Aug 07 17:54:19 2005 +0000 @@ -0,0 +1,1108 @@ +/** + * @file upnp.c UPnP Implementation + * @ingroup core + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 + */ +#include "internal.h" +#include "gtkgaim.h" + +#include "debug.h" +#include "network.h" +#include "eventloop.h" +#include "upnp.h" + + +/** + * Information on the httpResponse callback + */ +typedef struct +{ + guint inpa; /* gaim_input_add handle */ + guint tima; /* gaim_timout_add handle */ + char* recvBuffer; /* response data */ + guint totalSizeRecv; + gboolean done; + +} HRD; + + +/*************************************************************** +** General Defines * +****************************************************************/ +#define HTTP_OK "200 OK" +#define SIZEOF_HTTP 7 /* size of "http://" */ +#define RECIEVE_TIMEOUT 10000 +#define CONSECUTIVE_RECIEVE_TIMEOUT 500 +#define DISCOVERY_TIMEOUT 1000 + + +/*************************************************************** +** Discovery/Description Defines * +****************************************************************/ +#define NUM_UDP_ATTEMPTS 2 + +/* Address and port of an SSDP request used for discovery */ +#define HTTPMU_HOST_ADDRESS "239.255.255.250" +#define HTTPMU_HOST_PORT 1900 + +#define SEARCH_REQUEST_DEVICE "urn:schemas-upnp-org:service:" \ + "WANIPConnection:1" + +#define SEARCH_REQUEST_STRING "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:" \ + "WANIPConnection:1\r\n" \ + "\r\n" + +#define MAX_DISCOVERY_RECIEVE_SIZE 400 +#define MAX_DESCRIPTION_RECIEVE_SIZE 7000 +#define MAX_DESCRIPTION_HTTP_HEADER_SIZE 100 + + +/****************************************************************** +** Action Defines * +*******************************************************************/ +#define HTTP_HEADER_ACTION "POST %s HTTP/1.1\r\n" \ + "HOST: %s\r\n" \ + "SOAPACTION: " \ + "\"urn:schemas-upnp-org:" \ + "service:%s#%s\"\r\n" \ + "CONTENT-TYPE: text/xml ; charset=\"utf-8\"\r\n"\ + "Content-Length: %i\r\n\r\n" + +#define SOAP_ACTION "\r\n" \ + "\r\n" \ + "\r\n" \ + "\r\n%s" \ + "\r\n" \ + "\r\n" \ + "\r\n" + +#define PORT_MAPPING_LEASE_TIME "0" +#define PORT_MAPPING_DESCRIPTION "GAIM_UPNP_PORT_FORWARD" + +#define ADD_PORT_MAPPING_PARAMS "\r\n" \ + "%i\r\n"\ + "%s\r\n" \ + "%i\r\n"\ + "%s" \ + "\r\n" \ + "1\r\n" \ + "" \ + PORT_MAPPING_DESCRIPTION \ + "\r\n" \ + "" \ + PORT_MAPPING_LEASE_TIME \ + "\r\n" + +#define DELETE_PORT_MAPPING_PARAMS "\r\n" \ + "%i" \ + "\r\n" \ + "%s\r\n" + + + +/* validate an http url without a port */ +static gboolean +gaim_upnp_validate_url(const char* url) +{ + int i1, i2, i3, i4, r; + + if(url == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_validate_url(): url == NULL\n\n"); + return FALSE; + } + r = sscanf(url, "http://%3i.%3i.%3i.%3i/", &i1, &i2, &i3, &i4); + if(r == 4) { + return TRUE; + } + + gaim_debug_info("upnp", + "gaim_upnp_validate_url(): Failed In validate URL\n\n"); + return FALSE; +} + + +static void +gaim_upnp_timeout(gpointer data, + gint source, + GaimInputCondition cond) +{ + HRD* hrd = data; + + gaim_input_remove(hrd->inpa); + gaim_timeout_remove(hrd->tima); + + if(hrd->totalSizeRecv == 0) { + free(hrd->recvBuffer); + hrd->recvBuffer = NULL; + } else { + hrd->recvBuffer[hrd->totalSizeRecv] = '\0'; + } + + hrd->done = TRUE; +} + + +static void +gaim_upnp_http_read(gpointer data, + gint sock, + GaimInputCondition cond) +{ + int sizeRecv; + extern int errno; + HRD* hrd = data; + + sizeRecv = recv(sock, &(hrd->recvBuffer[hrd->totalSizeRecv]), + MAX_DESCRIPTION_RECIEVE_SIZE-hrd->totalSizeRecv, 0); + if(sizeRecv < 0 && errno != EINTR) { + gaim_debug_info("upnp", + "gaim_upnp_http_read(): recv < 0: %i!\n\n", errno); + free(hrd->recvBuffer); + hrd->recvBuffer = NULL; + gaim_timeout_remove(hrd->tima); + gaim_input_remove(hrd->inpa); + hrd->done = TRUE; + return; + }else if(errno == EINTR) { + sizeRecv = 0; + } + hrd->totalSizeRecv += sizeRecv; + + if(sizeRecv == 0) { + if(hrd->totalSizeRecv == 0) { + gaim_debug_info("upnp", + "gaim_upnp_http_read(): totalSizeRecv == 0\n\n"); + free(hrd->recvBuffer); + hrd->recvBuffer = NULL; + } else { + hrd->recvBuffer[hrd->totalSizeRecv] = '\0'; + } + gaim_timeout_remove(hrd->tima); + gaim_input_remove(hrd->inpa); + hrd->done = TRUE; + } else { + gaim_timeout_remove(hrd->tima); + gaim_input_remove(hrd->inpa); + hrd->tima = gaim_timeout_add(CONSECUTIVE_RECIEVE_TIMEOUT, + (GSourceFunc)gaim_upnp_timeout, hrd); + hrd->inpa = gaim_input_add(sock, GAIM_INPUT_READ, + gaim_upnp_http_read, hrd); + } + +} + + + +static char* +gaim_upnp_http_request(const char* address, + unsigned short port, + const char* httpRequest) +{ + int sock; + int sizeSent, totalSizeSent = 0; + extern int errno; + struct sockaddr_in serv_addr; + struct hostent *server; + char* recvBuffer; + + HRD* hrd = (HRD*)malloc(sizeof(HRD)); + if(hrd == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_http_request(): Failed in hrd MALLOC\n\n"); + return NULL; + } + + hrd->recvBuffer = NULL; + hrd->totalSizeRecv = 0; + hrd->done = FALSE; + + hrd->recvBuffer = (char*)malloc(MAX_DESCRIPTION_RECIEVE_SIZE); + if(hrd == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_http_request(): Failed in recvBuffer MALLOC\n\n"); + free(hrd); + return NULL; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock < 0) { + gaim_debug_info("upnp", + "gaim_upnp_http_request(): Failed In sock creation\n\n"); + free(hrd->recvBuffer); + free(hrd); + return NULL; + } + + server = gethostbyname(address); + if(server == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_http_request(): Failed In gethostbyname\n\n"); + free(hrd->recvBuffer); + free(hrd); + close(sock); + return NULL; + } + memset((char*)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + memcpy(&serv_addr.sin_addr, + server->h_addr_list[0], + server->h_length); + serv_addr.sin_port = htons(port); + + if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0) { + gaim_debug_info("upnp", + "gaim_upnp_http_request(): Failed In connect\n\n"); + free(hrd->recvBuffer); + free(hrd); + close(sock); + return NULL; + } + + while(totalSizeSent < strlen(httpRequest)) { + sizeSent = send(sock,(char*)((int)httpRequest+totalSizeSent), + strlen(httpRequest),0); + if(sizeSent <= 0 && errno != EINTR) { + gaim_debug_info("upnp", + "gaim_upnp_http_request(): Failed In send\n\n"); + free(hrd->recvBuffer); + free(hrd); + close(sock); + return NULL; + }else if(errno == EINTR) { + sizeSent = 0; + } + totalSizeSent += sizeSent; + } + + hrd->tima = gaim_timeout_add(RECIEVE_TIMEOUT, + (GSourceFunc)gaim_upnp_timeout, hrd); + hrd->inpa = gaim_input_add(sock, GAIM_INPUT_READ, + gaim_upnp_http_read, hrd); + while (!hrd->done) { + gtk_main_iteration(); + } + close(sock); + + recvBuffer = hrd->recvBuffer; + free(hrd); + return recvBuffer; +} + + + +/* This function takes the HTTP response requesting the description xml from + * the UPnP enabled IGD. It also takes as input the URL the request was sent + * to. + * + * The description contains the URL needed to control the actions of the UPnP + * enabled IGD. This URL can be in the form of just a path, in whcih case we + * have to append the path to the base URL. The base URL might be specified + * in the XML, or it might not, in which case u append to the httpURL. + */ + +/* at some point, maybe parse the description response using an xml library */ +static char* +gaim_upnp_parse_description_response(const char* httpResponse, + const char* httpURL) +{ + char* wanIPConnectionStart; + char urlBaseTag[] = ""; + char urlBaseEndTag[] = ""; + char controlURLTag[] = ""; + char controlURLEndTag[] = ""; + char* urlBaseTagLoc; + char* urlBaseEndTagLoc; + char* controlURLTagLoc; + char* controlURLEndTagLoc; + int controlURLSize; + int baseURLSize; + char* controlURL; + char* baseURL; + + if(strstr(httpResponse, HTTP_OK) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In HTTP_OK\n\n"); + return NULL; + } + + if((wanIPConnectionStart = + strstr(httpResponse, SEARCH_REQUEST_DEVICE)) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed SEARCH_REQUEST_DEVICE\n\n"); + return NULL; + } + if((controlURLTagLoc = strstr(wanIPConnectionStart, controlURLTag)) + == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In controlURLTagLoc\n\n"); + return NULL; + } + if((controlURLEndTagLoc = + strstr(wanIPConnectionStart, controlURLEndTag)) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In controlURLEndTagLoc\n\n"); + return NULL; + } + + + controlURLSize = + controlURLEndTagLoc-&controlURLTagLoc[strlen(controlURLTag)]; + if((controlURL = (char*)malloc(controlURLSize+1)) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In MALLOC controlURL\n\n"); + return NULL; + } + memcpy(controlURL, &controlURLTagLoc[strlen(controlURLTag)], + controlURLSize); + controlURL[controlURLSize] = '\0'; + + + if((urlBaseTagLoc = strstr(httpResponse, urlBaseTag)) == NULL) { + if((baseURL = (char*)malloc(strlen(httpURL)+controlURLSize+1)) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In MALLOC baseURL\n\n"); + return NULL; + } + memcpy(baseURL, httpURL, strlen(httpURL)); + baseURL[strlen(httpURL)] = '\0'; + } else { + if((urlBaseEndTagLoc = strstr(httpResponse, urlBaseEndTag)) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In urlBaseEndTagLoc\n\n"); + return NULL; + } + baseURLSize = + urlBaseEndTagLoc - &urlBaseTagLoc[strlen(urlBaseTag)]; + if((baseURL = (char*)malloc(baseURLSize+controlURLSize+1)) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed 2nd MALLOC baseURL\n\n"); + return NULL; + } + memcpy(baseURL, &urlBaseTagLoc[strlen(urlBaseTag)], baseURLSize); + baseURL[baseURLSize] = '\0'; + } + + if(strstr(controlURL, "http://") == NULL) { + memcpy(&baseURL[strlen(baseURL)], controlURL, strlen(controlURL)+1); + free(controlURL); + controlURL = baseURL; + }else{ + free(baseURL); + } + + return controlURL; +} + + + + +/* parse a url into it's approrpiate parts: + address, port, path. return true on success, + false on failure */ +static gboolean +gaim_upnp_parse_url(const char* url, char** path, + char** address, char** port) +{ + char* temp, *temp2; + + /* get the path */ + if((temp = strchr(&url[SIZEOF_HTTP], '/')) < 0) { + gaim_debug_info("upnp", + "gaim_upnp_parse_url(): Failed In 1\n\n"); + return FALSE; + } + if((*path = (char*)malloc(strlen(temp)+1)) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_url(): Failed In 2\n\n"); + return FALSE; + } + memcpy(*path, temp, strlen(temp)); + (*path)[strlen(temp)] = '\0'; + + /* get the port */ + if((temp2 = strchr(&url[SIZEOF_HTTP], ':')) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_url(): Failed In 3\n\n"); + free(*path); + return FALSE; + } + if((*port = (char*)malloc(temp-temp2)) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_url(): Failed In 4\n\n"); + free(*path); + return FALSE; + } + memcpy(*port, &temp2[1], (temp-1)-temp2); + (*port)[(temp-1)-temp2] = '\0'; + + /* get the address */ + if((*address = (char*)malloc((temp2-&url[SIZEOF_HTTP])+1)) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_url(): Failed In 5\n\n"); + free(*path); + free(*port); + return FALSE; + } + memcpy(*address, &url[SIZEOF_HTTP], + temp2-&url[SIZEOF_HTTP]); + (*address)[temp2-&url[SIZEOF_HTTP]] = '\0'; + + return TRUE; +} + + + +/* + * This function takes the URL where the UPnP enabled IGD description is + * located at, and sends an HTTP request to retrieve that description. Once + * the description is retrieved, we send the response to the + * gaim_upnp_parse_description_response() function. This will parse the + * description, and return the control URL needed to control the IGD, which + * this function returns. + */ +static char* +gaim_upnp_parse_description(const char* descriptionURL) +{ + char* fullURL; + char* controlURL; + char* httpResponse; + char httpRequest[MAX_DESCRIPTION_HTTP_HEADER_SIZE]; + + char* descriptionXMLAddress; + char* descriptionAddressPort; + char* descriptionAddress; + char* descriptionPort; + unsigned short port; + + /* parse the 4 above variables out of the descriptionURL + example description URL: http://192.168.1.1:5678/rootDesc.xml */ + + /* parse the url into address, port, path variables */ + if(!gaim_upnp_parse_url(descriptionURL, &descriptionXMLAddress, + &descriptionAddress, &descriptionPort)) { + return NULL; + } + if((port = atoi(descriptionPort)) == 0) { + gaim_debug_info("upnp", + "gaim_upnp_parse_description(): failed atoi\n\n"); + free(descriptionXMLAddress); + free(descriptionAddress); + free(descriptionPort); + return NULL; + } + + if((descriptionAddressPort = (char*)malloc(strlen(descriptionAddress) + + strlen(descriptionPort) + 2)) + == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_description(): failed MALLOC desAddPort\n\n"); + free(descriptionXMLAddress); + free(descriptionAddress); + free(descriptionPort); + return NULL; + } + sprintf(descriptionAddressPort, "%s:%s", + descriptionAddress, descriptionPort); + + if((fullURL = (char*)malloc(strlen(descriptionAddressPort) + + SIZEOF_HTTP + 1)) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_description(): failed MALLOC fullURL\n\n"); + free(descriptionXMLAddress); + free(descriptionAddress); + free(descriptionPort); + free(descriptionAddressPort); + } + sprintf(fullURL, "http://%s", descriptionAddressPort); + + /* for example... + GET /rootDesc.xml HTTP/1.1\r\nHost: 192.168.1.1:5678\r\n\r\n */ + sprintf(httpRequest, "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", + descriptionXMLAddress, descriptionAddressPort); + + httpResponse = gaim_upnp_http_request(descriptionAddress, + port, httpRequest); + if(httpResponse == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_description(): httpResponse is NULL\n\n"); + free(descriptionXMLAddress); + free(descriptionAddress); + free(descriptionPort); + free(descriptionAddressPort); + free(fullURL); + return NULL; + } + + controlURL = gaim_upnp_parse_description_response(httpResponse, fullURL); + + free(descriptionXMLAddress); + free(descriptionAddress); + free(descriptionPort); + free(descriptionAddressPort); + free(fullURL); + free(httpResponse); + + if(controlURL == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_parse_description(): controlURL is NULL\n\n"); + return NULL; + } + + return controlURL; +} + + +static char* +gaim_upnp_parse_discover_response(const char* buf, + unsigned int bufSize) +{ + char* startDescURL; + char* endDescURL; + char* descURL; + unsigned int descURLSize; + char* retVal; + + if(strstr(buf, HTTP_OK) == NULL) { + gaim_debug_info("upnp", + "parse_discover_response(): Failed In HTTP_OK\n\n"); + return NULL; + } + + if((startDescURL = strstr(buf, "http://")) == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In finding http://\n\n"); + return NULL; + } + + endDescURL = strchr(startDescURL, '\r'); + if(endDescURL == NULL) { + endDescURL = strchr(startDescURL, '\n'); + if(endDescURL == NULL) { + gaim_debug_info("upnp", + "parse_description_response(): Failed In endDescURL\n\n"); + return NULL; + }else if(endDescURL == startDescURL) { + gaim_debug_info("upnp", + "parse_description_response(): endDescURL == startDescURL\n\n"); + return NULL; + } + }else if(endDescURL == startDescURL) { + gaim_debug_info("upnp", + "parse_description_response(): 2nd endDescURL == startDescURL\n\n"); + return NULL; + } + + descURLSize = (endDescURL-startDescURL)+1; + descURL = (char*)malloc(descURLSize); + memcpy(descURL, startDescURL, descURLSize-1); + descURL[descURLSize-1] = '\0'; + + retVal = gaim_upnp_parse_description(descURL); + free(descURL); + return retVal; +} + + + +static void +gaim_upnp_discover_udp_read(gpointer data, + gint sock, + GaimInputCondition cond) +{ + unsigned int length; + extern int errno; + struct sockaddr_in from; + int sizeRecv; + HRD* hrd = data; + + gaim_timeout_remove(hrd->tima); + length = sizeof(struct sockaddr_in); + hrd->recvBuffer = (char*)malloc(MAX_DISCOVERY_RECIEVE_SIZE); + + do { + sizeRecv = recvfrom(sock, hrd->recvBuffer, + MAX_DISCOVERY_RECIEVE_SIZE, 0, + (struct sockaddr*)&from, &length); + + if(sizeRecv > 0) { + hrd->recvBuffer[sizeRecv] = '\0'; + }else if(errno != EINTR) { + free(hrd->recvBuffer); + hrd->recvBuffer = NULL; + } + }while(errno == EINTR); + + gaim_input_remove(hrd->inpa); + hrd->done = TRUE; + return; +} + + + +const char* +gaim_upnp_discover(void) +{ + int sock, i; + int sizeSent, totalSizeSent; + extern int errno; + gboolean sentSuccess, recvSuccess; + struct sockaddr_in server; + struct hostent *hp; + char sendMessage[] = SEARCH_REQUEST_STRING; + char *controlURL = NULL; + + HRD* hrd = (HRD*)malloc(sizeof(HRD)); + hrd->recvBuffer = NULL; + hrd->done = FALSE; + + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock == -1) { + close(sock); + gaim_debug_info("upnp", + "gaim_upnp_discover(): Failed In sock creation\n\n"); + return NULL; + } + + memset(&server, 0, sizeof(struct sockaddr)); + server.sin_family = AF_INET; + if((hp = gethostbyname(HTTPMU_HOST_ADDRESS)) == NULL) { + close(sock); + gaim_debug_info("upnp", + "gaim_upnp_discover(): Failed In gethostbyname\n\n"); + return NULL; + } + + memcpy(&server.sin_addr, + hp->h_addr_list[0], + hp->h_length); + server.sin_port = htons(HTTPMU_HOST_PORT); + + /* because we are sending over UDP, if there is a failure + we should retry the send NUM_UDP_ATTEMPTS times */ + for(i = 0; i < NUM_UDP_ATTEMPTS; i++) { + sentSuccess = TRUE; + recvSuccess = TRUE; + totalSizeSent = 0; + + while(totalSizeSent < strlen(sendMessage)) { + sizeSent = sendto(sock,(void*)&sendMessage[totalSizeSent], + strlen(&sendMessage[totalSizeSent]),0, + (struct sockaddr*)&server, + sizeof(struct sockaddr_in)); + if(sizeSent <= 0 && errno != EINTR) { + sentSuccess = FALSE; + break; + }else if(errno == EINTR) { + sizeSent = 0; + } + totalSizeSent += sizeSent; + } + + if(sentSuccess) { + hrd->tima = gaim_timeout_add(DISCOVERY_TIMEOUT, + (GSourceFunc)gaim_upnp_timeout, hrd); + hrd->inpa = gaim_input_add(sock, GAIM_INPUT_READ, + gaim_upnp_discover_udp_read, hrd); + while (!hrd->done) { + gtk_main_iteration(); + } + if(hrd->recvBuffer == NULL) { + recvSuccess = FALSE; + } else { + /* parse the response, and see if it was a success */ + close(sock); + if((controlURL= + gaim_upnp_parse_discover_response(hrd->recvBuffer, + strlen(hrd->recvBuffer)))==NULL) { + gaim_debug_info("upnp", + "gaim_upnp_discover(): Failed In parse response\n\n"); + return NULL; + } + } + } + + /* if sent success and recv successful, then break */ + if(sentSuccess && recvSuccess) { + i = NUM_UDP_ATTEMPTS; + } + } + if(!sentSuccess || !recvSuccess) { + close(sock); + gaim_debug_info("upnp", + "gaim_upnp_discover(): Failed In sent/recv success\n\n"); + return NULL; + } + + return controlURL; +} + + + + +static char* +gaim_upnp_generate_action_message_and_send(const char* controlURL, + const char* actionName, + const char* actionParams) +{ + char serviceType[] = "WANIPConnection:1"; + char* actionMessage; + char* soapMessage; + char* httpResponse; + + char* pathOfControl; + char* addressOfControl; + char* addressPortOfControl; + char* portOfControl; + unsigned short port; + + /* set the soap message */ + if((soapMessage = (char*)malloc(strlen(SOAP_ACTION) + + strlen(serviceType) + + strlen(actionParams) + + strlen(actionName) + + strlen(actionName))) == NULL) { + gaim_debug_info("upnp", + "generate_action_message_and_send(): Failed MALLOC soapMessage\n\n"); + return NULL; + } + sprintf(soapMessage, SOAP_ACTION, actionName, serviceType, + actionParams, actionName); + + /* parse the url into address, port, path variables */ + if(!gaim_upnp_parse_url(controlURL, &pathOfControl, + &addressOfControl, &portOfControl)) { + gaim_debug_info("upnp", + "generate_action_message_and_send(): Failed In Parse URL\n\n"); + free(soapMessage); + return NULL; + } + + /* set the addressPortOfControl variable which should have a + form like the following: 192.168.1.1:8000 */ + if((addressPortOfControl = (char*)malloc(strlen(addressOfControl) + + strlen(portOfControl) + + 2)) == NULL) { + gaim_debug_info("upnp", + "generate_action_message_and_send(): MALLOC addressPortOfControl\n\n"); + free(soapMessage); + free(pathOfControl); + free(addressOfControl); + free(portOfControl); + return NULL; + } + sprintf(addressPortOfControl, "%s:%s", addressOfControl, portOfControl); + if((port = atoi(portOfControl)) == 0) { + gaim_debug_info("upnp", + "generate_action_message_and_send(): Failed In port = atoi\n\n"); + free(soapMessage); + free(pathOfControl); + free(addressOfControl); + free(portOfControl); + free(addressPortOfControl); + return NULL; + } + + /* set the HTTP Header */ + if((actionMessage = (char*)malloc(strlen(HTTP_HEADER_ACTION) + + strlen(pathOfControl) + + strlen(addressPortOfControl) + + strlen(serviceType) + + strlen(actionName) + + strlen(soapMessage))) == NULL) { + gaim_debug_info("upnp", + "generate_action_message_and_send(): Failed MALLOC actionMessage\n\n"); + free(soapMessage); + free(pathOfControl); + free(addressOfControl); + free(portOfControl); + free(addressPortOfControl); + return NULL; + } + sprintf(actionMessage, HTTP_HEADER_ACTION, + pathOfControl, addressPortOfControl, + serviceType, actionName, strlen(soapMessage)); + + /* append to the header the body */ + strcat(actionMessage, soapMessage); + + /* get the return of the http response */ + httpResponse = gaim_upnp_http_request(addressOfControl, + port, actionMessage); + if(httpResponse == NULL) { + gaim_debug_info("upnp", + "generate_action_message_and_send(): Failed In httpResponse\n\n"); + } + + free(actionMessage); + free(soapMessage); + free(pathOfControl); + free(addressOfControl); + free(portOfControl); + free(addressPortOfControl); + + return httpResponse; +} + + + + +const char* +gaim_upnp_get_public_ip(const char* controlURL) +{ + char* extIPAddress; + char* httpResponse; + char actionName[] = "GetExternalIPAddress"; + char actionParams[] = ""; + char* temp, *temp2; + + /* make sure controlURL is a valid URL */ + if(!gaim_upnp_validate_url(controlURL)) { + gaim_debug_info("upnp", + "gaim_upnp_get_public_ip(): Failed In Validate URL\n\n"); + return NULL; + } + + httpResponse = gaim_upnp_generate_action_message_and_send(controlURL, + actionName, + actionParams); + if(httpResponse == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_get_public_ip(): Failed In httpResponse\n\n"); + return NULL; + } + + /* extract the ip, or see if there is an error */ + /* at some point, maybe extract the ip using an xml library */ + if((temp = strstr(httpResponse, "')) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_get_public_ip(): Failed In Finding >\n\n"); + free(httpResponse); + return NULL; + } + if((temp2 = strchr(temp, '<')) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_get_public_ip(): Failed In Finding <\n\n"); + free(httpResponse); + return NULL; + } + if((extIPAddress = (char*)malloc(temp2-temp)) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_get_public_ip(): Failed In MALLOC extIPAddress\n\n"); + free(httpResponse); + return NULL; + } + memcpy(extIPAddress, &temp[1], (temp2-1)-temp); + extIPAddress[(temp2-1)-temp] = '\0'; + + free(httpResponse); + gaim_debug_info("upnp", + "gaim_upnp_get_public_ip() IP: %s\n\n", extIPAddress); + return extIPAddress; +} + + + +static const char* +gaim_upnp_get_local_ip_address(const char* address) { + const char* ip; + int sock; + struct sockaddr_in serv_addr; + struct hostent *server; + char* pathOfControl; + char* addressOfControl; + char* portOfControl; + unsigned short port; + + /* parse the url into address, port, path variables */ + if(!gaim_upnp_parse_url(address, &pathOfControl, + &addressOfControl, &portOfControl)) { + gaim_debug_info("upnp", + "get_local_ip_address(): Failed In Parse URL\n\n"); + return NULL; + } + if((port = atoi(portOfControl)) == 0) { + gaim_debug_info("upnp", + "get_local_ip_address(): Failed In port = atoi\n\n"); + return NULL; + } + + sock = socket(AF_INET, SOCK_STREAM, 0); + if(sock < 0) { + gaim_debug_info("upnp", + "get_local_ip_address(): Failed In sock creation\n\n"); + return NULL; + } + + server = gethostbyname(addressOfControl); + if(server == NULL) { + gaim_debug_info("upnp", + "get_local_ip_address(): Failed In gethostbyname\n\n"); + close(sock); + return NULL; + } + memset((char*)&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = AF_INET; + memcpy(&serv_addr.sin_addr, + server->h_addr_list[0], + server->h_length); + serv_addr.sin_port = htons(port); + + if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr)) < 0) { + gaim_debug_info("upnp", + "get_local_ip_address(): Failed In connect\n\n"); + close(sock); + return NULL; + } + + ip = gaim_network_get_local_system_ip(sock); + + close(sock); + return ip; +} + + + +gboolean +gaim_upnp_set_port_mapping(const char* controlURL, unsigned short portMap, + const char* protocol) +{ + char* httpResponse; + char actionName[] = "AddPortMapping"; + char* actionParams; + const char* internalIP; + + /* make sure controlURL is a valid URL */ + if(!gaim_upnp_validate_url(controlURL)) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed In Validate URL\n\n"); + return FALSE; + } + + /* get the internal IP */ + if((internalIP = gaim_upnp_get_local_ip_address(controlURL)) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): couldn't get local ip\n\n"); + return FALSE; + } + + /* make the portMappingParams variable */ + if((actionParams = (char*)malloc(strlen(ADD_PORT_MAPPING_PARAMS) + + strlen(internalIP) + + 10 + /* size of port as int * 2 */ + strlen(protocol))) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed MALLOC portMappingParams\n\n"); + return FALSE; + } + sprintf(actionParams, ADD_PORT_MAPPING_PARAMS, portMap, + protocol, portMap, internalIP); + + httpResponse = gaim_upnp_generate_action_message_and_send(controlURL, + actionName, + actionParams); + + if(httpResponse == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed In httpResponse\n\n"); + free(actionParams); + return FALSE; + } + + /* determine if port mapping was a success */ + if(strstr(httpResponse, HTTP_OK) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed HTTP_OK\n\n%s\n\n", httpResponse); + free(actionParams); + free(httpResponse); + return FALSE; + } + + free(actionParams); + free(httpResponse); + return TRUE; +} + + +gboolean +gaim_upnp_remove_port_mapping(const char* controlURL, unsigned short portMap, + const char* protocol) +{ + char* httpResponse; + char actionName[] = "DeletePortMapping"; + char* actionParams; + + /* make sure controlURL is a valid URL */ + if(!gaim_upnp_validate_url(controlURL)) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed In Validate URL\n\n"); + return FALSE; + } + + /* make the portMappingParams variable */ + if((actionParams = (char*)malloc(strlen(DELETE_PORT_MAPPING_PARAMS) + + 5 + /* size of port as int */ + strlen(protocol))) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed MALLOC portMappingParams\n\n"); + return FALSE; + } + sprintf(actionParams, DELETE_PORT_MAPPING_PARAMS, + portMap, protocol); + + httpResponse = gaim_upnp_generate_action_message_and_send(controlURL, + actionName, + actionParams); + + if(httpResponse == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed In httpResponse\n\n"); + free(actionParams); + return FALSE; + } + + /* determine if port mapping was a success */ + if(strstr(httpResponse, HTTP_OK) == NULL) { + gaim_debug_info("upnp", + "gaim_upnp_set_port_mapping(): Failed HTTP_OK\n\n%s\n\n", httpResponse); + free(actionParams); + free(httpResponse); + return FALSE; + } + + free(actionParams); + free(httpResponse); + return TRUE; +} diff -r 4c1f45ac00e9 -r 3aeb85cc9cda src/upnp.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/upnp.h Sun Aug 07 17:54:19 2005 +0000 @@ -0,0 +1,100 @@ +/** + * @file upnp.h Universal Plug N Play API + * @ingroup core + * + * gaim + * + * Gaim is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * 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 + */ + +#ifndef _GAIM_UPNP_H_ +#define _GAIM_UPNP_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/**************************************************************************/ +/** @name UPnP API */ +/**************************************************************************/ +/*@{*/ + +/** + * Sends a discovery request to search for a UPnP enabled IGD that + * contains the WANIPConnection service that will allow us to recieve the + * public IP address of the IGD, and control it for forwarding ports. + * + * @param void + * + * @return The control URL for the IGD we'll use to use the IGD services + */ +char *gaim_upnp_discover(void); + + +/** + * Gets the IP address from a UPnP enabled IGD that sits on the local + * network, so when getting the network IP, instead of returning the + * local network IP, the public IP is retrieved. + * + * @param controlURL The control URL retrieved from gaim_upnp_discover. + * + * @return The IP address of the network, or NULL if something went wrong + */ +char* gaim_upnp_get_public_ip(const char* controlURL); + + +/** + * Maps Ports in a UPnP enabled IGD that sits on the local network to + * this gaim client. Essentially, this function takes care of the port + * forwarding so things like file transfers can work behind NAT firewalls + * + * @param controlURL The control URL retrieved from gaim_upnp_discover. + * @param portMap The port to map to this client + * @param protocol The protocol to map, either "TCP" or "UDP" + * @param internalIP The internal IP to map to + * + * @return TRUE if success, FALSE if something went wrong. + */ +gboolean gaim_upnp_set_port_mapping(const char* controlURL, + unsigned short portMap, + const char* protocol, const char* internalIP); + +/** + * Deletes a port mapping in a UPnP enabled IGD that sits on the local network + * to this gaim client. Essentially, this function takes care of deleting the + * port forwarding after they have completed a connection so another client on + * the local network can take advantage of the port forwarding + * + * @param controlURL The control URL retrieved from gaim_upnp_discover. + * @param portMap The port to delete the mapping for + * @param protocol The protocol to map to. Either "TCP" or "UDP" + * + * @return TRUE if success, FALSE if something went wrong. + */ +gboolean +gaim_upnp_remove_port_mapping(const char* controlURL, unsigned short portMap, + const char* protocol); +/*@}*/ + + +#ifdef __cplusplus +} +#endif + +#endif /* _GAIM_UPNP_H_ */