1 /* Copyright (c) 2006 Adam Warrington
2 ** $Id$
3 **
4 ** Permission is hereby granted, free of charge, to any person obtaining a copy
5 ** of this software and associated documentation files (the "Software"), to deal
6 ** in the Software without restriction, including without limitation the rights
7 ** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 ** copies of the Software, and to permit persons to whom the Software is
9 ** furnished to do so, subject to the following conditions:
10 **
11 ** The above copyright notice and this permission notice shall be included in
12 ** all copies or substantial portions of the Software.
13 **
14 ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 ** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 ** SOFTWARE.
21 **
22 ******************************************************************************
23 **
24 ** This  file defines UPnP functions to control an Internet Gateway Device using
25 ** the Universal Plug and Play protocol.
26 */
27 
28 /* define this to deprecate unsecure string warnings in vs2005.net */
29 #define _CRT_SECURE_NO_DEPRECATE 1
30 
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include "libnat.h"
35 #include "http_libnat.h"
36 #include "ssdp.h"
37 #include "os.h"       /* for getting the local ip */
38 #include "utility.h"
39 #include "expat.h"
40 
41 /***************************************************************
42 ** Discovery/Description Defines                               *
43 ****************************************************************/
44 #define NUM_SSDP_ATTEMPTS 2
45 
46 /* Search target for an SSDP query */
47 #define SSDP_TARGET                 "urn:schemas-upnp-org:service:%s"
48 
49 /* wanip target */
50 #define WANIP_TARGET                  "WANIPConnection:1"
51 
52 /* wanppp target */
53 #define WANPPP_TARGET                 "WANPPPConnection:1"
54 
55 /* Header tag for the location of the description url */
56 #define LOCATION_TAG                  "LOCATION:"
57 
58 /* xml element for control url */
59 #define SERVICE_TYPE_ELEMENT          "serviceType"
60 #define BASE_URL_ELEMENT              "baseURL"
61 #define CONTROL_URL_ELEMENT           "controlURL"
62 
63 
64 /******************************************************************
65 ** Action Defines                                                 *
66 *******************************************************************/
67 #define SOAP_ACTION_TAG     "SOAPACTION"
68 #define SOAP_ACTION_VAL     "\"" SSDP_TARGET "#%s\""
69 
70 #define CONTENT_TYPE_TAG    "CONTENT-TYPE"
71 
72 #define CONTENT_TYPE_VAL    "text/xml ; charset=\"utf-8\""
73 
74 #define CONTENT_LENGTH_TAG      "Content-Length"
75 #define MAX_CONTENT_STRING_LEN  5
76 #define CONTENT_LENGTH_VAL      "%5d"
77 
78 #define SOAP_ACTION  "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n"     \
79                      "<s:Envelope xmlns:s="                               \
80                      "\"http://schemas.xmlsoap.org/soap/envelope/\" "     \
81                      "s:encodingStyle="                                   \
82                      "\"http://schemas.xmlsoap.org/soap/encoding/\">\r\n" \
83                      "<s:Body>\r\n"                                       \
84                      "<u:%s xmlns:u="                                     \
85                      "\"urn:schemas-upnp-org:service:%s\">\r\n%s"         \
86                      "</u:%s>\r\n"                                        \
87                      "</s:Body>\r\n"                                      \
88                      "</s:Envelope>\r\n"
89 
90 #define GET_PUBLIC_IP_ACTION_NAME         "GetExternalIPAddress"
91 #define GET_PUBLIC_IP_RESPONSE_ELEMENT    "NewExternalIPAddress"
92 #define SET_PORT_MAPPING_ACTION_NAME      "AddPortMapping"
93 #define REMOVE_PORT_MAPPING_ACTION_NAME   "DeletePortMapping"
94 
95 #define PORT_MAPPING_LEASE_TIME "0"
96 
97 #define GET_PUBLIC_IP_PARAMS    ""
98 #define SET_PORT_MAPPING_PARAMS "<NewRemoteHost></NewRemoteHost>\r\n"      \
99                                 "<NewExternalPort>%d</NewExternalPort>\r\n"\
100                                 "<NewProtocol>%s</NewProtocol>\r\n"        \
101                                 "<NewInternalPort>%d</NewInternalPort>\r\n"\
102                                 "<NewInternalClient>%s"                    \
103                                 "</NewInternalClient>\r\n"                 \
104                                 "<NewEnabled>1</NewEnabled>\r\n"           \
105                                 "<NewPortMappingDescription>"              \
106                                 "</NewPortMappingDescription>\r\n"         \
107                                 "<NewLeaseDuration>"                       \
108                                 PORT_MAPPING_LEASE_TIME                    \
109                                 "</NewLeaseDuration>\r\n"
110 
111 #define REMOVE_PORT_MAPPING_PARAMS "<NewRemoteHost></NewRemoteHost>\r\n" \
112                                    "<NewExternalPort>%d"                 \
113                                    "</NewExternalPort>\r\n"              \
114                                    "<NewProtocol>%s</NewProtocol>\r\n"
115 
116 
117 /************************************************************************
118 ** Internal Structures
119 *************************************************************************/
120 
121 /* data to pass to xml description parse events */
122 struct XmlDescData
123 {
124   char * control_url;           /* control url of the service */
125   char * url_base;              /* base of the control url */
126   char * correct_service;       /* service type we are looking for */
127 
128   int found_url_base;           /* 1 if we found a base element */
129   int found_service_type;       /* 1 if we found a service type element */
130   int found_correct_service;    /* 1 if the service type was the right one */
131   int found_control_url;        /* 1 if we found a control url element */
132 };
133 typedef struct XmlDescData XmlDescData;
134 
135 /* data to pass to xml get public ip parse events */
136 struct XmlGetPublicIpData
137 {
138   char * ext_ip;      /* the external ip address found in the xml */
139   int found_ext_ip;   /* will equal 1 when the external ip element is found */
140 };
141 typedef struct XmlGetPublicIpData XmlGetPublicIpData;
142 
143 
144 /* local file function prototypes */
145 static int Send_Ssdp_Discover(UpnpController * c, char ** ssdp_response);
146 static int Get_Description_Url(const char * ssdp_response, char ** desc_url);
147 static int Get_Description(const char * desc_url, char ** description);
148 static int Get_Control_Url(UpnpController * c, const char * desc_url, const char * description);
149 static int Parse_Url(const char * desc_url, char * host, char * resource, unsigned short int * port);
150 static void Start_Desc_Element(void * userData, const char * name, const char ** atts);
151 static void End_Desc_Element(void * userData, const char * name);
152 static void Data_Desc_Handler(void * userData, const char * s, int len);
153 static void XmlDescData_Free(XmlDescData d);
154 static int Get_Public_Ip_From_Response(const char * response, char * public_ip, int public_ip_size);
155 static void Start_Ip_Element(void * userData, const char * name, const char ** atts);
156 static void End_Ip_Element(void * userData, const char * name);
157 static void Data_Ip_Handler(void * userData, const char * s, int len);
158 static int Create_Action_Message(const char * service_type, const char * action_name, const char * action_params, char ** action_message);
159 static int Send_Action_Message(const UpnpController * c, const char * action_name, const char * body, char ** response);
160 static int Get_Local_Ip(const UpnpController * c, char ** ip_map);
161 
162 /********************************************************************************
163 **                   UPnP Discover Specific Functions                           *
164 ********************************************************************************/
165 
166 /**
167  * Sends a discovery request to search for a UPnP enabled IGD that
168  * contains the WANIPConnection service or WANPPPConnection that will
169  * allow us to recieve the public IP address of the IGD, and control
170  * it for forwarding ports.
171  *
172  * Return the UpnpController for the IGD in the c parameter.
173  * User should call LNat_Upnp_Free() to free the memory allocated for
174  * this UpnpController object.
175  *
176  * Return OK if everything was successful.
177  */
178 LIBNAT_API
LNat_Upnp_Discover(UpnpController ** c)179 int LNat_Upnp_Discover(UpnpController ** c)
180 {
181   int ret;
182   char * ssdp_response;
183   char * desc_url = NULL;
184   char * description;
185 
186   /* allocate space for our controller object, and initialize it's members */
187   *c = (UpnpController *)malloc(sizeof(UpnpController));
188   if(NULL == *c) {
189     return BAD_MALLOC;
190   }
191   (*c)->control_url = NULL;
192   (*c)->service_type = NULL;
193 
194   /* send a discovery request, and get a response */
195   if((ret = Send_Ssdp_Discover(*c, &ssdp_response)) != OK) {
196     (void)LNat_Upnp_Controller_Free(c);
197     return ret;
198   }
199 
200   /* get the description URL out of the ssdp discovery request */
201   if((ret = Get_Description_Url(ssdp_response, &desc_url)) != OK) {
202     free(ssdp_response);
203     (void)LNat_Upnp_Controller_Free(c);
204     return ret;
205   }
206 
207   /* retreive the description from the desc_url */
208   if((ret = Get_Description(desc_url, &description)) != OK) {
209     free(ssdp_response);
210     free(desc_url);
211     (void)LNat_Upnp_Controller_Free(c);
212     return ret;
213   }
214 
215   /* get the control_url from the description, and store it in c */
216   if((ret = Get_Control_Url(*c, desc_url, description)) != OK) {
217     free(ssdp_response);
218     free(desc_url);
219     free(description);
220     (void)LNat_Upnp_Controller_Free(c);
221     return ret;
222   }
223 
224   free(ssdp_response);
225   free(desc_url);
226   free(description);
227 
228   return OK;
229 }
230 
231 
232 /* Send SSDP discovery requests searching for WANIPConnection and
233    WANPPPConnection services on the local network. Return OK if we
234    can find one. Also set the serviceType parameter of the
235    UpnpController structure. */
Send_Ssdp_Discover(UpnpController * c,char ** ssdp_response)236 static int Send_Ssdp_Discover(UpnpController * c, char ** ssdp_response)
237 {
238   int i;
239   int ret;
240   /* allocate space for our search string */
241   char * search_target = (char *)malloc(strlen(SSDP_TARGET) +
242                                         strlen(WANPPP_TARGET)+
243                                         NULL_TERM_LEN);
244   if(NULL == search_target) {
245     return BAD_MALLOC;
246   }
247 
248   for(i=0; i<NUM_SSDP_ATTEMPTS; i++) {
249     if(i%2 == 0) {
250       (void)sprintf(search_target, SSDP_TARGET, WANIP_TARGET);
251     } else {
252       (void)sprintf(search_target, SSDP_TARGET, WANPPP_TARGET);
253     }
254     ret = LNat_Ssdp_Discover(search_target, ssdp_response);
255 
256     /* if ret == OK, we need to use i with the val it has, so break out */
257     if(ret == OK) {
258       break;
259     }
260   }
261   if(i == NUM_SSDP_ATTEMPTS) {
262     free(search_target);
263     return ret;
264   }
265 
266   /* store the service type variable */
267   if(i%2 == 0) {
268     c->service_type = (char *)malloc(strlen(WANIP_TARGET) +
269                                      NULL_TERM_LEN);
270     if(NULL == c->service_type) {
271       free(search_target);
272       return BAD_MALLOC;
273     }
274     (void)strcpy(c->service_type, WANIP_TARGET);
275   } else {
276     c->service_type = (char *)malloc(strlen(WANPPP_TARGET) +
277                                      NULL_TERM_LEN);
278     if(NULL == c->service_type) {
279       free(search_target);
280       return BAD_MALLOC;
281     }
282     (void)strcpy(c->service_type, WANPPP_TARGET);
283   }
284   free(search_target);
285 
286   return OK;
287 }
288 
289 
290 /* Get the url to retrieve the upnp description from. This URL is parsed
291    out of the ssdp response retrieved from an ssdp request. */
Get_Description_Url(const char * ssdp_response,char ** desc_url)292 static int Get_Description_Url(const char * ssdp_response, char ** desc_url)
293 {
294   const char * fir_rn;
295   const char * sec_rn;
296   const char * end_loc_tag;
297   int end_loc_offset;
298   int desc_url_malloc_size;
299   char * ssdp_upper_response = (char *)malloc(strlen(ssdp_response) +
300                                               NULL_TERM_LEN);
301   if(NULL == ssdp_upper_response) {
302     return BAD_MALLOC;
303   }
304   /* parsing done on all caps version */
305   (void)LNat_Str_To_Upper(ssdp_response, ssdp_upper_response);
306 
307   /* if we didnt receive an HTTP OK response, return error */
308   if(NULL == strstr(ssdp_response, HTTP_OK)) {
309     free(ssdp_upper_response);
310     return SSDP_HEADER_NOT_OK;
311   }
312 
313   /* we will scan for successive pairs of /r/n's, and evaluate
314      the data between them to see if it is the location tag. if
315      it is the location tag, then copy the data between that, and
316      the last /r/n. */
317   fir_rn = ssdp_upper_response;
318   while(1) {
319     if((fir_rn = strstr(fir_rn, "\r\n")) == NULL) {
320       break;
321     }
322     fir_rn += strlen("\r\n");
323     if((sec_rn = strstr(fir_rn, "\r\n")) == NULL) {
324       break;
325     }
326     end_loc_tag = fir_rn + strlen(LOCATION_TAG);
327     end_loc_offset = end_loc_tag - ssdp_upper_response;
328     if(!strncmp(fir_rn, LOCATION_TAG, strlen(LOCATION_TAG))) {
329       desc_url_malloc_size = (sec_rn - end_loc_tag) + NULL_TERM_LEN;
330       *desc_url = (char *)malloc(desc_url_malloc_size);
331       if(NULL == *desc_url) {
332         free(ssdp_upper_response);
333         return BAD_MALLOC;
334       }
335       sscanf(ssdp_response + end_loc_offset, "%s", *desc_url);
336       free(ssdp_upper_response);
337       return OK;
338     }
339   }
340   free(ssdp_upper_response);
341 
342   /* now make sure that the url size is appropriate */
343   if(*desc_url && strlen(*desc_url)+NULL_TERM_LEN > MAX_URL_LEN) {
344     free(*desc_url);
345     return UPNP_URL_OVER_MAX_LEN;
346   }
347   return SSDP_NO_LOCATION;
348 }
349 
350 
351 /* Get the description from the description url by making an http request to it.
352    The caller is responsible for freeing the space allocated for description. */
Get_Description(const char * desc_url,char ** description)353 static int Get_Description(const char * desc_url, char ** description)
354 {
355   int ret = 0;
356   char host[MAX_HOST_LEN];
357   unsigned short int port;
358   char resource[MAX_RESOURCE_LEN];
359   GetMessage * gm;
360 
361   if((ret = Parse_Url(desc_url, host, resource, &port)) != OK) {
362     return ret;
363   }
364 
365   if((ret = LNat_Generate_Http_Get(host, resource, port, &gm)) != OK) {
366     return ret;
367   }
368   if((ret = LNat_Http_Request_Get(gm, description)) != OK) {
369     (void)LNat_Destroy_Http_Get(&gm);
370     return ret;
371   }
372 
373   (void)LNat_Destroy_Http_Get(&gm);
374   return OK;
375 }
376 
377 
378 /* get the control url from the description, which is in xml format, and
379    set the control_url variable in c equal to the string */
Get_Control_Url(UpnpController * c,const char * desc_url,const char * description)380 static int Get_Control_Url(UpnpController * c, const char * desc_url,
381                            const char * description)
382 {
383   int ret;
384   char host[MAX_HOST_LEN];
385   char host_port[MAX_HOST_LEN];
386   unsigned short int port;
387   XML_Parser parser;
388   XmlDescData xdescdat = {0, 0, 0, 0, 0, 0, 0};
389 
390   if((ret = Parse_Url(desc_url, host, NULL, &port)) != OK) {
391     return ret;
392   }
393   sprintf(host_port, "%s:%d", host, port);
394 
395   parser = XML_ParserCreate(NULL);
396   xdescdat.correct_service = (char *)malloc(strlen(SSDP_TARGET) +
397                                             strlen(c->service_type) +
398                                             NULL_TERM_LEN);
399   (void)sprintf(xdescdat.correct_service, SSDP_TARGET, c->service_type);
400 
401   XML_SetUserData(parser, &xdescdat);
402   XML_SetElementHandler(parser, Start_Desc_Element, End_Desc_Element);
403   XML_SetCharacterDataHandler(parser, Data_Desc_Handler);
404 
405   if(!XML_Parse(parser, description, (int)strlen(description), 1)) {
406     XML_ParserFree(parser);
407     XmlDescData_Free(xdescdat);
408     return UPNP_BAD_DESCRIPTION;
409   }
410 
411   /* if we didn't find a control url, then bail */
412   if(NULL == xdescdat.control_url) {
413     XmlDescData_Free(xdescdat);
414     return UPNP_BAD_DESCRIPTION;
415   }
416 
417   /* if we didn't find a base url in the description, then it is the
418      same url as the desc_url */
419   if(NULL == xdescdat.url_base) {
420     xdescdat.url_base = (char *)malloc(strlen(host_port) + NULL_TERM_LEN);
421     strcpy(xdescdat.url_base, host_port);
422   }
423 
424   /* if the control_url contains the http protocol string,
425      then don't prepend the base url to it */
426   if(strncmp(xdescdat.control_url, HTTP_PROTOCOL_STRING,
427              strlen(HTTP_PROTOCOL_STRING))  == 0) {
428     c->control_url = (char *)malloc(strlen(xdescdat.control_url) +
429                                     NULL_TERM_LEN);
430     if(NULL == c->control_url) {
431       XmlDescData_Free(xdescdat);
432       return BAD_MALLOC;
433     }
434     strcpy(c->control_url, xdescdat.control_url);
435   } else {
436     c->control_url = (char *)malloc(strlen(xdescdat.url_base)+
437                                     strlen(xdescdat.control_url) +
438                                     NULL_TERM_LEN);
439     if(NULL == c->control_url) {
440       XmlDescData_Free(xdescdat);
441       return BAD_MALLOC;
442     }
443     (void)sprintf(c->control_url, "%s%s", xdescdat.url_base, xdescdat.control_url);
444   }
445 
446   XmlDescData_Free(xdescdat);
447   XML_ParserFree(parser);
448 
449   /* now make sure that the url size is appropriate */
450   if(strlen(c->control_url)+NULL_TERM_LEN > MAX_URL_LEN) {
451     free(c->control_url);
452     c->control_url = NULL;
453     return UPNP_URL_OVER_MAX_LEN;
454   }
455   return OK;
456 }
457 
458 
XmlDescData_Free(XmlDescData d)459 static void XmlDescData_Free(XmlDescData d)
460 {
461   if(d.control_url != NULL) {
462     free(d.control_url);
463   }
464 
465   if(d.url_base != NULL) {
466     free(d.url_base);
467   }
468 
469   if(d.correct_service != NULL) {
470     free(d.correct_service);
471   }
472 }
473 
474 
475 /* parser start element handler for the description xml */
Start_Desc_Element(void * userData,const char * name,const char ** atts)476 static void Start_Desc_Element(void * userData, const char * name,
477                              const char ** atts)
478 {
479   XmlDescData * xdescdat = (XmlDescData *)userData;
480   if(strcmp(name, BASE_URL_ELEMENT) == 0) {
481     xdescdat->found_url_base = 1;
482     return;
483   }
484 
485   if(strcmp(name, SERVICE_TYPE_ELEMENT) == 0) {
486     xdescdat->found_service_type = 1;
487     return;
488   }
489 
490   if(strcmp(name, CONTROL_URL_ELEMENT) == 0) {
491     xdescdat->found_control_url = 1;
492   }
493 }
494 
495 /* parser end element handler for the description xml */
End_Desc_Element(void * userData,const char * name)496 static void End_Desc_Element(void * userData, const char * name)
497 {
498   XmlDescData * xdescdat = (XmlDescData *)userData;
499 
500   /* if we found the url base, reset the bit */
501   if(xdescdat->found_url_base) {
502     xdescdat->found_url_base = 0;
503   }
504 
505   /* if we found a service element, reset the bit */
506   if(xdescdat->found_service_type) {
507     xdescdat->found_service_type = 0;
508   }
509 
510   /* only reset the correct service flag if we found the control url */
511   if(xdescdat->found_correct_service && xdescdat->found_control_url) {
512     xdescdat->found_correct_service = 0;
513   }
514 
515   /* if we found the control url element, we are done, so reset bit */
516   if(xdescdat->found_control_url) {
517     xdescdat->found_control_url = 0;
518   }
519 }
520 
521 /* parser character data handler for the description xml */
Data_Desc_Handler(void * userData,const char * s,int len)522 static void Data_Desc_Handler(void * userData, const char * s, int len)
523 {
524   XmlDescData * xdescdat = (XmlDescData *)userData;
525 
526   /* if it is the url base we found, set it */
527   if(xdescdat->found_url_base == 1) {
528     xdescdat->url_base = (char *)malloc(len+NULL_TERM_LEN);
529     if(NULL == xdescdat->url_base) {
530       return;
531     }
532     strncpy(xdescdat->url_base, s, len);
533     xdescdat->url_base[len] = NULL_TERM;
534     return;
535   }
536 
537   /* if it is the service_type we found, see if it is the right one */
538   if(xdescdat->found_service_type == 1) {
539     /* if we fond the right one, set found correct service */
540     if(strncmp(xdescdat->correct_service, s, len) == 0) {
541       xdescdat->found_correct_service = 1;
542     }
543     return;
544   }
545 
546   /* if we found th control url and have the correct service, store controlurl */
547   if(xdescdat->found_correct_service && xdescdat->found_control_url) {
548     xdescdat->control_url = (char *)malloc(len+NULL_TERM_LEN);
549     if(NULL == xdescdat->control_url) {
550       return;
551     }
552     strncpy(xdescdat->control_url, s, len);
553     xdescdat->control_url[len] = NULL_TERM;
554     return;
555   }
556 }
557 
558 
559 
560 /********************************************************************************
561 **                      UPnP Get Public IP Functions                            *
562 ********************************************************************************/
563 
564 /**
565  * Gets the IP address from a UPnP enabled IGD that sits on the local
566  * network, so when getting the network IP, instead of returning the
567  * local network IP, the public IP is retrieved.
568  *
569  * controller: The UpnpController retrieved from LNat_Upnp_Discover.
570  *
571  * return the IP address of the network in a null terminated string
572  * in the public_ip parameter or NULL if something went wrong.
573  *
574  * Return OK if everything was successful.
575  */
576 LIBNAT_API
LNat_Upnp_Get_Public_Ip(const UpnpController * c,char * public_ip,int public_ip_size)577 int LNat_Upnp_Get_Public_Ip(const UpnpController * c,
578                             char * public_ip, int public_ip_size)
579 {
580   int ret;
581   char * body;
582   char * response;
583 
584   /* create the body of the http post request */
585   ret = Create_Action_Message(c->service_type, GET_PUBLIC_IP_ACTION_NAME,
586                               GET_PUBLIC_IP_PARAMS, &body);
587   if(OK != ret) {
588     return ret;
589   }
590 
591   ret = Send_Action_Message(c, GET_PUBLIC_IP_ACTION_NAME, body, &response);
592   if(OK != ret) {
593     free(body);
594     return ret;
595   }
596   free(body);
597 
598   ret = Get_Public_Ip_From_Response(response, public_ip, public_ip_size);
599   if(OK != ret) {
600     free(response);
601     return ret;
602   }
603   free(response);
604 
605   return OK;
606 }
607 
608 /* we have a response of the ip address, so parse it out of the xml */
Get_Public_Ip_From_Response(const char * response,char * public_ip,int public_ip_size)609 static int Get_Public_Ip_From_Response(const char * response,
610                                        char * public_ip, int public_ip_size)
611 {
612   XmlGetPublicIpData dat = {0, 0};
613   XML_Parser parser = XML_ParserCreate(NULL);
614 
615   XML_SetUserData(parser, &dat);
616   XML_SetElementHandler(parser, Start_Ip_Element, End_Ip_Element);
617   XML_SetCharacterDataHandler(parser, Data_Ip_Handler);
618 
619   if(!XML_Parse(parser, response, (int)strlen(response), 1)) {
620     XML_ParserFree(parser);
621     return UPNP_BAD_PUBLIC_IP_RESPONSE;
622   }
623 
624   if(NULL == dat.ext_ip) {
625     return UPNP_GET_PUBLIC_IP_FAILED;
626   }
627   strncpy(public_ip, dat.ext_ip, public_ip_size - NULL_TERM_LEN);
628   public_ip[public_ip_size - NULL_TERM_LEN] = NULL_TERM;
629 
630   free(dat.ext_ip);
631   XML_ParserFree(parser);
632   return OK;
633 }
634 
635 
Start_Ip_Element(void * userData,const char * name,const char ** atts)636 static void Start_Ip_Element(void * userData, const char * name,
637                              const char ** atts)
638 {
639   XmlGetPublicIpData * dat  = userData;
640   if(strcmp(name, GET_PUBLIC_IP_RESPONSE_ELEMENT) == 0) {
641     dat->found_ext_ip = 1;
642   }
643 }
644 
645 
End_Ip_Element(void * userData,const char * name)646 static void End_Ip_Element(void * userData, const char * name)
647 {
648   XmlGetPublicIpData * dat  = userData;
649   dat->found_ext_ip = 0;
650 }
651 
652 
Data_Ip_Handler(void * userData,const char * s,int len)653 static void Data_Ip_Handler(void * userData, const char * s, int len)
654 {
655   XmlGetPublicIpData * dat  = userData;
656   if(dat->found_ext_ip == 1) {
657     dat->ext_ip = (char *)malloc(len+NULL_TERM_LEN);
658     // TODO insert a check if the malloc fails
659     strncpy(dat->ext_ip, s, len);
660     dat->ext_ip[len] = NULL_TERM;
661   }
662 }
663 
664 
665 
666 /********************************************************************************
667 **                    UPnP Port Mapping Functions                               *
668 ********************************************************************************/
669 
670 
671 /**
672  * Maps Ports in a UPnP enabled IGD that sits on the local network to.
673  * Essentially, this function takes care of the port forwarding so things
674  * like file transfers can work behind NAT firewalls.
675  *
676  * controller: The UpnpController retrieved from LNat_Upnp_Discover.
677  * ipMap:      The ip to add this mapping for. If NULL, this function will
678  *             attempt to determine the IP address of this machine.
679  * portMap:    The port to map to this client
680  * protocol:   The protocol to map, either "TCP" or "UDP"
681  *
682  * return OK if successful.
683  */
684 LIBNAT_API
LNat_Upnp_Set_Port_Mapping(const UpnpController * c,const char * ip_map,short int port_map,const char * protocol)685 int LNat_Upnp_Set_Port_Mapping(const UpnpController * c,
686                                const char * ip_map,
687                                short int port_map,
688                                const char* protocol)
689 {
690   int ret;
691   char * body;
692   char * response;
693   char * params;
694   char * local_ip = NULL;
695 
696   /* if ipMap is null, attempt to get own ip address */
697   if(ip_map == NULL) {
698     if((ret = Get_Local_Ip(c, &local_ip)) != OK) {
699       return ret;
700     }
701     ip_map = local_ip;
702   }
703 
704   params = (char *)malloc(strlen(SET_PORT_MAPPING_PARAMS) +
705                                  MAX_PORT_STRING_LEN +
706                                  strlen(protocol) +
707                                  MAX_PORT_STRING_LEN +
708                                  strlen(ip_map));
709   if(NULL == params) {
710     free(local_ip);
711     return BAD_MALLOC;
712   }
713   (void)sprintf(params, SET_PORT_MAPPING_PARAMS, port_map,
714                 protocol, port_map, ip_map);
715 
716   /* create the body of the http post request */
717   ret = Create_Action_Message(c->service_type, SET_PORT_MAPPING_ACTION_NAME,
718                               params, &body);
719   if(OK != ret) {
720     free(local_ip);
721     free(params);
722     return ret;
723   }
724   free(params);
725 
726   ret = Send_Action_Message(c, SET_PORT_MAPPING_ACTION_NAME, body, &response);
727   if(OK != ret) {
728     free(local_ip);
729     free(body);
730     return ret;
731   }
732   free(local_ip);
733   free(body);
734   free(response);
735 
736   return OK;
737 }
738 
Get_Local_Ip(const UpnpController * c,char ** ip_map)739 static int Get_Local_Ip(const UpnpController * c, char ** ip_map)
740 {
741   int ret;
742   OsSocket * s;
743   char host[MAX_HOST_LEN];
744   char resource[MAX_RESOURCE_LEN];
745   unsigned short int port;
746 
747   /* parse the host, resource, and port out of the url */
748   ret = Parse_Url(c->control_url, host, resource, &port);
749   if(OK != ret) {
750     return ret;
751   }
752 
753   if((ret = LNat_Os_Socket_Connect(&s, host, port, 2)) != OK) {
754     return ret;
755   }
756 
757   if((ret = LNat_Os_Get_Local_Ip(s, ip_map)) != OK) {
758     return ret;
759   }
760 
761   (void)LNat_Os_Socket_Close(&s);
762   return OK;
763 }
764 
765 
766 /**
767  * Deletes a port mapping in a UPnP enabled IGD that sits on the local network.
768  * Essentially, this function takes care of deleting the port forwarding after
769  * they have completed a connection so another client on the local network can
770  * take advantage of the port forwarding
771  *
772  * controller: The UpnpController retrieved from LNat_Upnp_Discover.
773  * ipMap:      The ip to delete this mapping for. If NULL, this function will
774  *             attempt to determine the IP address of this machine.
775  * portMap:    The port to delete the mapping for
776  * protocol :  The protocol the port was mapped to. Either "TCP" or "UDP"
777  *
778  * return OK if successful.
779  */
780 LIBNAT_API
LNat_Upnp_Remove_Port_Mapping(const UpnpController * c,short int port_map,const char * protocol)781 int LNat_Upnp_Remove_Port_Mapping(const UpnpController * c,
782                                   short int port_map,
783                                   const char* protocol)
784 {
785   int ret;
786   char * body;
787   char * response;
788   char * params;
789 
790   params = (char *)malloc(strlen(REMOVE_PORT_MAPPING_PARAMS) +
791                                  MAX_PORT_STRING_LEN +
792                                  strlen(protocol));
793   if(NULL == params) {
794     return BAD_MALLOC;
795   }
796   (void)sprintf(params, REMOVE_PORT_MAPPING_PARAMS, port_map, protocol);
797 
798   /* create the body of the http post request */
799   ret = Create_Action_Message(c->service_type, REMOVE_PORT_MAPPING_ACTION_NAME,
800                               params, &body);
801   if(OK != ret) {
802     free(params);
803     return ret;
804   }
805   free(params);
806 
807   ret = Send_Action_Message(c, REMOVE_PORT_MAPPING_ACTION_NAME, body, &response);
808   if(OK != ret) {
809     free(body);
810     return ret;
811   }
812   free(response);
813   free(body);
814 
815   return OK;
816 }
817 
818 
819 
820 
821 /********************************************************************************
822 **                        Other Helper Functions                                *
823 ********************************************************************************/
824 
825 /**
826  * Destroy a UpnpController object that was allocated by LNat_Upnp_Discover.
827  * return OK if successful.
828  */
829 LIBNAT_API
LNat_Upnp_Controller_Free(UpnpController ** controller)830 int LNat_Upnp_Controller_Free(UpnpController ** controller)
831 {
832   if(NULL == *controller) {
833     return OK;
834   }
835   if(NULL != (*controller)->control_url) {
836     free((*controller)->control_url);
837   }
838   if(NULL != (*controller)->service_type) {
839     free((*controller)->service_type);
840   }
841   free(*controller);
842   return OK;
843 }
844 
845 
846 /* send an action message using http post */
Send_Action_Message(const UpnpController * c,const char * action_name,const char * body,char ** response)847 static int Send_Action_Message(const UpnpController * c,
848                                const char * action_name,
849                                const char * body,
850                                char ** response)
851 {
852   int ret;
853   char host[MAX_HOST_LEN];
854   char resource[MAX_RESOURCE_LEN];
855   unsigned short int port;
856   char * soap_action_header;
857   char * content_length_header;
858   PostMessage * pm;
859 
860   /* parse the host, resource, and port out of the url */
861   ret = Parse_Url(c->control_url, host, resource, &port);
862   if(OK != ret) {
863     return ret;
864   }
865 
866   /* generate an http_post request */
867   ret = LNat_Generate_Http_Post(host, resource, port, body, &pm);
868   if(OK != ret) {
869     return ret;
870   }
871 
872   /* add some entity headers, start with content length */
873   content_length_header = (char *)malloc(MAX_CONTENT_STRING_LEN +
874                                          NULL_TERM_LEN);
875   if(NULL == content_length_header) {
876     LNat_Destroy_Http_Post(&pm);
877     return BAD_MALLOC;
878   }
879   (void)sprintf(content_length_header, CONTENT_LENGTH_VAL, (int)strlen(body));
880   ret = LNat_Http_Post_Add_Entity_Header(pm, CONTENT_LENGTH_TAG, content_length_header);
881   if(OK != ret) {
882     free(content_length_header);
883     LNat_Destroy_Http_Post(&pm);
884     return ret;
885   }
886   free(content_length_header);
887 
888   /* add content type entity header */
889   ret = LNat_Http_Post_Add_Entity_Header(pm, CONTENT_TYPE_TAG, CONTENT_TYPE_VAL);
890   if(OK != ret) {
891     LNat_Destroy_Http_Post(&pm);
892     return ret;
893   }
894 
895   /* add soap action entity header */
896   soap_action_header = (char *)malloc(strlen(SOAP_ACTION_VAL) +
897                                       strlen(c->service_type) +
898                                       strlen(action_name) +
899                                       NULL_TERM_LEN);
900   if(NULL == soap_action_header) {
901     LNat_Destroy_Http_Post(&pm);
902     return BAD_MALLOC;
903   }
904   (void)sprintf(soap_action_header, SOAP_ACTION_VAL, c->service_type, action_name);
905   ret = LNat_Http_Post_Add_Entity_Header(pm, SOAP_ACTION_TAG, soap_action_header);
906   if(OK != ret) {
907     free(soap_action_header);
908     LNat_Destroy_Http_Post(&pm);
909     return ret;
910   }
911   free(soap_action_header);
912 
913   /* now send the http request */
914   ret = LNat_Http_Request_Post(pm, response);
915   if(OK != ret) {
916     return ret;
917   }
918 
919   (void)LNat_Destroy_Http_Post(&pm);
920   return OK;
921 }
922 
923 
924 
925 /* create an action message for one of the action requests, such as
926    Get_Public_Ip, Set_Port_Mapping, and Remove_Port_Mapping */
Create_Action_Message(const char * service_type,const char * action_name,const char * action_params,char ** action_message)927 static int Create_Action_Message(const char * service_type,
928                                  const char * action_name,
929                                  const char * action_params,
930                                  char ** action_message)
931 {
932   *action_message = (char *)malloc(strlen(SOAP_ACTION) +
933                                    strlen(service_type) +
934                                    strlen(action_name) +
935                                    strlen(action_params) +
936                                    strlen(action_name) +
937                                    NULL_TERM_LEN);
938   if(NULL == *action_message) {
939     return BAD_MALLOC;
940   }
941 
942   (void)sprintf(*action_message, SOAP_ACTION, action_name,
943                 service_type, action_params, action_name);
944   return OK;
945 }
946 
947 
948 
949 
Parse_Url(const char * url,char * host,char * resource,unsigned short int * port)950 static int Parse_Url(const char * url, char * host,
951                      char * resource, unsigned short int * port)
952 {
953   char * loc_of_semicolon;
954   char * loc_of_slash;
955   char * end_of_host_loc;
956   char * loc_of_resource;
957   char * loc_of_port;
958   char url_wo_http[MAX_HOST_LEN];
959 
960   if(sscanf(url, "http://%255s", url_wo_http) != 1) {
961     if(sscanf(url, "%255s", url_wo_http) != 1) {
962       return HTTP_INVALID_URL;
963     }
964   }
965 
966   loc_of_semicolon = strchr(url_wo_http, ':');
967   loc_of_slash = strchr(url_wo_http, '/');
968 
969   /* we need a slash for the resource */
970   if(NULL == loc_of_slash) {
971     return HTTP_INVALID_URL;
972   } else {
973     loc_of_resource = loc_of_slash+1;
974   }
975 
976   /* dont necessarily need a port, so if we don't have one default port is 80 */
977   if(NULL == loc_of_semicolon || loc_of_semicolon > loc_of_slash) {
978     loc_of_semicolon = NULL;
979     if(NULL != port) {
980       *port = DEFAULT_HTTP_PORT;
981     }
982     end_of_host_loc = loc_of_slash;
983   } else {
984     /* port is right after the semicolon */
985     loc_of_port = loc_of_semicolon + 1;
986     /* make sure it is not larger than MAX_PORT_STRING_LENGTH */
987     if((loc_of_slash - loc_of_port) > MAX_PORT_STRING_LEN) {
988       return HTTP_INVALID_URL;
989     }
990     end_of_host_loc = loc_of_semicolon;
991   }
992 
993   /* store the host */
994   if(NULL != host) {
995     strncpy(host, url_wo_http, end_of_host_loc - url_wo_http);
996     host[end_of_host_loc - url_wo_http] = NULL_TERM;
997   }
998   /* extract the port */
999   if(NULL != loc_of_semicolon) {
1000     if(sscanf(loc_of_port, "%hu", port) != 1) {
1001       *port = DEFAULT_HTTP_PORT;
1002     }
1003     if(*port > MAX_PORT_SIZE) {
1004       return HTTP_INVALID_URL;
1005     }
1006   }
1007   /* extract the resource */
1008   if(NULL != resource) {
1009     strcpy(resource, loc_of_resource);
1010   }
1011 
1012   return OK;
1013 }
1014 
1015 
1016