]> git.saurik.com Git - apple/mdnsresponder.git/blame - mDNSMacOSX/LegacyNATTraversal.c
mDNSResponder-214.3.2.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / LegacyNATTraversal.c
CommitLineData
7f0064bd
A
1/* -*- Mode: C; tab-width: 4 -*-
2 *
3 * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
4 *
67c8f8a1
A
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
7f0064bd 15 * limitations under the License.
7f0064bd
A
16
17 Change History (most recent first):
18
19$Log: LegacyNATTraversal.c,v $
32bb7e43
A
20Revision 1.65 2009/07/03 03:16:07 jessic2
21<rdar://problem/7026146> BTMM: UPnP works in Leopard but doesn't work in SnowLeopard (URLBase is empty) Made changes to support the case where the URLBase tag exists but there isn't a valid URL
22
23Revision 1.64 2009/06/25 21:07:44 herscher
24<rdar://problem/4147784> B4W should support UPnP
25
26Revision 1.63 2009/03/26 03:59:00 jessic2
27Changes for <rdar://problem/6492552&6492593&6492609&6492613&6492628&6492640&6492699>
28
29Revision 1.62 2009/02/13 06:31:09 cheshire
30Converted LogOperation messages to LogInfo
31
32Revision 1.61 2009/01/23 19:25:43 mcguire
33<rdar://problem/6514439> UPnP: Should not use NATErr_Refused when too many conflict retries
34
35Revision 1.60 2009/01/23 00:38:36 mcguire
36<rdar://problem/5570906> BTMM: Doesn't work with Linksys WRT54GS firmware 4.71.1
37
38Revision 1.59 2009/01/22 20:32:17 mcguire
39<rdar://problem/6446934> BTMM: pref pane reports enabled but negotiation failed
40Make sure we push the pointer out past the LF if we read it.
41
42Revision 1.58 2009/01/22 01:15:58 mcguire
43<rdar://problem/6446934> BTMM: pref pane reports enabled but negotiation failed
44
45Revision 1.57 2008/12/19 21:09:22 mcguire
46<rdar://problem/6431147> UPnP: error messages when canceling seemingly unrelated browse
47
48Revision 1.56 2008/12/06 01:42:57 mcguire
49<rdar://problem/6418958> Need to exponentially back-off after failure to get public address
50
51Revision 1.55 2008/12/01 19:43:48 mcguire
52<rdar://problem/6404766> UPnP: Handle errorCode 718 as a conflict when requesting a port mapping
53
54Revision 1.54 2008/11/26 20:57:37 cheshire
55For consistency with other similar macros, renamed mdnsIsDigit/mdnsIsLetter/mdnsValidHostChar
56to mDNSIsDigit/mDNSIsLetter/mDNSValidHostChar
57
58Revision 1.53 2008/11/26 20:34:04 cheshire
59Changed "destroying SSDPSocket" LogOperation debugging messages to debugf
60
61Revision 1.52 2008/11/26 19:54:03 cheshire
62Changed some "LogOperation" debugging messages to "debugf"
63
64Revision 1.51 2008/11/20 02:23:38 mcguire
65<rdar://problem/6041208> need to handle URLBase
66
67Revision 1.50 2008/09/20 00:34:22 mcguire
51e746cf
A
68<rdar://problem/6129039> BTMM: Add support for WANPPPConnection
69
32bb7e43
A
70Revision 1.49 2008/08/07 21:51:13 mcguire
71<rdar://problem/5904423> UPnP: Possible memory corruption bug
72<rdar://problem/5930173> UPnP: Combine URL parsing code
73
9f29194f
A
74Revision 1.48 2008/07/24 20:23:04 cheshire
75<rdar://problem/3988320> Should use randomized source ports and transaction IDs to avoid DNS cache poisoning
76
77Revision 1.47 2008/07/18 21:37:46 mcguire
78<rdar://problem/5736845> BTMM: alternate SSDP queries between multicast & unicast
79
80Revision 1.46 2008/05/13 01:51:12 mcguire
81<rdar://problem/5839161> UPnP compatibility workaround for Netgear WGT624
82
030b743d
A
83Revision 1.45 2007/12/06 00:22:27 mcguire
84<rdar://problem/5604567> BTMM: Doesn't work with Linksys WAG300N 1.01.06 (sending from 1026/udp)
85
96f69b28
A
86Revision 1.44 2007/11/02 20:45:40 cheshire
87Don't log "connection failed" in customer builds
88
89Revision 1.43 2007/10/18 20:09:47 cheshire
90<rdar://problem/5545930> BTMM: Back to My Mac not working with D-Link DGL-4100 NAT gateway
91
92Revision 1.42 2007/10/16 17:37:18 cheshire
93<rdar://problem/3557903> Performance: Core code will not work on platforms with small stacks
94Cut SendSOAPMsgControlAction stack from 2144 to 96 bytes
95
96Revision 1.41 2007/10/15 23:02:00 cheshire
97Off-by-one error: Incorrect trailing zero byte on the end of the SSDP Discovery message
98
67c8f8a1
A
99Revision 1.40 2007/09/20 21:41:49 cheshire
100<rdar://problem/5495568> Legacy NAT Traversal - unmap request failed with error -65549
101
102Revision 1.39 2007/09/20 20:41:40 cheshire
103Reordered functions in file, in preparation for following fix
104
105Revision 1.38 2007/09/18 21:42:30 cheshire
106To reduce programming mistakes, renamed ExtPort to RequestedPort
107
108Revision 1.37 2007/09/14 21:26:09 cheshire
109<rdar://problem/5482627> BTMM: Need to manually avoid port conflicts when using UPnP gateways
110
111Revision 1.36 2007/09/14 01:15:50 cheshire
112Minor fixes for problems discovered in pre-submission testing
113
114Revision 1.35 2007/09/13 00:16:42 cheshire
115<rdar://problem/5468706> Miscellaneous NAT Traversal improvements
116
117Revision 1.34 2007/09/12 23:03:08 cheshire
118<rdar://problem/5476978> DNSServiceNATPortMappingCreate callback not giving correct interface index
119
120Revision 1.33 2007/09/12 19:22:20 cheshire
121Variable renaming in preparation for upcoming fixes e.g. priv/pub renamed to intport/extport
122Made NAT Traversal packet handlers take typed data instead of anonymous "mDNSu8 *" byte pointers
123
124Revision 1.32 2007/09/11 19:19:16 cheshire
125Correct capitalization of "uPNP" to "UPnP"
126
127Revision 1.31 2007/09/10 22:14:16 cheshire
128When constructing fake NATAddrReply or NATPortMapReply packet, need to calculate
129plausible upseconds value or core logic will think NAT engine has been rebooted
130
131Revision 1.30 2007/09/05 20:46:17 cheshire
132Tidied up alignment of code layout
133
134Revision 1.29 2007/08/03 20:18:01 vazquez
135<rdar://problem/5382177> LegacyNATTraversal: reading out of bounds can lead to DoS
fbae3d85 136
67c8f8a1
A
137Revision 1.28 2007/07/31 02:28:36 vazquez
138<rdar://problem/3734269> NAT-PMP: Detect public IP address changes and base station reboot
139
140Revision 1.27 2007/07/30 23:17:03 vazquez
141Since lease times are meaningless in UPnP, return NATMAP_DEFAULT_LEASE in UPnP port mapping reply
142
143Revision 1.26 2007/07/27 22:50:08 vazquez
144Allocate memory for UPnP request and reply buffers instead of using arrays
145
146Revision 1.25 2007/07/27 20:33:44 vazquez
147Make sure we clean up previous port mapping requests before starting an unmap
148
149Revision 1.24 2007/07/27 00:57:48 vazquez
150If a tcp connection is already established for doing a port mapping, don't start it again
151
152Revision 1.23 2007/07/26 21:19:26 vazquez
153Retry port mapping with incremented port number (up to max) in order to handle
154port mapping conflicts on UPnP gateways
155
156Revision 1.22 2007/07/25 21:41:00 vazquez
157Make sure we clean up opened sockets when there are network transitions and when changing
158port mappings
159
160Revision 1.21 2007/07/25 03:05:03 vazquez
161Fixes for:
162<rdar://problem/5338913> LegacyNATTraversal: UPnP heap overflow
163<rdar://problem/5338933> LegacyNATTraversal: UPnP stack buffer overflow
164and a myriad of other security problems
165
166Revision 1.20 2007/07/16 20:15:10 vazquez
167<rdar://problem/3867231> LegacyNATTraversal: Need complete rewrite
168
169Revision 1.19 2007/06/21 16:37:43 jgraessley
170Bug #: 5280520
171Reviewed by: Stuart Cheshire
172Additional changes to get this compiling on the embedded platform.
173
174Revision 1.18 2007/05/09 01:43:32 cheshire
175<rdar://problem/5187028> Change sprintf and strcpy to their safer snprintf and strlcpy equivalents
176
177Revision 1.17 2007/02/27 02:48:25 cheshire
178Parameter to LNT_GetPublicIP function is IPv4 address, not anonymous "mDNSOpaque32" object
179
180Revision 1.16 2006/08/14 23:24:39 cheshire
181Re-licensed mDNSResponder daemon source code under Apache License, Version 2.0
182
183Revision 1.15 2006/07/05 23:30:57 cheshire
184Rename LegacyNATInit() -> LNT_Init()
c9d2d929
A
185
186Revision 1.14 2005/12/08 03:00:33 cheshire
187<rdar://problem/4349971> Byte order bugs in Legacy NAT traversal code
188
189Revision 1.13 2005/09/07 18:23:05 ksekar
190<rdar://problem/4151514> Off-by-one overflow in LegacyNATTraversal
4aea607d 191
05292456
A
192Revision 1.12 2005/07/22 21:36:16 ksekar
193Fix GCC 4.0/Intel compiler warnings
194
7f0064bd
A
195Revision 1.11 2004/12/03 03:34:20 ksekar
196<rdar://problem/3882674> LegacyNATTraversal.c leaks threads
197
198Revision 1.10 2004/12/01 02:43:49 cheshire
199Update copyright message
200
201Revision 1.9 2004/10/27 02:25:05 cheshire
202<rdar://problem/3816029> Random memory smashing bug
203
204Revision 1.8 2004/10/27 02:17:21 cheshire
205Turn off "safe_close: ERROR" error messages -- there are too many of them
206
207Revision 1.7 2004/10/26 21:15:40 cheshire
208<rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0
209Additional fixes: Code should set fds to -1 after closing sockets.
210
211Revision 1.6 2004/10/26 20:59:20 cheshire
212<rdar://problem/3854314> Legacy NAT traversal code closes file descriptor 0
213
214Revision 1.5 2004/10/26 01:01:35 cheshire
215Use "#if 0" instead of commenting out code
216
217Revision 1.4 2004/10/10 06:51:36 cheshire
218Declared some strings "const" as appropriate
219
220Revision 1.3 2004/09/21 23:40:12 ksekar
221<rdar://problem/3810349> mDNSResponder to return errors on NAT traversal failure
222
223Revision 1.2 2004/09/17 01:08:52 cheshire
224Renamed mDNSClientAPI.h to mDNSEmbeddedAPI.h
225 The name "mDNSClientAPI.h" is misleading to new developers looking at this code. The interfaces
226 declared in that file are ONLY appropriate to single-address-space embedded applications.
227 For clients on general-purpose computers, the interfaces defined in dns_sd.h should be used.
228
229Revision 1.1 2004/08/18 17:35:41 ksekar
230<rdar://problem/3651443>: Feature #9586: Need support for Legacy NAT gateways
67c8f8a1 231*/
7f0064bd 232
67c8f8a1 233#ifdef _LEGACY_NAT_TRAVERSAL_
7f0064bd 234
67c8f8a1
A
235#include "stdlib.h" // For strtol()
236#include "string.h" // For strlcpy(), For strncpy(), strncasecmp()
32bb7e43
A
237
238#if defined( WIN32 )
239# include <winsock2.h>
240# include <ws2tcpip.h>
241# define strcasecmp _stricmp
242# define strncasecmp _strnicmp
243# define mDNSASLLog( UUID, SUBDOMAIN, RESULT, SIGNATURE, FORMAT, ... ) ;
244
245static int
246inet_pton( int family, const char * addr, void * dst )
247 {
248 struct sockaddr_storage ss;
249 int sslen = sizeof( ss );
250
251 ZeroMemory( &ss, sizeof( ss ) );
252 ss.ss_family = family;
253
254 if ( WSAStringToAddressA( addr, family, NULL, ( struct sockaddr* ) &ss, &sslen ) == 0 )
255 {
256 if ( family == AF_INET ) { memcpy( dst, &( ( struct sockaddr_in* ) &ss)->sin_addr, sizeof( IN_ADDR ) ); return 1; }
257 else if ( family == AF_INET6 ) { memcpy( dst, &( ( struct sockaddr_in6* ) &ss)->sin6_addr, sizeof( IN6_ADDR ) ); return 1; }
258 else return 0;
259 }
260 else return 0;
261 }
262#else
263# include <arpa/inet.h> // For inet_pton()
264#endif
7f0064bd
A
265
266#include "mDNSEmbeddedAPI.h"
67c8f8a1 267#include "uDNS.h" // For natTraversalHandleAddressReply() etc.
7f0064bd 268
67c8f8a1
A
269// used to format SOAP port mapping arguments
270typedef struct Property_struct
271 {
272 char *name;
273 char *type;
274 char *value;
275 } Property;
276
277// All of the text parsing in this file is intentionally transparent so that we know exactly
278// what's being done to the text, with an eye towards preventing security problems.
279
280// This is an evolving list of useful acronyms to know. Please add to it at will.
281// ST Service Type
282// NT Notification Type
283// USN Unique Service Name
284// UDN Unique Device Name
285// UUID Universally Unique Identifier
286// URN/urn Universal Resource Name
287
288// Forward declaration because of circular reference:
289// SendPortMapRequest -> SendSOAPMsgControlAction -> MakeTCPConnection -> tcpConnectionCallback -> handleLNTPortMappingResponse
290// In the event of a port conflict, handleLNTPortMappingResponse then increments tcpInfo->retries and calls back to SendPortMapRequest to try again
291mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n);
292
293#define RequestedPortNum(n) (mDNSVal16(mDNSIPPortIsZero((n)->RequestedPort) ? (n)->IntPort : (n)->RequestedPort) + (n)->tcpInfo.retries)
294
32bb7e43
A
295// Note that this function assumes src is already NULL terminated
296mDNSlocal void AllocAndCopy(mDNSu8** dst, mDNSu8* src)
297 {
298 if (src == mDNSNULL) return;
299 if ((*dst = (mDNSu8 *) mDNSPlatformMemAllocate(strlen((char*)src) + 1)) == mDNSNULL) { LogMsg("AllocAndCopy: can't allocate string"); return; }
300 strcpy((char *)*dst, (char*)src);
301 }
302
303// This function does a simple parse of an HTTP URL that may include a hostname, port, and path
304// If found in the URL, addressAndPort and path out params will point to newly allocated space (and will leak if they were previously pointing at allocated space)
305mDNSlocal mStatus ParseHttpUrl(char* ptr, char* end, mDNSu8** addressAndPort, mDNSIPPort* port, mDNSu8** path)
306 {
307 // if the data begins with "http://", we assume there is a hostname and possibly a port number
308 if (end - ptr >= 7 && strncasecmp(ptr, "http://", 7) == 0)
309 {
310 int i;
311 char* stop = end;
312 char* addrPtr = mDNSNULL;
313
314 ptr += 7; //skip over "http://"
315 if (ptr >= end) { LogInfo("ParseHttpUrl: past end of buffer parsing host:port"); return mStatus_BadParamErr; }
316
317 // find the end of the host:port
318 addrPtr = ptr;
319 for (i = 0; addrPtr && addrPtr != end; i++, addrPtr++) if (*addrPtr == '/') break;
320
321 // allocate the buffer (len i+1 so we have space to terminate the string)
322 if ((*addressAndPort = (mDNSu8 *) mDNSPlatformMemAllocate(i+1)) == mDNSNULL) { LogMsg("ParseHttpUrl: can't allocate address string"); return mStatus_NoMemoryErr; }
323 strncpy((char *)*addressAndPort, ptr, i);
324 (*addressAndPort)[i] = '\0';
325
326 // find the port number in the string, by looking backwards for the ':'
327 stop = ptr; // can't go back farther than the original start
328 ptr = addrPtr; // move ptr to the path part
329
330 for (addrPtr--;addrPtr>stop;addrPtr--)
331 {
332 if (*addrPtr == ':')
333 {
334 int tmpport;
335 addrPtr++; // skip over ':'
336 tmpport = (int)strtol(addrPtr, mDNSNULL, 10);
337 *port = mDNSOpaque16fromIntVal(tmpport); // store it properly converted
338 break;
339 }
340 }
341 }
342
343 // ptr should now point to the first character we haven't yet processed
344 // everything that remains is the path
345 if (path && ptr < end)
346 {
347 if ((*path = (mDNSu8 *)mDNSPlatformMemAllocate(end - ptr + 1)) == mDNSNULL) { LogMsg("ParseHttpUrl: can't mDNSPlatformMemAllocate path"); return mStatus_NoMemoryErr; }
348 strncpy((char *)*path, ptr, end - ptr);
349 (*path)[end - ptr] = '\0';
350 }
351
352 return mStatus_NoError;
353 }
354
355enum
356 {
357 HTTPCode_NeedMoreData = -1, // No code found in stream
358 HTTPCode_Other = -2, // Valid code other than those below found in stream
359 HTTPCode_Bad = -3,
360 HTTPCode_200 = 200,
361 HTTPCode_404 = 404,
362 HTTPCode_500 = 500,
363 };
364
365mDNSlocal mDNSs16 ParseHTTPResponseCode(mDNSu8** data, mDNSu8* end)
366 {
367 mDNSu8* ptr = *data;
368 char * code;
369
370 if (end - ptr < 5) return HTTPCode_NeedMoreData;
371 if (strncasecmp((char*)ptr, "HTTP/", 5) != 0) return HTTPCode_Bad;
372 ptr += 5;
373 // should we care about the HTTP protocol version?
374
375 // look for first space, which must come before first LF
376 while (ptr && ptr != end)
377 {
378 if (*ptr == '\n') return HTTPCode_Bad;
379 if (*ptr == ' ') break;
380 ptr++;
381 }
382 if (ptr == end) return HTTPCode_NeedMoreData;
383 ptr++;
384
385 if (end - ptr < 3) return HTTPCode_NeedMoreData;
386
387 code = (char*)ptr;
388 ptr += 3;
389 while (ptr && ptr != end)
390 {
391 if (*ptr == '\n') break;
392 ptr++;
393 }
394 if (ptr == end) return HTTPCode_NeedMoreData;
395 *data = ++ptr;
396
397 if (memcmp(code, "200", 3) == 0) return HTTPCode_200;
398 if (memcmp(code, "404", 3) == 0) return HTTPCode_404;
399 if (memcmp(code, "500", 3) == 0) return HTTPCode_500;
400
401 LogInfo("ParseHTTPResponseCode found unexpected result code: %c%c%c", code[0], code[1], code[2]);
402 return HTTPCode_Other;
403 }
404
67c8f8a1 405// This function parses the xml body of the device description response from the router. Basically, we look to make sure this is a response
51e746cf 406// referencing a service we care about (WANIPConnection or WANPPPConnection), look for the "controlURL" header immediately following, and copy the addressing and URL info we need
67c8f8a1 407mDNSlocal void handleLNTDeviceDescriptionResponse(tcpLNTInfo *tcpInfo)
7f0064bd 408 {
9f29194f
A
409 mDNS *m = tcpInfo->m;
410 char *ptr = (char *)tcpInfo->Reply;
411 char *end = (char *)tcpInfo->Reply + tcpInfo->nread;
412 char *stop = mDNSNULL;
32bb7e43
A
413 mDNSs16 http_result;
414
415 if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return; // already have the info we need
416
417 http_result = ParseHTTPResponseCode((mDNSu8**)&ptr, (mDNSu8*)end); // Note: modifies ptr
418 if (http_result == HTTPCode_404) LNT_ClearState(m);
419 if (http_result != HTTPCode_200)
420 {
421 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "noop", "HTTP Result", "HTTP code: %d", http_result);
422 return;
423 }
7f0064bd 424
51e746cf
A
425 // Always reset our flag to use WANIPConnection. We'll use WANPPPConnection if we find it and don't find WANIPConnection.
426 m->UPnPWANPPPConnection = mDNSfalse;
427
428 // find either service we care about
32bb7e43 429 while (ptr && ptr < end)
7f0064bd 430 {
51e746cf 431 if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break;
67c8f8a1 432 ptr++;
7f0064bd 433 }
51e746cf
A
434 if (ptr == end)
435 {
436 ptr = (char *)tcpInfo->Reply;
32bb7e43 437 while (ptr && ptr < end)
51e746cf
A
438 {
439 if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0))
440 {
441 m->UPnPWANPPPConnection = mDNStrue;
442 break;
443 }
444 ptr++;
445 }
446 }
32bb7e43 447 if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find WANIPConnection:1 or WANPPPConnection:1 string"); return; }
67c8f8a1
A
448
449 // find "controlURL", starting from where we left off
32bb7e43 450 while (ptr && ptr < end)
7f0064bd 451 {
67c8f8a1
A
452 if (*ptr == 'c' && (strncasecmp(ptr, "controlURL", 10) == 0)) break; // find the first 'c'; is this controlURL? if not, keep looking
453 ptr++;
7f0064bd 454 }
32bb7e43 455 if (ptr == mDNSNULL || ptr == end) { LogInfo("handleLNTDeviceDescriptionResponse: didn't find controlURL string"); return; }
67c8f8a1 456 ptr += 11; // skip over "controlURL>"
32bb7e43 457 if (ptr >= end) { LogInfo("handleLNTDeviceDescriptionResponse: past end of buffer and no body!"); return; } // check ptr again in case we skipped over the end of the buffer
7f0064bd 458
9f29194f 459 // find the end of the controlURL element
32bb7e43 460 for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } }
9f29194f
A
461
462 // fill in default port
463 m->UPnPSOAPPort = m->UPnPRouterPort;
464
32bb7e43
A
465 // free string pointers and set to NULL
466 if (m->UPnPSOAPAddressString != mDNSNULL)
7f0064bd 467 {
32bb7e43
A
468 mDNSPlatformMemFree(m->UPnPSOAPAddressString);
469 m->UPnPSOAPAddressString = mDNSNULL;
470 }
471 if (m->UPnPSOAPURL != mDNSNULL)
472 {
473 mDNSPlatformMemFree(m->UPnPSOAPURL);
474 m->UPnPSOAPURL = mDNSNULL;
475 }
476
477 if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, &m->UPnPSOAPURL) != mStatus_NoError) return;
478 // the SOAPURL should look something like "/uuid:0013-108c-4b3f0000f3dc"
67c8f8a1 479
32bb7e43
A
480 if (m->UPnPSOAPAddressString == mDNSNULL)
481 {
482 ptr = (char *)tcpInfo->Reply;
483 while (ptr && ptr < end)
9f29194f 484 {
32bb7e43
A
485 if (*ptr == 'U' && (strncasecmp(ptr, "URLBase", 7) == 0)) break;
486 ptr++;
487 }
488
489 if (ptr < end) // found URLBase
490 {
491 LogInfo("handleLNTDeviceDescriptionResponse: found URLBase");
492 ptr += 8; // skip over "URLBase>"
493 // find the end of the URLBase element
494 for (stop = ptr; stop < end; stop++) { if (*stop == '<') { end = stop; break; } }
495 if (ParseHttpUrl(ptr, end, &m->UPnPSOAPAddressString, &m->UPnPSOAPPort, mDNSNULL) != mStatus_NoError)
9f29194f 496 {
32bb7e43 497 LogInfo("handleLNTDeviceDescriptionResponse: failed to parse URLBase");
9f29194f
A
498 }
499 }
9f29194f 500
32bb7e43
A
501 // if all else fails, use the router address string
502 if (m->UPnPSOAPAddressString == mDNSNULL) AllocAndCopy(&m->UPnPSOAPAddressString, m->UPnPRouterAddressString);
7f0064bd 503 }
32bb7e43
A
504 if (m->UPnPSOAPAddressString == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPAddressString is NULL");
505 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP address string [%s]", m->UPnPSOAPAddressString);
7f0064bd 506
32bb7e43
A
507 if (m->UPnPSOAPURL == mDNSNULL) AllocAndCopy(&m->UPnPSOAPURL, m->UPnPRouterURL);
508 if (m->UPnPSOAPURL == mDNSNULL) LogMsg("handleLNTDeviceDescriptionResponse: UPnPSOAPURL is NULL");
509 else LogInfo("handleLNTDeviceDescriptionResponse: SOAP URL [%s]", m->UPnPSOAPURL);
7f0064bd
A
510 }
511
67c8f8a1 512mDNSlocal void handleLNTGetExternalAddressResponse(tcpLNTInfo *tcpInfo)
7f0064bd 513 {
67c8f8a1
A
514 mDNS *m = tcpInfo->m;
515 mDNSu16 err = NATErr_None;
516 mDNSv4Addr ExtAddr;
32bb7e43
A
517 mDNSu8 *ptr = (mDNSu8*)tcpInfo->Reply;
518 mDNSu8 *end = (mDNSu8*)tcpInfo->Reply + tcpInfo->nread;
519 mDNSu8 *addrend;
96f69b28 520 static char tagname[20] = "NewExternalIPAddress"; // Array NOT including a terminating nul
7f0064bd 521
32bb7e43 522// LogInfo("handleLNTGetExternalAddressResponse: %s", ptr);
7f0064bd 523
32bb7e43
A
524 mDNSs16 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
525 if (http_result == HTTPCode_404) LNT_ClearState(m);
526 if (http_result != HTTPCode_200)
527 {
528 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "HTTP Result", "HTTP code: %d", http_result);
529 return;
530 }
531
532
533 while (ptr < end && strncasecmp((char*)ptr, tagname, sizeof(tagname))) ptr++;
96f69b28
A
534 ptr += sizeof(tagname); // Skip over "NewExternalIPAddress"
535 while (ptr < end && *ptr != '>') ptr++;
536 ptr += 1; // Skip over ">"
537 // Find the end of the address and terminate the string so inet_pton() can convert it
538 addrend = ptr;
32bb7e43 539 while (addrend < end && (mDNSIsDigit(*addrend) || *addrend == '.')) addrend++;
96f69b28
A
540 if (addrend >= end) return;
541 *addrend = 0;
7f0064bd 542
32bb7e43
A
543 if (inet_pton(AF_INET, (char*)ptr, &ExtAddr) <= 0)
544 {
545 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", "noop", "inet_pton", "");
546 LogMsg("handleLNTGetExternalAddressResponse: Router returned bad address %s", ptr);
547 err = NATErr_NetFail;
548 ExtAddr = zerov4Addr;
549 }
550 if (!err) LogInfo("handleLNTGetExternalAddressResponse: External IP address is %.4a", &ExtAddr);
7f0064bd 551
67c8f8a1
A
552 natTraversalHandleAddressReply(m, err, ExtAddr);
553 }
7f0064bd 554
67c8f8a1 555mDNSlocal void handleLNTPortMappingResponse(tcpLNTInfo *tcpInfo)
7f0064bd 556 {
67c8f8a1
A
557 mDNS *m = tcpInfo->m;
558 mDNSIPPort extport = zeroIPPort;
32bb7e43
A
559 mDNSu8 *ptr = (mDNSu8*)tcpInfo->Reply;
560 mDNSu8 *end = (mDNSu8*)tcpInfo->Reply + tcpInfo->nread;
67c8f8a1 561 NATTraversalInfo *natInfo;
32bb7e43 562 mDNSs16 http_result;
7f0064bd 563
67c8f8a1 564 for (natInfo = m->NATTraversals; natInfo; natInfo=natInfo->next) { if (natInfo == tcpInfo->parentNATInfo) break; }
96f69b28 565
32bb7e43 566 if (!natInfo) { LogInfo("handleLNTPortMappingResponse: can't find matching tcpInfo in NATTraversals!"); return; }
7f0064bd 567
32bb7e43
A
568 http_result = ParseHTTPResponseCode(&ptr, end); // Note: modifies ptr
569 if (http_result == HTTPCode_200)
570 {
571 LogInfo("handleLNTPortMappingResponse: got a valid response, sending reply to natTraversalHandlePortMapReply(internal %d external %d retries %d)",
572 mDNSVal16(natInfo->IntPort), RequestedPortNum(natInfo), tcpInfo->retries);
573
574 // Make sure to compute extport *before* we zero tcpInfo->retries
575 extport = mDNSOpaque16fromIntVal(RequestedPortNum(natInfo));
576 tcpInfo->retries = 0;
577 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, mStatus_NoError, extport, NATMAP_DEFAULT_LEASE);
578 }
579 else if (http_result == HTTPCode_500)
67c8f8a1 580 {
32bb7e43 581 while (ptr && ptr != end)
67c8f8a1 582 {
32bb7e43 583 if (((*ptr == 'c' || *ptr == 'C') && end - ptr >= 8 && strncasecmp((char*)ptr, "Conflict", 8) == 0) || (*ptr == '>' && end - ptr >= 15 && strncasecmp((char*)ptr, ">718</errorCode", 15) == 0))
67c8f8a1 584 {
32bb7e43
A
585 if (tcpInfo->retries < 100)
586 {
587 tcpInfo->retries++; SendPortMapRequest(tcpInfo->m, natInfo);
588 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict", "Retry %d", tcpInfo->retries);
589 }
590 else
67c8f8a1 591 {
32bb7e43
A
592 LogMsg("handleLNTPortMappingResponse too many conflict retries %d %d", mDNSVal16(natInfo->IntPort), mDNSVal16(natInfo->RequestedPort));
593 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "Conflict - too many retries", "Retries: %d", tcpInfo->retries);
594 natTraversalHandlePortMapReply(m, natInfo, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0);
67c8f8a1 595 }
32bb7e43 596 return;
7f0064bd 597 }
32bb7e43 598 ptr++;
7f0064bd 599 }
7f0064bd 600 }
32bb7e43
A
601 else if (http_result == HTTPCode_Bad) LogMsg("handleLNTPortMappingResponse got data that was not a valid HTTP response");
602 else if (http_result == HTTPCode_Other) LogMsg("handleLNTPortMappingResponse got unexpected response code");
603 else if (http_result == HTTPCode_404) LNT_ClearState(m);
604 if (http_result != HTTPCode_200 && http_result != HTTPCode_500)
605 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", "noop", "HTTP Result", "HTTP code: %d", http_result);
7f0064bd
A
606 }
607
67c8f8a1
A
608mDNSlocal void DisposeInfoFromUnmapList(mDNS *m, tcpLNTInfo *tcpInfo)
609 {
610 tcpLNTInfo **ptr = &m->tcpInfoUnmapList;
611 while (*ptr && *ptr != tcpInfo) ptr = &(*ptr)->next;
612 if (*ptr) { *ptr = (*ptr)->next; mDNSPlatformMemFree(tcpInfo); } // If we found it, cut it from our list and free the memory
7f0064bd
A
613 }
614
67c8f8a1
A
615mDNSlocal void tcpConnectionCallback(TCPSocket *sock, void *context, mDNSBool ConnectionEstablished, mStatus err)
616 {
617 mStatus status = mStatus_NoError;
618 tcpLNTInfo *tcpInfo = (tcpLNTInfo *)context;
619 mDNSBool closed = mDNSfalse;
620 long n = 0;
621 long nsent = 0;
7f0064bd 622
32bb7e43 623 if (tcpInfo == mDNSNULL) { LogInfo("tcpConnectionCallback: no tcpInfo context"); status = mStatus_Invalid; goto exit; }
7f0064bd 624
67c8f8a1
A
625 // The handlers below expect to be called with the lock held
626 mDNS_Lock(tcpInfo->m);
627
32bb7e43 628 if (err) { LogInfo("tcpConnectionCallback: received error"); goto exit; }
7f0064bd 629
67c8f8a1
A
630 if (ConnectionEstablished) // connection is established - send the message
631 {
32bb7e43 632 LogInfo("tcpConnectionCallback: connection established, sending message");
67c8f8a1
A
633 nsent = mDNSPlatformWriteTCP(sock, (char *)tcpInfo->Request, tcpInfo->requestLen);
634 if (nsent != (long)tcpInfo->requestLen) { LogMsg("tcpConnectionCallback: error writing"); status = mStatus_UnknownErr; goto exit; }
7f0064bd 635 }
67c8f8a1
A
636 else
637 {
638 n = mDNSPlatformReadTCP(sock, (char *)tcpInfo->Reply + tcpInfo->nread, tcpInfo->replyLen - tcpInfo->nread, &closed);
32bb7e43 639 LogInfo("tcpConnectionCallback: mDNSPlatformReadTCP read %d bytes", n);
7f0064bd 640
32bb7e43
A
641 if (n < 0) { LogInfo("tcpConnectionCallback - read returned %d", n); status = mStatus_ConnFailed; goto exit; }
642 else if (closed) { LogInfo("tcpConnectionCallback: socket closed by remote end %d", tcpInfo->nread); status = mStatus_ConnFailed; goto exit; }
7f0064bd 643
67c8f8a1 644 tcpInfo->nread += n;
32bb7e43 645 LogInfo("tcpConnectionCallback tcpInfo->nread %d", tcpInfo->nread);
67c8f8a1
A
646 if (tcpInfo->nread > LNT_MAXBUFSIZE)
647 {
32bb7e43 648 LogInfo("result truncated...");
67c8f8a1 649 tcpInfo->nread = LNT_MAXBUFSIZE;
7f0064bd 650 }
7f0064bd 651
67c8f8a1
A
652 switch (tcpInfo->op)
653 {
654 case LNTDiscoveryOp: handleLNTDeviceDescriptionResponse (tcpInfo); break;
655 case LNTExternalAddrOp: handleLNTGetExternalAddressResponse(tcpInfo); break;
656 case LNTPortMapOp: handleLNTPortMappingResponse (tcpInfo); break;
657 case LNTPortMapDeleteOp: status = mStatus_ConfigChanged; break;
658 default: LogMsg("tcpConnectionCallback: bad tcp operation! %d", tcpInfo->op); status = mStatus_Invalid; break;
7f0064bd 659 }
7f0064bd 660 }
67c8f8a1
A
661exit:
662 if (err || status)
7f0064bd 663 {
32bb7e43
A
664 mDNS *m = tcpInfo->m;
665 switch (tcpInfo->op)
666 {
667 case LNTDiscoveryOp: if (m->UPnPSOAPAddressString == mDNSNULL)
668 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP Address", "");
669 if (m->UPnPSOAPURL == mDNSNULL)
670 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "failure", "SOAP path", "");
671 if (m->UPnPSOAPAddressString && m->UPnPSOAPURL)
672 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.DeviceDescription", "success", "success", "");
673 break;
674 case LNTExternalAddrOp: mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.AddressRequest", mDNSIPv4AddressIsZero(m->ExternalAddress) ? "failure" : "success", mDNSIPv4AddressIsZero(m->ExternalAddress) ? "failure" : "success", "");
675 break;
676 case LNTPortMapOp: if (tcpInfo->parentNATInfo)
677 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.PortMapRequest", (tcpInfo->parentNATInfo->Result) ? "failure" : "success",
678 (tcpInfo->parentNATInfo->Result) ? "failure" : "success", "Result: %d", tcpInfo->parentNATInfo->Result);
679 break;
680 case LNTPortMapDeleteOp: break;
681 default: break;
682 }
683
67c8f8a1
A
684 mDNSPlatformTCPCloseConnection(tcpInfo->sock);
685 tcpInfo->sock = mDNSNULL;
686 if (tcpInfo->Request) { mDNSPlatformMemFree(tcpInfo->Request); tcpInfo->Request = mDNSNULL; }
687 if (tcpInfo->Reply ) { mDNSPlatformMemFree(tcpInfo->Reply); tcpInfo->Reply = mDNSNULL; }
7f0064bd
A
688 }
689
67c8f8a1 690 if (tcpInfo) mDNS_Unlock(tcpInfo->m);
7f0064bd 691
67c8f8a1 692 if (status == mStatus_ConfigChanged) DisposeInfoFromUnmapList(tcpInfo->m, tcpInfo);
7f0064bd 693 }
7f0064bd 694
67c8f8a1 695mDNSlocal mStatus MakeTCPConnection(mDNS *const m, tcpLNTInfo *info, const mDNSAddr *const Addr, const mDNSIPPort Port, LNTOp_t op)
7f0064bd 696 {
67c8f8a1
A
697 mStatus err = mStatus_NoError;
698 mDNSIPPort srcport = zeroIPPort;
699
700 if (mDNSIPv4AddressIsZero(Addr->ip.v4) || mDNSIPPortIsZero(Port))
701 { LogMsg("LNT MakeTCPConnection: bad address/port %#a:%d", Addr, mDNSVal16(Port)); return(mStatus_Invalid); }
702 info->m = m;
703 info->Address = *Addr;
704 info->Port = Port;
705 info->op = op;
706 info->nread = 0;
707 info->replyLen = LNT_MAXBUFSIZE;
708 if (info->Reply != mDNSNULL) mDNSPlatformMemZero(info->Reply, LNT_MAXBUFSIZE); // reuse previously allocated buffer
32bb7e43 709 else if ((info->Reply = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate reply buffer"); return (mStatus_NoMemoryErr); }
67c8f8a1 710
32bb7e43 711 if (info->sock) { LogInfo("MakeTCPConnection: closing previous open connection"); mDNSPlatformTCPCloseConnection(info->sock); info->sock = mDNSNULL; }
67c8f8a1
A
712 info->sock = mDNSPlatformTCPSocket(m, kTCPSocketFlags_Zero, &srcport);
713 if (!info->sock) { LogMsg("LNT MakeTCPConnection: unable to create TCP socket"); mDNSPlatformMemFree(info->Reply); info->Reply = mDNSNULL; return(mStatus_NoMemoryErr); }
32bb7e43 714 LogInfo("MakeTCPConnection: connecting to %#a:%d", &info->Address, mDNSVal16(info->Port));
67c8f8a1
A
715 err = mDNSPlatformTCPConnect(info->sock, Addr, Port, 0, tcpConnectionCallback, info);
716
717 if (err == mStatus_ConnPending) err = mStatus_NoError;
718 else if (err == mStatus_ConnEstablished)
7f0064bd 719 {
67c8f8a1
A
720 mDNS_DropLockBeforeCallback();
721 tcpConnectionCallback(info->sock, info, mDNStrue, mStatus_NoError);
722 mDNS_ReclaimLockAfterCallback();
723 err = mStatus_NoError;
7f0064bd 724 }
67c8f8a1 725 else
7f0064bd 726 {
96f69b28 727 // Don't need to log this in customer builds -- it happens quite often during sleep, wake, configuration changes, etc.
32bb7e43 728 LogInfo("LNT MakeTCPConnection: connection failed");
67c8f8a1
A
729 mDNSPlatformTCPCloseConnection(info->sock); // Dispose the socket we created with mDNSPlatformTCPSocket() above
730 info->sock = mDNSNULL;
731 mDNSPlatformMemFree(info->Reply);
732 info->Reply = mDNSNULL;
7f0064bd 733 }
67c8f8a1 734 return(err);
7f0064bd
A
735 }
736
67c8f8a1 737mDNSlocal unsigned int AddSOAPArguments(char *buf, unsigned int maxlen, int numArgs, Property *a)
7f0064bd 738 {
67c8f8a1
A
739 static const char f1[] = "<%s>%s</%s>";
740 static const char f2[] = "<%s xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"%s\">%s</%s>";
741 int i, len = 0;
742 *buf = 0;
743 for (i = 0; i < numArgs; i++)
7f0064bd 744 {
67c8f8a1
A
745 if (a[i].type) len += mDNS_snprintf(buf + len, maxlen - len, f2, a[i].name, a[i].type, a[i].value, a[i].name);
746 else len += mDNS_snprintf(buf + len, maxlen - len, f1, a[i].name, a[i].value, a[i].name);
7f0064bd 747 }
67c8f8a1
A
748 return(len);
749 }
7f0064bd 750
67c8f8a1
A
751mDNSlocal mStatus SendSOAPMsgControlAction(mDNS *m, tcpLNTInfo *info, char *Action, int numArgs, Property *Arguments, LNTOp_t op)
752 {
753 // SOAP message header format -
754 // - control URL
755 // - action (string)
756 // - router's host/port ("host:port")
757 // - content-length
758 static const char header[] =
759 "POST %s HTTP/1.1\r\n"
760 "Content-Type: text/xml; charset=\"utf-8\"\r\n"
51e746cf 761 "SOAPAction: \"urn:schemas-upnp-org:service:WAN%sConnection:1#%s\"\r\n"
67c8f8a1
A
762 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows 9x)\r\n"
763 "Host: %s\r\n"
764 "Content-Length: %d\r\n"
765 "Connection: close\r\n"
766 "Pragma: no-cache\r\n"
767 "\r\n"
768 "%s\r\n";
769
770 static const char body1[] =
771 "<?xml version=\"1.0\"?>\r\n"
772 "<SOAP-ENV:Envelope"
773 " xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\""
774 " SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
775 "<SOAP-ENV:Body>"
51e746cf 776 "<m:%s xmlns:m=\"urn:schemas-upnp-org:service:WAN%sConnection:1\">";
67c8f8a1
A
777
778 static const char body2[] =
779 "</m:%s>"
780 "</SOAP-ENV:Body>"
781 "</SOAP-ENV:Envelope>\r\n";
782
783 mStatus err;
96f69b28 784 char *body = (char *)&m->omsg; // Typically requires 1110-1122 bytes; m->omsg is 8952 bytes, which is plenty
67c8f8a1
A
785 int bodyLen;
786
32bb7e43
A
787 if (mDNSIPPortIsZero(m->UPnPSOAPPort) || m->UPnPSOAPURL == mDNSNULL || m->UPnPSOAPAddressString == mDNSNULL) // if no SOAP URL or address exists get out here
788 { LogInfo("SendSOAPMsgControlAction: no SOAP port, URL or address string"); return mStatus_Invalid; }
67c8f8a1
A
789
790 // Create body
51e746cf 791 bodyLen = mDNS_snprintf (body, sizeof(m->omsg), body1, Action, m->UPnPWANPPPConnection ? "PPP" : "IP");
96f69b28
A
792 bodyLen += AddSOAPArguments(body + bodyLen, sizeof(m->omsg) - bodyLen, numArgs, Arguments);
793 bodyLen += mDNS_snprintf (body + bodyLen, sizeof(m->omsg) - bodyLen, body2, Action);
67c8f8a1
A
794
795 // Create info->Request; the header needs to contain the bodyLen in the "Content-Length" field
796 if (!info->Request) info->Request = mDNSPlatformMemAllocate(LNT_MAXBUFSIZE);
797 if (!info->Request) { LogMsg("SendSOAPMsgControlAction: Can't allocate info->Request"); return mStatus_NoMemoryErr; }
51e746cf 798 info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, header, m->UPnPSOAPURL, m->UPnPWANPPPConnection ? "PPP" : "IP", Action, m->UPnPSOAPAddressString, bodyLen, body);
67c8f8a1
A
799
800 err = MakeTCPConnection(m, info, &m->Router, m->UPnPSOAPPort, op);
801 if (err) { mDNSPlatformMemFree(info->Request); info->Request = mDNSNULL; }
802 return err;
803 }
804
805// Build port mapping request with new port (up to max) and send it
806mDNSlocal mStatus SendPortMapRequest(mDNS *m, NATTraversalInfo *n)
807 {
808 char externalPort[6];
809 char internalPort[6];
810 char localIPAddrString[30];
811 char publicPortString[40];
812 Property propArgs[8];
813 mDNSu16 ReqPortNum = RequestedPortNum(n);
814 NATTraversalInfo *n2 = m->NATTraversals;
815
816 // Scan our m->NATTraversals list to make sure the external port we're requesting is locally unique.
817 // UPnP gateways will report conflicts if different devices request the same external port, but if two
818 // clients on the same device request the same external port the second one just stomps over the first.
819 // One way this can happen is like this:
820 // 1. Client A binds local port 80
821 // 2. Client A requests external port 80 -> internal port 80
822 // 3. UPnP NAT gateway refuses external port 80 (some other client already has it)
823 // 4. Client A tries again, and successfully gets external port 80 -> internal port 81
824 // 5. Client B on same machine tries to bind local port 80, and fails
825 // 6. Client B tries again, and successfully binds local port 81
826 // 7. Client B now requests external port 81 -> internal port 81
827 // 8. UPnP NAT gateway allows this, stomping over Client A's existing mapping
828
829 while (n2)
7f0064bd 830 {
67c8f8a1 831 if (n2 == n || RequestedPortNum(n2) != ReqPortNum) n2=n2->next;
7f0064bd 832 else
7f0064bd 833 {
67c8f8a1 834 if (n->tcpInfo.retries < 100)
7f0064bd 835 {
67c8f8a1
A
836 n->tcpInfo.retries++;
837 ReqPortNum = RequestedPortNum(n); // Pick a new port number
838 n2 = m->NATTraversals; // And re-scan the list looking for conflicts
7f0064bd 839 }
67c8f8a1 840 else
7f0064bd 841 {
32bb7e43 842 natTraversalHandlePortMapReply(m, n, m->UPnPInterfaceID, NATErr_Res, zeroIPPort, 0);
67c8f8a1 843 return mStatus_NoError;
7f0064bd
A
844 }
845 }
7f0064bd 846 }
7f0064bd 847
67c8f8a1
A
848 // create strings to use in the message
849 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", ReqPortNum);
850 mDNS_snprintf(internalPort, sizeof(internalPort), "%u", mDNSVal16(n->IntPort));
851 mDNS_snprintf(publicPortString, sizeof(publicPortString), "iC%u", ReqPortNum);
852 mDNS_snprintf(localIPAddrString, sizeof(localIPAddrString), "%u.%u.%u.%u",
853 m->AdvertisedV4.ip.v4.b[0], m->AdvertisedV4.ip.v4.b[1], m->AdvertisedV4.ip.v4.b[2], m->AdvertisedV4.ip.v4.b[3]);
854
855 // build the message
856 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
857 propArgs[0].name = "NewRemoteHost";
858 propArgs[0].type = "string";
859 propArgs[0].value = "";
860 propArgs[1].name = "NewExternalPort";
861 propArgs[1].type = "ui2";
862 propArgs[1].value = externalPort;
863 propArgs[2].name = "NewProtocol";
864 propArgs[2].type = "string";
865 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
866 propArgs[3].name = "NewInternalPort";
867 propArgs[3].type = "ui2";
868 propArgs[3].value = internalPort;
869 propArgs[4].name = "NewInternalClient";
870 propArgs[4].type = "string";
871 propArgs[4].value = localIPAddrString;
872 propArgs[5].name = "NewEnabled";
873 propArgs[5].type = "boolean";
874 propArgs[5].value = "1";
875 propArgs[6].name = "NewPortMappingDescription";
876 propArgs[6].type = "string";
877 propArgs[6].value = publicPortString;
878 propArgs[7].name = "NewLeaseDuration";
879 propArgs[7].type = "ui4";
880 propArgs[7].value = "0";
881
32bb7e43 882 LogInfo("SendPortMapRequest: internal %u external %u", mDNSVal16(n->IntPort), ReqPortNum);
67c8f8a1
A
883 return SendSOAPMsgControlAction(m, &n->tcpInfo, "AddPortMapping", 8, propArgs, LNTPortMapOp);
884 }
885
886mDNSexport mStatus LNT_MapPort(mDNS *m, NATTraversalInfo *n)
887 {
32bb7e43 888 LogInfo("LNT_MapPort");
67c8f8a1
A
889 if (n->tcpInfo.sock) return(mStatus_NoError); // If we already have a connection up don't make another request for the same thing
890 n->tcpInfo.parentNATInfo = n;
891 n->tcpInfo.retries = 0;
892 return SendPortMapRequest(m, n);
7f0064bd
A
893 }
894
67c8f8a1
A
895mDNSexport mStatus LNT_UnmapPort(mDNS *m, NATTraversalInfo *n)
896 {
897 char externalPort[10];
898 Property propArgs[3];
899 tcpLNTInfo *info;
900 tcpLNTInfo **infoPtr = &m->tcpInfoUnmapList;
901 mStatus err;
902
903 // If no NAT gateway to talk to, no need to do all this work for nothing
32bb7e43 904 if (mDNSIPPortIsZero(m->UPnPSOAPPort) || !m->UPnPSOAPURL || !m->UPnPSOAPAddressString) return mStatus_NoError;
67c8f8a1
A
905
906 mDNS_snprintf(externalPort, sizeof(externalPort), "%u", mDNSVal16(mDNSIPPortIsZero(n->RequestedPort) ? n->IntPort : n->RequestedPort));
907
908 mDNSPlatformMemZero(propArgs, sizeof(propArgs));
909 propArgs[0].name = "NewRemoteHost";
910 propArgs[0].type = "string";
911 propArgs[0].value = "";
912 propArgs[1].name = "NewExternalPort";
913 propArgs[1].type = "ui2";
914 propArgs[1].value = externalPort;
915 propArgs[2].name = "NewProtocol";
916 propArgs[2].type = "string";
917 propArgs[2].value = (n->Protocol == NATOp_MapUDP) ? "UDP" : "TCP";
918
919 n->tcpInfo.parentNATInfo = n;
920
921 // clean up previous port mapping requests and allocations
32bb7e43 922 if (n->tcpInfo.sock) LogInfo("LNT_UnmapPort: closing previous open connection");
67c8f8a1
A
923 if (n->tcpInfo.sock ) { mDNSPlatformTCPCloseConnection(n->tcpInfo.sock); n->tcpInfo.sock = mDNSNULL; }
924 if (n->tcpInfo.Request) { mDNSPlatformMemFree(n->tcpInfo.Request); n->tcpInfo.Request = mDNSNULL; }
925 if (n->tcpInfo.Reply ) { mDNSPlatformMemFree(n->tcpInfo.Reply); n->tcpInfo.Reply = mDNSNULL; }
926
927 // make a copy of the tcpInfo that we can clean up later (the one passed in will be destroyed by the client as soon as this returns)
928 if ((info = mDNSPlatformMemAllocate(sizeof(tcpLNTInfo))) == mDNSNULL)
32bb7e43 929 { LogInfo("LNT_UnmapPort: can't allocate tcpInfo"); return(mStatus_NoMemoryErr); }
67c8f8a1
A
930 *info = n->tcpInfo;
931
932 while (*infoPtr) infoPtr = &(*infoPtr)->next; // find the end of the list
933 *infoPtr = info; // append
7f0064bd 934
67c8f8a1
A
935 err = SendSOAPMsgControlAction(m, info, "DeletePortMapping", 3, propArgs, LNTPortMapDeleteOp);
936 if (err) DisposeInfoFromUnmapList(m, info);
937 return err;
7f0064bd
A
938 }
939
67c8f8a1
A
940mDNSexport mStatus LNT_GetExternalAddress(mDNS *m)
941 {
942 return SendSOAPMsgControlAction(m, &m->tcpAddrInfo, "GetExternalIPAddress", 0, mDNSNULL, LNTExternalAddrOp);
7f0064bd
A
943 }
944
67c8f8a1
A
945mDNSlocal mStatus GetDeviceDescription(mDNS *m, tcpLNTInfo *info)
946 {
947 // Device description format -
948 // - device description URL
949 // - host/port
950 static const char szSSDPMsgDescribeDeviceFMT[] =
951 "GET %s HTTP/1.1\r\n"
952 "Accept: text/xml, application/xml\r\n"
953 "User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1)\r\n"
954 "Host: %s\r\n"
955 "Connection: close\r\n"
956 "\r\n";
957
32bb7e43
A
958 if (!mDNSIPPortIsZero(m->UPnPSOAPPort)) return mStatus_NoError; // already have the info we need
959
960 if (m->UPnPRouterURL == mDNSNULL || m->UPnPRouterAddressString == mDNSNULL) { LogInfo("GetDeviceDescription: no router URL or address string!"); return (mStatus_Invalid); }
67c8f8a1
A
961
962 // build message
963 if (info->Request != mDNSNULL) mDNSPlatformMemZero(info->Request, LNT_MAXBUFSIZE); // reuse previously allocated buffer
32bb7e43 964 else if ((info->Request = (mDNSs8 *) mDNSPlatformMemAllocate(LNT_MAXBUFSIZE)) == mDNSNULL) { LogInfo("can't allocate send buffer for discovery"); return (mStatus_NoMemoryErr); }
67c8f8a1 965 info->requestLen = mDNS_snprintf((char *)info->Request, LNT_MAXBUFSIZE, szSSDPMsgDescribeDeviceFMT, m->UPnPRouterURL, m->UPnPRouterAddressString);
32bb7e43 966 LogInfo("Describe Device: [%s]", info->Request);
67c8f8a1
A
967 return MakeTCPConnection(m, info, &m->Router, m->UPnPRouterPort, LNTDiscoveryOp);
968 }
969
970// This function parses the response to our SSDP discovery message. Basically, we look to make sure this is a response
51e746cf 971// referencing a service we care about (WANIPConnection or WANPPPConnection), then look for the "Location:" header and copy the addressing and
67c8f8a1
A
972// URL info we need.
973mDNSexport void LNT_ConfigureRouterInfo(mDNS *m, const mDNSInterfaceID InterfaceID, mDNSu8 *data, mDNSu16 len)
974 {
975 char *ptr = (char *)data;
976 char *end = (char *)data + len;
32bb7e43
A
977 char *stop = ptr;
978
979 if (!mDNSIPPortIsZero(m->UPnPRouterPort)) return; // already have the info we need
7f0064bd 980
67c8f8a1
A
981 // The formatting of the HTTP header is not always the same when it comes to the placement of
982 // the service and location strings, so we just look for each of them from the beginning for every response
983
984 // figure out if this is a message from a service we care about
985 while (ptr && ptr != end)
7f0064bd 986 {
51e746cf 987 if (*ptr == 'W' && (strncasecmp(ptr, "WANIPConnection:1", 17) == 0)) break;
67c8f8a1 988 ptr++;
7f0064bd 989 }
51e746cf
A
990 if (ptr == end)
991 {
992 ptr = (char *)data;
993 while (ptr && ptr != end)
994 {
995 if (*ptr == 'W' && (strncasecmp(ptr, "WANPPPConnection:1", 18) == 0)) break;
996 ptr++;
997 }
998 }
67c8f8a1 999 if (ptr == mDNSNULL || ptr == end) return; // not a message we care about
7f0064bd 1000
67c8f8a1
A
1001 // find "Location:", starting from the beginning
1002 ptr = (char *)data;
1003 while (ptr && ptr != end)
7f0064bd 1004 {
1a175162 1005 if ((*ptr & 0xDF) == 'L' && (strncasecmp(ptr, "Location:", 9) == 0)) break; // find the first 'L'; is this Location? if not, keep looking
67c8f8a1 1006 ptr++;
7f0064bd 1007 }
32bb7e43
A
1008 if (ptr == mDNSNULL || ptr == end)
1009 {
1010 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Location", "");
1011 return; // not a message we care about
1012 }
1013 ptr += 9; //Skip over 'Location:'
1014 while (*ptr == ' ' && ptr < end) ptr++; // skip over spaces
1015 if (ptr >= end) return;
67c8f8a1 1016
32bb7e43
A
1017 // find the end of the line
1018 for (stop = ptr; stop != end; stop++) { if (*stop == '\r') { end = stop; break; } }
1019
1020 // fill in default port
1021 m->UPnPRouterPort = mDNSOpaque16fromIntVal(80);
1022
1023 // free string pointers and set to NULL
1024 if (m->UPnPRouterAddressString != mDNSNULL)
7f0064bd 1025 {
32bb7e43
A
1026 mDNSPlatformMemFree(m->UPnPRouterAddressString);
1027 m->UPnPRouterAddressString = mDNSNULL;
1028 }
1029 if (m->UPnPRouterURL != mDNSNULL)
1030 {
1031 mDNSPlatformMemFree(m->UPnPRouterURL);
1032 m->UPnPRouterURL = mDNSNULL;
1033 }
67c8f8a1 1034
32bb7e43
A
1035 // the Router URL should look something like "/dyndev/uuid:0013-108c-4b3f0000f3dc"
1036 if (ParseHttpUrl(ptr, end, &m->UPnPRouterAddressString, &m->UPnPRouterPort, &m->UPnPRouterURL) != mStatus_NoError)
1037 {
1038 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Parse URL", "");
1039 return;
7f0064bd 1040 }
7f0064bd 1041
32bb7e43
A
1042 m->UPnPInterfaceID = InterfaceID;
1043
1044 if (m->UPnPRouterAddressString == mDNSNULL)
7f0064bd 1045 {
32bb7e43
A
1046 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router address", "");
1047 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterAddressString is NULL");
7f0064bd 1048 }
32bb7e43
A
1049 else LogInfo("LNT_ConfigureRouterInfo: Router address string [%s]", m->UPnPRouterAddressString);
1050
1051 if (m->UPnPRouterURL == mDNSNULL)
1052 {
1053 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "failure", "Router path", "");
1054 LogMsg("LNT_ConfigureRouterInfo: UPnPRouterURL is NULL");
1055 }
1056 else LogInfo("LNT_ConfigureRouterInfo: Router URL [%s]", m->UPnPRouterURL);
1057
1058 LogInfo("LNT_ConfigureRouterInfo: Router port %d", mDNSVal16(m->UPnPRouterPort));
1059 LogInfo("LNT_ConfigureRouterInfo: Router interface %d", m->UPnPInterfaceID);
7f0064bd 1060
030b743d 1061 // Don't need the SSDP socket anymore
32bb7e43 1062 if (m->SSDPSocket) { debugf("LNT_ConfigureRouterInfo destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
7f0064bd 1063
32bb7e43 1064 mDNSASLLog((uuid_t *)&m->asl_uuid, "natt.legacy.ssdp", "success", "success", "");
67c8f8a1
A
1065 // now send message to get the device description
1066 GetDeviceDescription(m, &m->tcpDeviceInfo);
7f0064bd
A
1067 }
1068
67c8f8a1 1069mDNSexport void LNT_SendDiscoveryMsg(mDNS *m)
7f0064bd 1070 {
51e746cf 1071 static const char msg[] =
67c8f8a1
A
1072 "M-SEARCH * HTTP/1.1\r\n"
1073 "Host:239.255.255.250:1900\r\n"
51e746cf 1074 "ST:urn:schemas-upnp-org:service:WAN%sConnection:1\r\n"
67c8f8a1
A
1075 "Man:\"ssdp:discover\"\r\n"
1076 "MX:3\r\n\r\n";
9f29194f
A
1077 static const mDNSAddr multicastDest = { mDNSAddrType_IPv4, { { { 239, 255, 255, 250 } } } };
1078
51e746cf
A
1079 mDNSu8* buf = (mDNSu8*)&m->omsg; //m->omsg is 8952 bytes, which is plenty
1080 unsigned int bufLen;
32bb7e43
A
1081
1082 if (!mDNSIPPortIsZero(m->UPnPRouterPort))
1083 {
1084 if (m->SSDPSocket) { debugf("LNT_SendDiscoveryMsg destroying SSDPSocket %p", &m->SSDPSocket); mDNSPlatformUDPClose(m->SSDPSocket); m->SSDPSocket = mDNSNULL; }
1085 if (mDNSIPPortIsZero(m->UPnPSOAPPort) && !m->tcpDeviceInfo.sock) GetDeviceDescription(m, &m->tcpDeviceInfo);
1086 return;
1087 }
51e746cf
A
1088
1089 // Always query for WANIPConnection in the first SSDP packet
1090 if (m->retryIntervalGetAddr <= NATMAP_INIT_RETRY) m->SSDPWANPPPConnection = mDNSfalse;
1091
1092 // Create message
1093 bufLen = mDNS_snprintf((char*)buf, sizeof(m->omsg), msg, m->SSDPWANPPPConnection ? "PPP" : "IP");
7f0064bd 1094
32bb7e43 1095 debugf("LNT_SendDiscoveryMsg Router %.4a Current External Address %.4a", &m->Router.ip.v4, &m->ExternalAddress);
7f0064bd 1096
32bb7e43 1097 if (!mDNSIPv4AddressIsZero(m->Router.ip.v4))
030b743d 1098 {
32bb7e43 1099 if (!m->SSDPSocket) { m->SSDPSocket = mDNSPlatformUDPSocket(m, zeroIPPort); debugf("LNT_SendDiscoveryMsg created SSDPSocket %p", &m->SSDPSocket); }
51e746cf
A
1100 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &m->Router, SSDPPort);
1101 mDNSPlatformSendUDP(m, buf, buf + bufLen, 0, m->SSDPSocket, &multicastDest, SSDPPort);
030b743d 1102 }
9f29194f 1103
51e746cf 1104 m->SSDPWANPPPConnection = !m->SSDPWANPPPConnection;
7f0064bd
A
1105 }
1106
32bb7e43
A
1107mDNSexport void LNT_ClearState(mDNS *const m)
1108 {
1109 if (m->tcpAddrInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpAddrInfo.sock); m->tcpAddrInfo.sock = mDNSNULL; }
1110 if (m->tcpDeviceInfo.sock) { mDNSPlatformTCPCloseConnection(m->tcpDeviceInfo.sock); m->tcpDeviceInfo.sock = mDNSNULL; }
1111 m->UPnPSOAPPort = m->UPnPRouterPort = zeroIPPort; // Reset UPnP ports
1112 }
1113
67c8f8a1 1114#endif /* _LEGACY_NAT_TRAVERSAL_ */