Download
jcs
/subtext
/tcp.c
(View History)
jcs tcp: Don't bother manually zeroing open options | Latest amendment: 222 on 2022-07-24 |
1 | #include <stdio.h> |
2 | #include <string.h> |
3 | #include "tcp.h" |
4 | |
5 | #define RCV_BUFFER_SIZE 1024 |
6 | #define TCP_BUFFER_SIZE 8192 |
7 | #define OPEN_TIMEOUT 60 |
8 | |
9 | /* define to enable SOCKS5 code */ |
10 | #undef SOCKS |
11 | |
12 | short gIPPDriverRefNum; |
13 | |
14 | pascal void StrToAddrMarkDone(struct hostInfo *hi, char *data); |
15 | |
16 | unsigned long resolved_ip; |
17 | |
18 | OSErr |
19 | _TCPInit(void) |
20 | { |
21 | ParamBlockRec pb; |
22 | OSErr osErr; |
23 | |
24 | memset(&pb, 0, sizeof(pb)); |
25 | |
26 | gIPPDriverRefNum = -1; |
27 | |
28 | pb.ioParam.ioCompletion = 0; |
29 | pb.ioParam.ioNamePtr = "\p.IPP"; |
30 | pb.ioParam.ioPermssn = fsCurPerm; |
31 | |
32 | osErr = PBOpen(&pb, false); |
33 | if (noErr == osErr) |
34 | gIPPDriverRefNum = pb.ioParam.ioRefNum; |
35 | |
36 | return osErr; |
37 | } |
38 | |
39 | OSErr |
40 | _TCPGetOurIP(ip_addr *ip, long *netMask) |
41 | { |
42 | OSErr osErr; |
43 | GetAddrParamBlock pb; |
44 | |
45 | memset(&pb, 0, sizeof(pb)); |
46 | |
47 | pb.csCode = ipctlGetAddr; |
48 | pb.ioCRefNum = gIPPDriverRefNum; |
49 | pb.ioResult = 1; |
50 | |
51 | osErr = PBControl((ParmBlkPtr)&pb, true); |
52 | while (pb.ioResult > 0) |
53 | ; |
54 | |
55 | if (pb.ioResult != noErr) |
56 | return pb.ioResult; |
57 | |
58 | if (ip != NULL) |
59 | *ip = pb.ourAddress; |
60 | if (netMask != NULL) |
61 | *netMask = pb.ourNetMask; |
62 | |
63 | return osErr; |
64 | } |
65 | |
66 | OSErr |
67 | _TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen, |
68 | TCPNotifyProc aNotifyProc, Ptr userDataPtr, |
69 | TCPIOCompletionProc ioCompletion, Boolean async) |
70 | { |
71 | OSErr osErr; |
72 | |
73 | memset(pb, 0, sizeof(*pb)); |
74 | |
75 | pb->csCode = TCPCreate; |
76 | pb->ioCompletion = ioCompletion; |
77 | pb->ioCRefNum = gIPPDriverRefNum; |
78 | pb->ioResult = 1; |
79 | |
80 | pb->csParam.create.rcvBuff = rcvBufPtr; |
81 | pb->csParam.create.rcvBuffLen = rcvBufLen; |
82 | pb->csParam.create.notifyProc = aNotifyProc; |
83 | pb->csParam.create.userDataPtr = userDataPtr; |
84 | |
85 | osErr = PBControl((ParmBlkPtr)pb, async); |
86 | if (!async && (noErr == osErr)) { |
87 | *stream = pb->tcpStream; |
88 | } |
89 | |
90 | return osErr; |
91 | } |
92 | |
93 | /* listen for an incoming connection */ |
94 | OSErr |
95 | _TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP, |
96 | tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort, |
97 | Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) |
98 | { |
99 | OSErr osErr; |
100 | short index; |
101 | |
102 | memset(pb, 0, sizeof(*pb)); |
103 | |
104 | pb->csCode = TCPPassiveOpen; |
105 | pb->ioCompletion = ioCompletion; |
106 | pb->ioCRefNum = gIPPDriverRefNum; |
107 | pb->tcpStream = stream; |
108 | pb->ioResult = 1; |
109 | |
110 | pb->csParam.open.ulpTimeoutAction = 1; /* abort half-open connection */ |
111 | pb->csParam.open.ulpTimeoutValue = 5; /* after 5 seconds */ |
112 | pb->csParam.open.validityFlags = 0xC0; |
113 | pb->csParam.open.commandTimeoutValue = 0; |
114 | pb->csParam.open.remoteHost = 0; |
115 | pb->csParam.open.remotePort = 0; |
116 | pb->csParam.open.localHost = 0; |
117 | pb->csParam.open.localPort = *localPort; |
118 | pb->csParam.open.tosFlags = 0x1; /* low delay */ |
119 | pb->csParam.open.precedence = 0; |
120 | pb->csParam.open.dontFrag = 0; |
121 | pb->csParam.open.timeToLive = 0; |
122 | pb->csParam.open.security = 0; |
123 | pb->csParam.open.optionCnt = 0; |
124 | pb->csParam.open.userDataPtr = userData; |
125 | |
126 | osErr = PBControl((ParmBlkPtr) pb, async); |
127 | if (!async && (osErr == noErr)) { |
128 | if (remoteIP) |
129 | *remoteIP = pb->csParam.open.remoteHost; |
130 | if (remotePort) |
131 | *remotePort = pb->csParam.open.remotePort; |
132 | if (localIP) |
133 | *localIP = pb->csParam.open.localHost; |
134 | *localPort = pb->csParam.open.localPort; |
135 | } |
136 | |
137 | return osErr; |
138 | } |
139 | |
140 | /* make an outgoing connection */ |
141 | OSErr |
142 | _TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP, |
143 | tcp_port remotePort, ip_addr *localIP, tcp_port *localPort, |
144 | Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) |
145 | { |
146 | OSErr osErr; |
147 | short index; |
148 | |
149 | memset(pb, 0, sizeof(*pb)); |
150 | |
151 | pb->csCode = TCPActiveOpen; |
152 | pb->ioCompletion = ioCompletion; |
153 | pb->ioCRefNum = gIPPDriverRefNum; |
154 | pb->tcpStream = stream; |
155 | pb->ioResult = 1; |
156 | |
157 | pb->csParam.open.ulpTimeoutValue = 30; |
158 | pb->csParam.open.ulpTimeoutAction = 1; |
159 | pb->csParam.open.validityFlags = 0xC0; |
160 | #if 0 |
161 | /* not available with this csCode */ |
162 | pb->csParam.open.commandTimeoutValue = 30; |
163 | #endif |
164 | pb->csParam.open.remoteHost = remoteIP; |
165 | pb->csParam.open.remotePort = remotePort; |
166 | pb->csParam.open.localHost = 0; |
167 | pb->csParam.open.localPort = *localPort; |
168 | pb->csParam.open.tosFlags = 0; |
169 | pb->csParam.open.precedence = 0; |
170 | pb->csParam.open.dontFrag = 0; |
171 | pb->csParam.open.timeToLive = 0; |
172 | pb->csParam.open.security = 0; |
173 | pb->csParam.open.optionCnt = 0; |
174 | for (index = 0; index < sizeof(pb->csParam.open.options); ++index) |
175 | pb->csParam.open.options[index] = 0; |
176 | pb->csParam.open.userDataPtr = userData; |
177 | |
178 | osErr = PBControl((ParmBlkPtr) pb, async); |
179 | if (!async && (noErr == osErr)) { |
180 | *localIP = pb->csParam.open.localHost; |
181 | *localPort = pb->csParam.open.localPort; |
182 | } |
183 | |
184 | return osErr; |
185 | } |
186 | |
187 | OSErr |
188 | _TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, Ptr userData, |
189 | TCPIOCompletionProc ioCompletion, Boolean async) |
190 | { |
191 | memset(pb, 0, sizeof(*pb)); |
192 | |
193 | pb->csCode = TCPSend; |
194 | pb->ioCompletion = ioCompletion; |
195 | pb->ioCRefNum = gIPPDriverRefNum; |
196 | pb->tcpStream = stream; |
197 | pb->ioResult = 1; |
198 | |
199 | pb->csParam.send.ulpTimeoutValue = 30; |
200 | pb->csParam.send.ulpTimeoutAction = 1; |
201 | pb->csParam.send.validityFlags = 0xC0; |
202 | pb->csParam.send.pushFlag = 1; /* XXX */ |
203 | pb->csParam.send.urgentFlag = 0; |
204 | pb->csParam.send.wdsPtr = (Ptr)wdsPtr; |
205 | pb->csParam.send.sendFree = 0; |
206 | pb->csParam.send.sendLength = 0; |
207 | pb->csParam.send.userDataPtr = userData; |
208 | |
209 | return PBControl((ParmBlkPtr)pb, async); |
210 | } |
211 | |
212 | OSErr |
213 | _TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, |
214 | unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion, |
215 | Boolean async) |
216 | { |
217 | memset(pb, 0, sizeof(*pb)); |
218 | |
219 | pb->csCode = TCPNoCopyRcv; |
220 | pb->ioCompletion = ioCompletion; |
221 | pb->ioCRefNum = gIPPDriverRefNum; |
222 | pb->tcpStream = stream; |
223 | pb->ioResult = 1; |
224 | |
225 | pb->csParam.receive.commandTimeoutValue = 30; |
226 | pb->csParam.receive.urgentFlag = 0; |
227 | pb->csParam.receive.markFlag = 0; |
228 | pb->csParam.receive.rdsPtr = rdsPtr; |
229 | pb->csParam.receive.rdsLength = rdsLength; |
230 | pb->csParam.receive.userDataPtr = userData; |
231 | |
232 | return PBControl((ParmBlkPtr)pb, async); |
233 | } |
234 | |
235 | OSErr |
236 | _TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr, |
237 | unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion, |
238 | Boolean async) |
239 | { |
240 | OSErr osErr; |
241 | |
242 | memset(pb, 0, sizeof(*pb)); |
243 | |
244 | pb->csCode = TCPRcv; |
245 | pb->ioCompletion = ioCompletion; |
246 | pb->ioCRefNum = gIPPDriverRefNum; |
247 | pb->tcpStream = stream; |
248 | pb->ioResult = 1; |
249 | |
250 | pb->csParam.receive.commandTimeoutValue = 30; |
251 | pb->csParam.receive.urgentFlag = 0; |
252 | pb->csParam.receive.markFlag = 0; |
253 | pb->csParam.receive.rcvBuff = rcvBufPtr; |
254 | pb->csParam.receive.rcvBuffLen = *rcvBufLen; |
255 | pb->csParam.receive.userDataPtr = userData; |
256 | |
257 | osErr = PBControl((ParmBlkPtr)pb, async); |
258 | if (!async) |
259 | *rcvBufLen = pb->csParam.receive.rcvBuffLen; |
260 | |
261 | return osErr; |
262 | } |
263 | |
264 | OSErr |
265 | _TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData, |
266 | TCPIOCompletionProc ioCompletion, Boolean async) |
267 | { |
268 | memset(pb, 0, sizeof(*pb)); |
269 | |
270 | pb->csCode = TCPRcvBfrReturn; |
271 | pb->ioCompletion = ioCompletion; |
272 | pb->ioCRefNum = gIPPDriverRefNum; |
273 | pb->tcpStream = stream; |
274 | pb->ioResult = 1; |
275 | |
276 | pb->csParam.receive.rdsPtr = rdsPtr; |
277 | pb->csParam.receive.userDataPtr = userData; |
278 | |
279 | return PBControl((ParmBlkPtr)pb, async); |
280 | } |
281 | |
282 | OSErr |
283 | _TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData, |
284 | TCPIOCompletionProc ioCompletion, Boolean async) |
285 | { |
286 | memset(pb, 0, sizeof(*pb)); |
287 | |
288 | pb->csCode = TCPClose; |
289 | pb->ioCompletion = ioCompletion; |
290 | pb->ioCRefNum = gIPPDriverRefNum; |
291 | pb->tcpStream = stream; |
292 | pb->ioResult = 1; |
293 | |
294 | pb->csParam.close.ulpTimeoutValue = 30; |
295 | pb->csParam.close.ulpTimeoutAction = 1; |
296 | pb->csParam.close.validityFlags = 0xC0; |
297 | pb->csParam.close.userDataPtr = userData; |
298 | |
299 | return PBControl((ParmBlkPtr)pb, async); |
300 | } |
301 | |
302 | OSErr |
303 | _TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData, |
304 | TCPIOCompletionProc ioCompletion, Boolean async) |
305 | { |
306 | memset(pb, 0, sizeof(*pb)); |
307 | |
308 | pb->csCode = TCPAbort; |
309 | pb->ioCompletion = ioCompletion; |
310 | pb->ioCRefNum = gIPPDriverRefNum; |
311 | pb->tcpStream = stream; |
312 | pb->ioResult = 1; |
313 | |
314 | pb->csParam.abort.userDataPtr = userData; |
315 | |
316 | return PBControl((ParmBlkPtr)pb, async); |
317 | } |
318 | |
319 | OSErr |
320 | _TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status, |
321 | Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async) |
322 | { |
323 | OSErr osErr; |
324 | |
325 | memset(pb, 0, sizeof(*pb)); |
326 | |
327 | pb->csCode = TCPStatus; |
328 | pb->ioCompletion = ioCompletion; |
329 | pb->ioCRefNum = gIPPDriverRefNum; |
330 | pb->tcpStream = stream; |
331 | pb->csParam.status.userDataPtr = userData; |
332 | pb->ioResult = 1; |
333 | |
334 | osErr = PBControl((ParmBlkPtr)pb, async); |
335 | if (!async && (noErr == osErr)) { |
336 | *status = pb->csParam.status; |
337 | } |
338 | |
339 | return osErr; |
340 | } |
341 | |
342 | OSErr |
343 | _TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData, |
344 | TCPIOCompletionProc ioCompletion, Boolean async) |
345 | { |
346 | OSErr osErr; |
347 | |
348 | memset(pb, 0, sizeof(*pb)); |
349 | |
350 | pb->csCode = TCPRelease; |
351 | pb->ioCompletion = ioCompletion; |
352 | pb->ioCRefNum = gIPPDriverRefNum; |
353 | pb->tcpStream = stream; |
354 | pb->ioResult = 1; |
355 | |
356 | pb->csParam.status.userDataPtr = userData; |
357 | |
358 | osErr = PBControl((ParmBlkPtr)pb, async); |
359 | |
360 | return osErr; |
361 | } |
362 | |
363 | OSErr |
364 | _UDPMaxMTUSize(UDPiopb *pb, short *mtu) |
365 | { |
366 | OSErr osErr; |
367 | |
368 | memset(pb, 0, sizeof(*pb)); |
369 | |
370 | pb->csCode = UDPMaxMTUSize; |
371 | pb->ioCRefNum = gIPPDriverRefNum; |
372 | pb->ioResult = 1; |
373 | |
374 | pb->csParam.mtu.remoteHost = (ip_addr)0; |
375 | |
376 | osErr = PBControl((ParmBlkPtr)pb, false); |
377 | |
378 | if (osErr == noErr) |
379 | *mtu = pb->csParam.mtu.mtuSize; |
380 | |
381 | return osErr; |
382 | } |
383 | |
384 | OSErr |
385 | _UDPCreate(UDPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen, |
386 | UDPNotifyProc aNotifyProc, Ptr userDataPtr, |
387 | UDPIOCompletionProc ioCompletion, Boolean async) |
388 | { |
389 | OSErr osErr; |
390 | |
391 | memset(pb, 0, sizeof(*pb)); |
392 | |
393 | pb->csCode = UDPCreate; |
394 | pb->ioCompletion = ioCompletion; |
395 | pb->ioCRefNum = gIPPDriverRefNum; |
396 | pb->ioResult = 1; |
397 | |
398 | pb->csParam.create.rcvBuff = rcvBufPtr; |
399 | pb->csParam.create.rcvBuffLen = rcvBufLen; |
400 | pb->csParam.create.notifyProc = aNotifyProc; |
401 | pb->csParam.create.userDataPtr = userDataPtr; |
402 | |
403 | osErr = PBControl((ParmBlkPtr)pb, async); |
404 | if (!async && (noErr == osErr)) { |
405 | *stream = pb->udpStream; |
406 | } |
407 | |
408 | return osErr; |
409 | } |
410 | |
411 | OSErr |
412 | _UDPSend(UDPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, ip_addr remoteIP, |
413 | udp_port remotePort, Ptr userData, UDPIOCompletionProc ioCompletion, |
414 | Boolean async) |
415 | { |
416 | memset(pb, 0, sizeof(*pb)); |
417 | |
418 | pb->csCode = UDPWrite; |
419 | pb->ioCompletion = ioCompletion; |
420 | pb->ioCRefNum = gIPPDriverRefNum; |
421 | pb->udpStream = stream; |
422 | pb->ioResult = 1; |
423 | |
424 | pb->csParam.send.remoteHost = remoteIP; |
425 | pb->csParam.send.remotePort = remotePort; |
426 | pb->csParam.send.wdsPtr = (Ptr)wdsPtr; |
427 | pb->csParam.send.checkSum = 0; |
428 | pb->csParam.send.sendLength = 0; |
429 | pb->csParam.send.userDataPtr = userData; |
430 | |
431 | return PBControl((ParmBlkPtr)pb, async); |
432 | } |
433 | |
434 | OSErr |
435 | _UDPRelease(UDPiopb *pb, StreamPtr stream, Ptr userData, |
436 | UDPIOCompletionProc ioCompletion, Boolean async) |
437 | { |
438 | OSErr osErr; |
439 | |
440 | memset(pb, 0, sizeof(*pb)); |
441 | |
442 | pb->csCode = UDPRelease; |
443 | pb->ioCompletion = ioCompletion; |
444 | pb->ioCRefNum = gIPPDriverRefNum; |
445 | pb->udpStream = stream; |
446 | pb->ioResult = 1; |
447 | |
448 | //pb->csParam.status.userDataPtr = userData; |
449 | |
450 | osErr = PBControl((ParmBlkPtr)pb, async); |
451 | |
452 | return osErr; |
453 | } |
454 | |
455 | |
456 | /* convenience functions */ |
457 | |
458 | pascal void |
459 | StrToAddrMarkDone(struct hostInfo *hi, char *data) |
460 | { |
461 | volatile int *done = (int *)data; |
462 | *done = 1; |
463 | } |
464 | |
465 | unsigned long |
466 | ip2long(char *ip) |
467 | { |
468 | unsigned long address = 0; |
469 | short dotcount = 0, i; |
470 | unsigned short b = 0; |
471 | |
472 | for (i = 0; ip[i] != 0; i++) { |
473 | if (ip[i] == '.') { |
474 | if (++dotcount > 3) |
475 | return (0); |
476 | address <<= 8; |
477 | address |= b; |
478 | b = 0; |
479 | } else if (ip[i] >= '0' && ip[i] <= '9') { |
480 | b *= 10; |
481 | b += (ip[i] - '0'); |
482 | if (b > 255) |
483 | return (0); |
484 | } else |
485 | return (0); |
486 | } |
487 | |
488 | if (dotcount != 3) |
489 | return (0); |
490 | address <<= 8; |
491 | address |= b; |
492 | return address; |
493 | } |
494 | |
495 | void |
496 | long2ip(unsigned long num, char *ip) |
497 | { |
498 | unsigned char *tmp = (unsigned char *)# |
499 | sprintf(ip, "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]); |
500 | } |
501 | |
502 | #ifdef SOCKS |
503 | #define SOCKS_VERSION_SOCKS5 0x5 |
504 | #define SOCKS_METHOD_AUTH_NONE 0x0 |
505 | #define SOCKS_REQUEST_CONNECT 0x1 |
506 | #define SOCKS_REQUEST_ATYP_DOMAINNAME 0x3 |
507 | #define SOCKS_REPLY_SUCCESS 0x0 |
508 | |
509 | OSErr |
510 | SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip, |
511 | tcp_port socks_port, char *remote_host, tcp_port remote_port, |
512 | ip_addr *local_ip, tcp_port *local_port, Ptr user_data, |
513 | TCPIOCompletionProc io_completion, Boolean async) |
514 | { |
515 | OSErr err; |
516 | TCPStatusPB status_pb; |
517 | wdsEntry wds[2]; |
518 | char data[255] = { 0 }; |
519 | unsigned short len, remote_host_len; |
520 | |
521 | remote_host_len = strlen(remote_host); |
522 | if (remote_host_len + 7 > sizeof(data)) |
523 | return -1; |
524 | |
525 | err = _TCPActiveOpen(pb, stream, socks_ip, socks_port, local_ip, |
526 | local_port, user_data, io_completion, async); |
527 | if (err != noErr) |
528 | return err; |
529 | |
530 | data[0] = SOCKS_VERSION_SOCKS5; |
531 | data[1] = 1; /* nmethods */ |
532 | data[2] = SOCKS_METHOD_AUTH_NONE; |
533 | |
534 | memset(&wds, 0, sizeof(wds)); |
535 | wds[0].ptr = (Ptr)&data; |
536 | wds[0].length = 3; |
537 | |
538 | err = _TCPSend(pb, stream, wds, nil, nil, false); |
539 | if (err) |
540 | goto fail; |
541 | |
542 | for (;;) { |
543 | err = _TCPStatus(pb, stream, &status_pb, nil, nil, false); |
544 | if (err != noErr) |
545 | goto fail; |
546 | |
547 | if (status_pb.amtUnreadData >= 2) |
548 | break; |
549 | } |
550 | |
551 | len = 2; |
552 | err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false); |
553 | if (err != noErr) |
554 | goto fail; |
555 | |
556 | if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_METHOD_AUTH_NONE) |
557 | goto fail; |
558 | |
559 | len = 0; |
560 | data[len++] = SOCKS_VERSION_SOCKS5; |
561 | data[len++] = SOCKS_REQUEST_CONNECT; |
562 | data[len++] = 0; /* reserved */ |
563 | data[len++] = SOCKS_REQUEST_ATYP_DOMAINNAME; |
564 | data[len++] = remote_host_len; |
565 | memcpy(data + len, remote_host, remote_host_len); |
566 | len += remote_host_len; |
567 | data[len++] = (remote_port >> 8); |
568 | data[len++] = (remote_port & 0xff); |
569 | |
570 | memset(&wds, 0, sizeof(wds)); |
571 | wds[0].ptr = (Ptr)&data; |
572 | wds[0].length = len; |
573 | |
574 | err = _TCPSend(pb, stream, wds, nil, nil, false); |
575 | if (err) |
576 | goto fail; |
577 | |
578 | for (;;) { |
579 | err = _TCPStatus(pb, stream, &status_pb, nil, nil, false); |
580 | if (err != noErr) |
581 | goto fail; |
582 | |
583 | if (status_pb.amtUnreadData >= 7) |
584 | break; |
585 | } |
586 | |
587 | len = status_pb.amtUnreadData; |
588 | if (len > sizeof(data)) |
589 | len = sizeof(data); |
590 | err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false); |
591 | if (err != noErr) |
592 | goto fail; |
593 | |
594 | if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_REPLY_SUCCESS) |
595 | goto fail; |
596 | |
597 | return noErr; |
598 | |
599 | fail: |
600 | _TCPClose(pb, stream, nil, nil, false); |
601 | return err; |
602 | } |
603 | #endif /* SOCKS */ |