2 * Copyright (c) 2003 Ben Harris
5 * Permission is hereby granted, free of charge, to any person
6 * obtaining a copy of this software and associated documentation
7 * files (the "Software"), to deal in the Software without
8 * restriction, including without limitation the rights to use,
9 * copy, modify, merge, publish, distribute, sublicense, and/or
10 * sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following
14 * The above copyright notice and this permission notice shall be
15 * included in all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
21 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
22 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
23 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * mtcpnet.c - MacTCP interface
35 #include <MixedMode.h>
36 #include <Resources.h>
45 * The following structures are documented as being in
46 * <AddressXlation.h>, but that isn't shipped with Universal
47 * Interfaces, and it's easier to define them here than to require
48 * people to download yet another SDK.
51 static OSErr
OpenResolver(char *);
52 static OSErr
CloseResolver(void);
65 #define NUM_ALT_ADDRS 4
67 typedef struct hostInfo
{
70 unsigned long addr
[NUM_ALT_ADDRS
];
73 typedef CALLBACK_API(void, ResultProcPtr
)(struct hostInfo
*, char *);
74 typedef STACK_UPP_TYPE(ResultProcPtr
) ResultUPP
;
75 enum { uppResultProcInfo
= kPascalStackBased
76 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct hostInfo
*)))
77 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(char *)))
79 #define NewResultUPP(userRoutine) \
80 (ResultUPP)NewRoutineDescriptor((ProcPtr)(userRoutine), \
82 GetCurrentArchitecture())
83 #define DisposeResultUPP(userUPP) DisposeRoutineDescriptor(userUPP)
85 static OSErr
StrToAddr(char *, struct hostInfo
*, ResultUPP
*, char *);
87 typedef CALLBACK_API_C(OSErr
, OpenResolverProcPtr
)(UInt32
, char *);
88 typedef STACK_UPP_TYPE(OpenResolverProcPtr
) OpenResolverUPP
;
89 enum { uppOpenResolverProcInfo
= kCStackBased
90 | RESULT_SIZE(SIZE_CODE(sizeof(OSErr
)))
91 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(UInt32
)))
92 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(char*)))
94 #define InvokeOpenResolverUPP(selector, fileName, userUPP) \
95 CALL_TWO_PARAMETER_UPP((userUPP), uppOpenResolverProcInfo, \
96 (selector), (fileName))
98 typedef CALLBACK_API_C(OSErr
, CloseResolverProcPtr
)(UInt32
);
99 typedef STACK_UPP_TYPE(CloseResolverProcPtr
) CloseResolverUPP
;
100 enum { uppCloseResolverProcInfo
= kCStackBased
101 | RESULT_SIZE(SIZE_CODE(sizeof(OSErr
)))
102 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(UInt32
)))
104 #define InvokeCloseResolverUPP(selector, userUPP) \
105 CALL_ONE_PARAMETER_UPP((userUPP), uppCloseResolverProcInfo, (selector))
107 typedef CALLBACK_API_C(OSErr
, StrToAddrProcPtr
)(UInt32
, char *,
108 struct hostInfo
*, ResultUPP
,
110 typedef STACK_UPP_TYPE(StrToAddrProcPtr
) StrToAddrUPP
;
111 enum { uppStrToAddrProcInfo
= kCStackBased
112 | RESULT_SIZE(SIZE_CODE(sizeof(OSErr
)))
113 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(UInt32
)))
114 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(char *)))
115 | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(struct hostInfo
*)))
116 | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(ResultUPP
)))
117 | STACK_ROUTINE_PARAMETER(5, SIZE_CODE(sizeof(char *)))
119 #define InvokeStrToAddrUPP(selector, hostName, hostInfoPtr, ResultProc, \
120 userDataPtr, userUPP) \
121 CALL_FIVE_PARAMETER_UPP((userUPP), uppStrToAddrProcInfo, (selector),\
122 (hostName), (hostInfoPtr), (ResultProc), \
124 #define StrToAddr(hostName, hostInfoPtr, ResultProc, userDataPtr) \
125 InvokeStrToAddrUPP(STRTOADDR, hostName, hostInfoPtr, ResultProc, \
126 userDataPtr, (StrToAddrUPP)*mactcp.dnr_handle)
128 typedef CALLBACK_API_C(OSErr
, AddrToStrProcPtr
)(UInt32
, unsigned long, char *);
129 typedef STACK_UPP_TYPE(AddrToStrProcPtr
) AddrToStrUPP
;
130 enum { uppAddrToStrProcInfo
= kCStackBased
131 | RESULT_SIZE(SIZE_CODE(sizeof(OSErr
)))
132 | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(unsigned long)))
133 | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(char *)))
135 #define InvokeAddrToStrUPP(selector, addr, addrStr, userUPP) \
136 CALL_THREE_PARAMETER_UPP((userUPP), uppAddrToStrProcInfo, (selector),\
138 #define AddrToStr(addr, addrStr) \
139 InvokeAddrToStrUPP(ADDRTOSTR, addr, addrStr, \
140 (AddrToStrUPP)*mactcp.dnr_handle)
142 /* End of AddressXlation.h bits */
145 struct socket_function_table
*fn
;
146 /* the above variable absolutely *must* be the first in this structure */
151 bufchain output_data
;
154 int frozen
; /* this causes readability notifications to be ignored */
155 int frozen_readable
; /* this means we missed at least one readability
156 * notification while we were frozen */
157 int localhost_only
; /* for listening sockets */
160 int oobpending
; /* is there OOB data available to read? */
162 int pending_error
; /* in case send() returns error */
167 * We used to typedef struct Socket_tag *Socket.
169 * Since we have made the networking abstraction slightly more
170 * abstract, Socket no longer means a tcp socket (it could mean
171 * an ssl socket). So now we must use Actual_Socket when we know
172 * we are talking about a tcp socket.
174 typedef struct Socket_tag
*Actual_Socket
;
176 struct SockAddr_tag
{
178 struct hostInfo hostinfo
;
182 /* Global variables */
189 static pascal void mactcp_lookupdone(struct hostInfo
*hi
, char *cookie
);
190 static Plug
mactcp_plug(Socket
, Plug
);
191 static void mactcp_flush(Socket
);
192 static void mactcp_close(Socket
);
193 static int mactcp_write(Socket
, char *, int);
194 static int mactcp_write_oob(Socket
, char *, int);
195 static void mactcp_set_private_ptr(Socket
, void *);
196 static void *mactcp_get_private_ptr(Socket
);
197 static char *mactcp_socket_error(Socket
);
198 static void mactcp_set_frozen(Socket
, int);
202 * This should be called once before any TCP connection is opened.
205 OSErr
mactcp_init(void)
210 * IM:Devices describes a convoluted way of finding a spare unit
211 * number to open a driver on before calling OpenDriver. Happily,
212 * the MacTCP INIT ensures that .IPP is already open (and hence
213 * has a valid unit number already) so we don't need to go through
214 * all that. (MacTCP Programmer's Guide p6)
216 err
= OpenDriver("\p.IPP", &mactcp
.refnum
);
217 if (err
!= noErr
) return err
;
218 err
= OpenResolver(NULL
);
219 if (err
!= noErr
) return err
;
221 mactcp
.initialised
= TRUE
;
225 void mactcp_shutdown(void)
229 mactcp
.initialised
= FALSE
;
232 static ResultUPP mactcp_lookupdone_upp
;
234 SockAddr
sk_namelookup(char *host
, char **canonicalname
)
236 SockAddr ret
= smalloc(sizeof(struct SockAddr_tag
));
238 volatile int done
= FALSE
;
241 fprintf(stderr
, "Resolving %s...\n", host
);
242 /* Clear the structure. */
243 memset(ret
, 0, sizeof(struct SockAddr_tag
));
244 if (mactcp_lookupdone_upp
== NULL
)
245 mactcp_lookupdone_upp
= NewResultUPP(&mactcp_lookupdone
);
246 err
= StrToAddr(host
, &ret
->hostinfo
, mactcp_lookupdone_upp
,
249 * PuTTY expects DNS lookups to be synchronous (see bug
250 * "async-dns"), so we pretend they are.
252 if (err
== cacheFault
)
255 ret
->resolved
= TRUE
;
257 if (ret
->hostinfo
.rtnCode
== noErr
)
258 realhost
= ret
->hostinfo
.cname
;
261 *canonicalname
= smalloc(1+strlen(realhost
));
262 strcpy(*canonicalname
, realhost
);
263 fprintf(stderr
, "canonical name = %s\n", realhost
);
267 static pascal void mactcp_lookupdone(struct hostInfo
*hi
, char *cookie
)
269 volatile int *donep
= (int *)cookie
;
274 SockAddr
sk_nonamelookup(char *host
)
276 SockAddr ret
= smalloc(sizeof(struct SockAddr_tag
));
278 ret
->resolved
= FALSE
;
279 ret
->hostinfo
.rtnCode
= noErr
;
280 ret
->hostname
[0] = '\0';
281 strncat(ret
->hostname
, host
, lenof(ret
->hostname
) - 1);
285 void sk_getaddr(SockAddr addr
, char *buf
, int buflen
)
290 /* XXX only return first address */
291 err
= AddrToStr(addr
->hostinfo
.addr
[0], mybuf
);
294 strncat(buf
, mybuf
, buflen
- 1);
297 /* I think "local" here really means "loopback" */
299 int sk_hostname_is_local(char *name
)
302 return !strcmp(name
, "localhost");
305 int sk_address_is_local(SockAddr addr
)
310 for (i
= 0; i
< NUM_ALT_ADDRS
; i
++)
311 if (addr
->hostinfo
.addr
[i
] & 0xff000000 == 0x7f000000)
316 int sk_addrtype(SockAddr addr
)
320 return ADDRTYPE_IPV4
;
321 return ADDRTYPE_NAME
;
324 void sk_addrcopy(SockAddr addr
, char *buf
)
327 /* XXX only return first address */
328 memcpy(buf
, &addr
->hostinfo
.addr
[0], 4);
331 void sk_addr_free(SockAddr addr
)
337 static Plug
mactcp_plug(Socket sock
, Plug p
)
339 Actual_Socket s
= (Actual_Socket
) sock
;
347 static void mactcp_flush(Socket s
)
350 fatalbox("sk_tcp_flush");
353 Socket
sk_new(SockAddr addr
, int port
, int privport
, int oobinline
,
354 int nodelay
, Plug plug
)
356 static struct socket_function_table fn_table
= {
362 mactcp_set_private_ptr
,
363 mactcp_get_private_ptr
,
373 fprintf(stderr
, "Opening socket, port = %d\n", port
);
375 * Create Socket structure.
377 ret
= smalloc(sizeof(struct Socket_tag
));
382 bufchain_init(&ret
->output_data
);
383 ret
->connected
= 0; /* to start with */
384 ret
->writable
= 0; /* to start with */
385 ret
->sending_oob
= 0;
387 ret
->frozen_readable
= 0;
388 ret
->localhost_only
= 0; /* unused, but best init anyway */
389 ret
->pending_error
= 0;
390 ret
->oobpending
= FALSE
;
393 dstaddr
= addr
->hostinfo
.addr
[0]; /* XXX should try all of them */
395 * Create a TCP stream.
397 * MacTCP requires us to provide it with some buffer memory. Page
398 * 31 of the Programmer's Guide says it should be a minimum of
399 * 4*MTU+1024. Page 36 says a minimum of 4096 bytes. Assume
400 * they're both correct.
402 assert(addr
->resolved
);
403 upb
.ioCRefNum
= mactcp
.refnum
;
404 upb
.csCode
= UDPMaxMTUSize
;
405 upb
.csParam
.mtu
.remoteHost
= dstaddr
;
406 upb
.csParam
.mtu
.userDataPtr
= NULL
;
407 ret
->err
= PBControlSync((ParmBlkPtr
)&upb
);
408 fprintf(stderr
, "getting mtu, err = %d\n", ret
->err
);
409 if (ret
->err
!= noErr
) return (Socket
)ret
;
410 fprintf(stderr
, "Got MTU = %d\n", upb
.csParam
.mtu
.mtuSize
);
412 buflen
= upb
.csParam
.mtu
.mtuSize
* 4 + 1024;
413 if (buflen
< 4096) buflen
= 4096;
414 pb
.ioCRefNum
= mactcp
.refnum
;
415 pb
.csCode
= TCPCreate
;
416 pb
.csParam
.create
.rcvBuff
= smalloc(buflen
);
417 pb
.csParam
.create
.rcvBuffLen
= buflen
;
418 pb
.csParam
.create
.notifyProc
= NULL
;
419 pb
.csParam
.create
.userDataPtr
= (Ptr
)ret
;
420 ret
->err
= PBControlSync((ParmBlkPtr
)&pb
);
421 if (ret
->err
!= noErr
) return (Socket
)ret
;
422 ret
->s
= pb
.tcpStream
;
423 fprintf(stderr
, "stream opened\n");
426 * Open the connection.
428 pb
.ioCRefNum
= mactcp
.refnum
;
429 pb
.csCode
= TCPActiveOpen
;
430 pb
.tcpStream
= ret
->s
;
431 pb
.csParam
.open
.validityFlags
= 0;
432 pb
.csParam
.open
.remoteHost
= dstaddr
;
433 pb
.csParam
.open
.remotePort
= port
;
434 pb
.csParam
.open
.localPort
= privport ?
1023 : 0;
435 pb
.csParam
.open
.dontFrag
= FALSE
;
436 pb
.csParam
.open
.timeToLive
= 0;
437 pb
.csParam
.open
.security
= 0;
438 pb
.csParam
.open
.optionCnt
= 0;
439 pb
.csParam
.open
.userDataPtr
= (Ptr
)ret
;
441 ret
->err
= PBControlSync((ParmBlkPtr
)&pb
);
442 if (!privport
|| ret
->err
!= duplicateSocket
)
444 pb
.csParam
.open
.localPort
--;
445 if (pb
.csParam
.open
.localPort
== 0)
449 if (ret
->err
!= noErr
) return (Socket
)ret
;
451 ret
->connected
= TRUE
;
452 ret
->writable
= TRUE
;
454 fprintf(stderr
, "Socket connected\n");
458 static void mactcp_close(Socket sock
)
460 Actual_Socket s
= (Actual_Socket
)sock
;
464 * TCPClose is equivalent to shutdown(fd, SHUT_WR), and hence
465 * leaves the Rx side open, while TCPAbort seems rather vicious,
466 * throwing away Tx data that haven't been ACKed yet. We do both
469 pb
.ioCRefNum
= mactcp
.refnum
;
470 pb
.csCode
= TCPClose
;
472 pb
.csParam
.close
.validityFlags
= 0;
473 pb
.csParam
.close
.userDataPtr
= (Ptr
)s
;
474 s
->err
= PBControlSync((ParmBlkPtr
)&pb
);
475 /* Not much we can do about an error anyway. */
477 pb
.ioCRefNum
= mactcp
.refnum
;
478 pb
.csCode
= TCPAbort
;
480 pb
.csParam
.abort
.userDataPtr
= (Ptr
)s
;
481 s
->err
= PBControlSync((ParmBlkPtr
)&pb
);
482 /* Even less we can do about an error here. */
484 pb
.ioCRefNum
= mactcp
.refnum
;
485 pb
.csCode
= TCPRelease
;
487 pb
.csParam
.create
.userDataPtr
= (Ptr
)s
;
488 s
->err
= PBControlSync((ParmBlkPtr
)&pb
);
490 sfree(pb
.csParam
.create
.rcvBuff
);
494 static int mactcp_write(Socket sock
, char *buf
, int len
)
496 Actual_Socket s
= (Actual_Socket
) sock
;
500 fprintf(stderr
, "Write data, %d bytes\n", len
);
506 pb
.ioCRefNum
= mactcp
.refnum
;
509 pb
.csParam
.send
.validityFlags
= 0;
510 pb
.csParam
.send
.pushFlag
= TRUE
; /* XXX we want it to return. */
511 pb
.csParam
.send
.urgentFlag
= 0;
512 pb
.csParam
.send
.wdsPtr
= (Ptr
)wds
;
513 pb
.csParam
.send
.userDataPtr
= (Ptr
)s
;
514 s
->err
= PBControlSync((ParmBlkPtr
)&pb
);
518 static int mactcp_write_oob(Socket sock
, char *buf
, int len
)
521 fatalbox("mactcp_write_oob");
525 * Each socket abstraction contains a `void *' private field in
526 * which the client can keep state.
528 static void mactcp_set_private_ptr(Socket sock
, void *ptr
)
530 Actual_Socket s
= (Actual_Socket
) sock
;
531 s
->private_ptr
= ptr
;
534 static void *mactcp_get_private_ptr(Socket sock
)
536 Actual_Socket s
= (Actual_Socket
) sock
;
537 return s
->private_ptr
;
541 * Special error values are returned from sk_namelookup and sk_new
542 * if there's a problem. These functions extract an error message,
543 * or return NULL if there's no problem.
545 char *sk_addr_error(SockAddr addr
)
549 switch (addr
->hostinfo
.rtnCode
) {
553 return "Name syntax error";
555 return "No name server found";
557 return "Domain name does not exist";
559 return "No answer from domain name server";
561 return "Domain name server returned an error";
563 return "Out of memory";
565 sprintf(buf
, "Unknown DNR error %d", addr
->hostinfo
.rtnCode
);
570 static char *mactcp_socket_error(Socket sock
)
573 Actual_Socket s
= (Actual_Socket
) sock
;
578 case insufficientResources
:
579 return "Insufficient resources to open TCP stream";
580 case duplicateSocket
:
581 return "Duplicate socket";
583 return "Connection failed while opening";
585 sprintf(buf
, "Unknown MacTCP error %d", s
->err
);
590 static void mactcp_set_frozen(Socket sock
, int is_frozen
)
593 fatalbox("mactcp_set_frozen");
597 * Bits below here would usually be in dnr.c, shipped with the MacTCP
598 * SDK, but its convenient not to require that, and since we assume
599 * System 7 we can actually simplify things a lot.
602 static OSErr
OpenResolver(char *hosts_file
)
612 if (mactcp
.dnr_handle
!= NULL
)
615 err
= FindFolder(kOnSystemDisk
, kControlPanelFolderType
, FALSE
, &vrefnum
,
617 if (err
!= noErr
) return err
;
620 * Might be better to use PBCatSearch here, but it's not always
623 pb
.fileParam
.ioCompletion
= NULL
;
624 pb
.fileParam
.ioNamePtr
= filename
;
625 pb
.fileParam
.ioVRefNum
= vrefnum
;
626 pb
.fileParam
.ioFDirIndex
= 1;
627 pb
.fileParam
.ioDirID
= dirid
;
630 while (PBHGetFInfoSync(&pb
) == noErr
) {
631 if (pb
.fileParam
.ioFlFndrInfo
.fdType
== 'cdev' &&
632 pb
.fileParam
.ioFlFndrInfo
.fdCreator
== 'ztcp') {
633 fd
= HOpenResFile(vrefnum
, dirid
, filename
, fsRdPerm
);
634 if (fd
== -1) continue;
635 dnr_handle
= Get1IndResource('dnrp', 1);
636 if (dnr_handle
!= NULL
)
641 pb
.fileParam
.ioDirID
= dirid
;
642 pb
.fileParam
.ioFDirIndex
++;
647 DetachResource(dnr_handle
);
653 err
= InvokeOpenResolverUPP(OPENRESOLVER
, hosts_file
,
654 (OpenResolverUPP
)*dnr_handle
);
657 DisposeHandle(dnr_handle
);
661 mactcp
.dnr_handle
= dnr_handle
;
665 OSErr
CloseResolver(void)
667 Handle dnr_handle
= mactcp
.dnr_handle
;
670 if (mactcp
.dnr_handle
== NULL
)
673 err
= InvokeCloseResolverUPP(CLOSERESOLVER
,
674 (CloseResolverUPP
)*mactcp
.dnr_handle
);
678 mactcp
.dnr_handle
= NULL
;
680 DisposeHandle(dnr_handle
);
686 * c-file-style: "simon"