1 /* ========================================================================
2 * Copyright 2008-2010 Mark Crispin
3 * ========================================================================
4 */
5
6 /*
7 * Program: Macintosh TCP/IP routines
8 *
9 * Author: Mark Crispin
10 *
11 * Date: 26 January 1992
12 * Last Edited: 3 April 2010
13 *
14 * Previous versions of this file were:
15 *
16 * Copyright 1988-2008 University of Washington
17 *
18 * Licensed under the Apache License, Version 2.0 (the "License");
19 * you may not use this file except in compliance with the License.
20 * You may obtain a copy of the License at
21 *
22 * http://www.apache.org/licenses/LICENSE-2.0
23 */
24
25
26 /* This is a totally new operating-system dependent module for the Macintosh,
27 * written using THINK C on my Mac PowerBook-100 in my free time.
28 * Unlike earlier efforts, this version requires no external TCP library. It
29 * also takes advantage of the Map panel in System 7 for the timezone.
30 */
31
32 static tcptimeout_t tmoh = NIL; /* TCP timeout handler routine */
33 static long ttmo_open = 75; /* TCP timeouts, in seconds */
34 static long ttmo_read = 0;
35 static long ttmo_write = 0;
36 static long ttmo_close = 0;
37
38 static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
39 long *contd);
40
41 /* TCP/IP manipulate parameters
42 * Accepts: function code
43 * function-dependent value
44 * Returns: function-dependent return value
45 */
46
tcp_parameters(long function,void * value)47 void *tcp_parameters (long function,void *value)
48 {
49 void *ret = NIL;
50 switch ((int) function) {
51 case SET_TIMEOUT:
52 tmoh = (tcptimeout_t) value;
53 case GET_TIMEOUT:
54 ret = (void *) tmoh;
55 break;
56 case SET_OPENTIMEOUT:
57 ttmo_open = (long) value;
58 case GET_OPENTIMEOUT:
59 ret = (void *) ttmo_open;
60 break;
61 case SET_READTIMEOUT:
62 ttmo_read = (long) value;
63 case GET_READTIMEOUT:
64 ret = (void *) ttmo_read;
65 break;
66 case SET_WRITETIMEOUT:
67 ttmo_write = (long) value;
68 case GET_WRITETIMEOUT:
69 ret = (void *) ttmo_write;
70 break;
71 case SET_CLOSETIMEOUT:
72 ttmo_close = (long) value;
73 case GET_CLOSETIMEOUT:
74 ret = (void *) ttmo_close;
75 break;
76 }
77 return ret;
78 }
79
80 /* TCP/IP open
81 * Accepts: host name
82 * contact service name
83 * contact port number
84 * Returns: TCP stream if success else NIL
85 */
86
tcp_open(char * host,char * service,unsigned long port)87 TCPSTREAM *tcp_open (char *host,char *service,unsigned long port)
88 {
89 TCPSTREAM *stream;
90 struct hostInfo hst;
91 struct TCPCreatePB *createpb;
92 struct TCPOpenPB *openpb;
93 char *s;
94 unsigned long i,j,k,l;
95 char tmp[MAILTMPLEN];
96 port &= 0xffff; /* erase flags */
97 /* init MacTCP */
98 if (!TCPdriver && OpenDriver (TCPDRIVER,&TCPdriver)) {
99 mm_log ("Can't init MacTCP",ERROR);
100 return NIL;
101 }
102 if (!resolveropen && OpenResolver (NIL)) {
103 mm_log ("Can't init domain resolver",ERROR);
104 return NIL;
105 }
106 resolveropen = T; /* note resolver open now */
107 /* domain literal? */
108 if (host[0] == '[' && host[strlen (host)-1] == ']') {
109 if (((i = strtoul (s = host+1,&s,10)) <= 255) && *s++ == '.' &&
110 ((j = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
111 ((k = strtoul (s,&s,10)) <= 255) && *s++ == '.' &&
112 ((l = strtoul (s,&s,10)) <= 255) && *s++ == ']' && !*s) {
113 hst.addr[0] = (i << 24) + (j << 16) + (k << 8) + l;
114 hst.addr[1] = 0; /* only one address to try! */
115 sprintf (hst.cname,"[%ld.%ld.%ld.%ld]",i,j,k,l);
116 }
117 else {
118 sprintf (tmp,"Bad format domain-literal: %.80s",host);
119 mm_log (tmp,ERROR);
120 return NIL;
121 }
122 }
123
124 else { /* look up host name */
125 if (!tcp_dns_upp) tcp_dns_upp = NewResultProc (tcp_dns_result);
126 if (StrToAddr (host,&hst,tcp_dns_upp,NIL)) {
127 while (hst.rtnCode == cacheFault && wait ());
128 /* kludge around MacTCP bug */
129 if (hst.rtnCode == outOfMemory) {
130 mm_log ("Re-initializing domain resolver",WARN);
131 CloseResolver (); /* bop it on the head and try again */
132 OpenResolver (NIL); /* note this will leak 12K */
133 StrToAddr (host,&hst,tcp_dns_upp,NIL);
134 while (hst.rtnCode == cacheFault && wait ());
135 }
136 if (hst.rtnCode) { /* still have error status? */
137 switch (hst.rtnCode) { /* analyze return */
138 case nameSyntaxErr:
139 s = "Syntax error in name";
140 break;
141 case noResultProc:
142 s = "No result procedure";
143 break;
144 case noNameServer:
145 s = "No name server found";
146 break;
147 case authNameErr:
148 s = "Host does not exist";
149 break;
150 case noAnsErr:
151 s = "No name servers responding";
152 break;
153 case dnrErr:
154 s = "Name server returned an error";
155 break;
156 case outOfMemory:
157 s = "Not enough memory to resolve name";
158 break;
159 case notOpenErr:
160 s = "Driver not open";
161 break;
162 default:
163 s = NIL;
164 break;
165 }
166 if (s) sprintf (tmp,"%s: %.80s",s,host);
167 else sprintf (tmp,"Unknown resolver error (%ld): %.80s",
168 hst.rtnCode,host);
169 mm_log (tmp,ERROR);
170 return NIL;
171 }
172 }
173 }
174
175 /* create local TCP/IP stream */
176 stream = (TCPSTREAM *) fs_get (sizeof (TCPSTREAM));
177 stream->ictr = 0; /* initialize input */
178 stream->pb.ioCRefNum = TCPdriver;
179 createpb = &stream->pb.csParam.create;
180 openpb = &stream->pb.csParam.open;
181 stream->pb.csCode = TCPCreate;/* create a TCP stream */
182 /* set up buffer for TCP */
183 createpb->rcvBuffLen = 4*BUFLEN;
184 createpb->rcvBuff = fs_get (createpb->rcvBuffLen);
185 createpb->notifyProc = NIL; /* no special notify procedure */
186 createpb->userDataPtr = NIL;
187 if (PBControlSync ((ParmBlkPtr) &stream->pb))
188 fatal ("Can't create TCP stream");
189 /* open TCP connection */
190 stream->pb.csCode = TCPActiveOpen;
191 openpb->ulpTimeoutValue = (int) ttmo_open;
192 openpb->ulpTimeoutAction = T;
193 openpb->validityFlags = timeoutValue|timeoutAction;
194 /* remote host (should try all) */
195 openpb->remoteHost = hst.addr[0];
196 openpb->remotePort = port; /* caller specified remote port */
197 openpb->localPort = 0; /* generate a local port */
198 openpb->tosFlags = 0; /* no special TOS */
199 openpb->precedence = 0; /* no special precedence */
200 openpb->dontFrag = 0; /* allow fragmentation */
201 openpb->timeToLive = 255; /* standards say 60, UNIX uses 255 */
202 openpb->security = 0; /* no special security */
203 openpb->optionCnt = 0; /* no IP options */
204 openpb->options[0] = 0;
205 openpb->userDataPtr = NIL; /* no special data pointer */
206 PBControlAsync ((ParmBlkPtr) &stream->pb);
207 while (stream->pb.ioResult == inProgress && wait ());
208 if (stream->pb.ioResult) { /* got back error status? */
209 sprintf (tmp,"Can't connect to %.80s,%ld",hst.cname,port);
210 mm_log (tmp,ERROR);
211 /* nuke the buffer */
212 stream->pb.csCode = TCPRelease;
213 createpb->userDataPtr = NIL;
214 if (PBControlSync ((ParmBlkPtr) &stream->pb)) fatal ("TCPRelease lossage");
215 /* free its buffer */
216 fs_give ((void **) &createpb->rcvBuff);
217 fs_give ((void **) &stream);/* and the local stream */
218 return NIL;
219 }
220
221 /* copy host names for later use */
222 stream->host = cpystr (hst.cname);
223 /* tie off trailing dot */
224 stream->host[strlen (stream->host) - 1] = '\0';
225 /* the open gave us our address */
226 i = (openpb->localHost >> 24) & 0xff;
227 j = (openpb->localHost >> 16) & 0xff;
228 k = (openpb->localHost >> 8) & 0xff;
229 l = openpb->localHost & 0xff;
230 sprintf (tmp,"[%ld.%ld.%ld.%ld]",i,j,k,l);
231 stream->localhost = cpystr (tmp);
232 if (!myLocalHost) myLocalHost = cpystr (tmp);
233 stream->port = port; /* copy port number */
234 return stream;
235 }
236
237
238 /* Called when have return from DNS
239 * Accepts: host info pointer
240 * user data pointer
241 */
242
243 ResultUPP tcp_dns_upp = NIL;
244
tcp_dns_result(struct hostInfo * hostInfoPtr,char * userDataPtr)245 pascal void tcp_dns_result (struct hostInfo *hostInfoPtr,char *userDataPtr)
246 {
247 /* dummy routine */
248 }
249
250 /* TCP/IP authenticated open
251 * Accepts: NETMBX specifier
252 * service name
253 * returned user name buffer
254 * Returns: TCP/IP stream if success else NIL
255 */
256
tcp_aopen(NETMBX * mb,char * service,char * usrbuf)257 TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf)
258 {
259 return NIL; /* no authenticated opens on Mac */
260 }
261
262 /* TCP receive line
263 * Accepts: TCP stream
264 * Returns: text line string or NIL if failure
265 */
266
tcp_getline(TCPSTREAM * stream)267 char *tcp_getline (TCPSTREAM *stream)
268 {
269 unsigned long n,contd;
270 char *ret = tcp_getline_work (stream,&n,&contd);
271 if (ret && contd) { /* got a line needing continuation? */
272 STRINGLIST *stl = mail_newstringlist ();
273 STRINGLIST *stc = stl;
274 do { /* collect additional lines */
275 stc->text.data = (unsigned char *) ret;
276 stc->text.size = n;
277 stc = stc->next = mail_newstringlist ();
278 ret = tcp_getline_work (stream,&n,&contd);
279 } while (ret && contd);
280 if (ret) { /* stash final part of line on list */
281 stc->text.data = (unsigned char *) ret;
282 stc->text.size = n;
283 /* determine how large a buffer we need */
284 for (n = 0, stc = stl; stc; stc = stc->next) n += stc->text.size;
285 ret = fs_get (n + 1); /* copy parts into buffer */
286 for (n = 0, stc = stl; stc; n += stc->text.size, stc = stc->next)
287 memcpy (ret + n,stc->text.data,stc->text.size);
288 ret[n] = '\0';
289 }
290 mail_free_stringlist (&stl);/* either way, done with list */
291 }
292 return ret;
293 }
294
295 /* TCP receive line or partial line
296 * Accepts: TCP stream
297 * pointer to return size
298 * pointer to return continuation flag
299 * Returns: text line string, size and continuation flag, or NIL if failure
300 */
301
tcp_getline_work(TCPSTREAM * stream,unsigned long * size,long * contd)302 static char *tcp_getline_work (TCPSTREAM *stream,unsigned long *size,
303 long *contd)
304 {
305 unsigned long n;
306 char *s,*ret,c,d;
307 *contd = NIL; /* assume no continuation */
308 /* make sure have data */
309 if (!tcp_getdata (stream)) return NIL;
310 for (s = stream->iptr, n = 0, c = '\0'; stream->ictr--; n++, c = d) {
311 d = *stream->iptr++; /* slurp another character */
312 if ((c == '\015') && (d == '\012')) {
313 ret = (char *) fs_get (n--);
314 memcpy (ret,s,*size = n); /* copy into a free storage string */
315 ret[n] = '\0'; /* tie off string with null */
316 return ret;
317 }
318 }
319 /* copy partial string from buffer */
320 memcpy ((ret = (char *) fs_get (n)),s,*size = n);
321 /* get more data from the net */
322 if (!tcp_getdata (stream)) fs_give ((void **) &ret);
323 /* special case of newline broken by buffer */
324 else if ((c == '\015') && (*stream->iptr == '\012')) {
325 stream->iptr++; /* eat the line feed */
326 stream->ictr--;
327 ret[*size = --n] = '\0'; /* tie off string with null */
328 }
329 else *contd = LONGT; /* continuation needed */
330 return ret;
331 }
332
333 /* TCP/IP receive buffer
334 * Accepts: TCP/IP stream
335 * size in bytes
336 * buffer to read into
337 * Returns: T if success, NIL otherwise
338 */
339
tcp_getbuffer(TCPSTREAM * stream,unsigned long size,char * buffer)340 long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer)
341 {
342 unsigned long n;
343 char *bufptr = buffer;
344 while (size > 0) { /* until request satisfied */
345 if (!tcp_getdata (stream)) return NIL;
346 n = min (size,stream->ictr);/* number of bytes to transfer */
347 /* do the copy */
348 memcpy (bufptr,stream->iptr,n);
349 bufptr += n; /* update pointer */
350 stream->iptr +=n;
351 size -= n; /* update # of bytes to do */
352 stream->ictr -=n;
353 }
354 bufptr[0] = '\0'; /* tie off string */
355 return T;
356 }
357
358
359 /* TCP/IP receive data
360 * Accepts: TCP/IP stream
361 * Returns: T if success, NIL otherwise
362 */
363
tcp_getdata(TCPSTREAM * stream)364 long tcp_getdata (TCPSTREAM *stream)
365 {
366 time_t t = time (0);
367 struct TCPReceivePB *receivepb = &stream->pb.csParam.receive;
368 struct TCPAbortPB *abortpb = &stream->pb.csParam.abort;
369 while (stream->ictr < 1) { /* if nothing in the buffer */
370 time_t tl = time (0);
371 stream->pb.csCode = TCPRcv; /* receive TCP data */
372 receivepb->commandTimeoutValue = (int) ttmo_read;
373 receivepb->rcvBuff = stream->ibuf;
374 receivepb->rcvBuffLen = BUFLEN;
375 receivepb->secondTimeStamp = 0;
376 receivepb->userDataPtr = NIL;
377 PBControlAsync ((ParmBlkPtr) &stream->pb);
378 while (stream->pb.ioResult == inProgress && wait ());
379 if (stream->pb.ioResult) { /* punt if got an error */
380 time_t tc = time (0);
381 if ((stream->pb.ioResult == commandTimeout) && tmoh &&
382 ((*tmoh) (tc - t,tc - tl))) continue;
383 /* nuke connection */
384 stream->pb.csCode = TCPAbort;
385 abortpb->userDataPtr = NIL;
386 PBControlSync ((ParmBlkPtr) &stream->pb);
387 return NIL;
388 }
389 stream->iptr = stream->ibuf;/* point at TCP buffer */
390 stream->ictr = receivepb->rcvBuffLen;
391 }
392 return T;
393 }
394
395 /* TCP/IP send string as record
396 * Accepts: TCP/IP stream
397 * string pointer
398 * Returns: T if success else NIL
399 */
400
tcp_soutr(TCPSTREAM * stream,char * string)401 long tcp_soutr (TCPSTREAM *stream,char *string)
402 {
403 return tcp_sout (stream,string,(unsigned long) strlen (string));
404 }
405
406
407 /* TCP/IP send string
408 * Accepts: TCP/IP stream
409 * string pointer
410 * byte count
411 * Returns: T if success else NIL
412 */
413
tcp_sout(TCPSTREAM * stream,char * string,unsigned long size)414 long tcp_sout (TCPSTREAM *stream,char *string,unsigned long size)
415 {
416 struct TCPSendPB *sendpb = &stream->pb.csParam.send;
417 struct TCPAbortPB *abortpb = &stream->pb.csParam.abort;
418 struct {
419 unsigned short length;
420 Ptr buffer;
421 unsigned short trailer;
422 } wds;
423 while (wds.length = (size > (unsigned long) 32768) ? 32768 : size) {
424 wds.buffer = string; /* buffer */
425 wds.trailer = 0; /* tie off buffer */
426 size -= wds.length; /* this many words will be output */
427 string += wds.length;
428 stream->pb.csCode = TCPSend;/* send TCP data */
429 sendpb->ulpTimeoutValue = (int) ttmo_write;
430 sendpb->ulpTimeoutAction = 0;
431 sendpb->validityFlags = timeoutValue|timeoutAction;
432 sendpb->pushFlag = T; /* send the data now */
433 sendpb->urgentFlag = NIL; /* non-urgent data */
434 sendpb->wdsPtr = (Ptr) &wds;
435 sendpb->userDataPtr = NIL;
436 PBControlAsync ((ParmBlkPtr) &stream->pb);
437 while (stream->pb.ioResult == inProgress && wait ());
438 if (stream->pb.ioResult) { /* punt if got an error */
439 /* nuke connection */
440 stream->pb.csCode =TCPAbort;
441 abortpb->userDataPtr = NIL;
442 PBControlSync ((ParmBlkPtr) &stream->pb);
443 return NIL;
444 }
445 }
446 return T; /* success */
447 }
448
449 /* TCP/IP close
450 * Accepts: TCP/IP stream
451 */
452
tcp_close(TCPSTREAM * stream)453 void tcp_close (TCPSTREAM *stream)
454 {
455 struct TCPClosePB *closepb = &stream->pb.csParam.close;
456 struct TCPCreatePB *createpb = &stream->pb.csParam.create;
457 stream->pb.csCode = TCPClose; /* close TCP stream */
458 closepb->ulpTimeoutValue = (int) ttmo_close;
459 closepb->ulpTimeoutAction = 0;
460 closepb->validityFlags = timeoutValue|timeoutAction;
461 closepb->userDataPtr = NIL;
462 PBControlAsync ((ParmBlkPtr) &stream->pb);
463 while (stream->pb.ioResult == inProgress && wait ());
464 stream->pb.csCode =TCPRelease;/* flush the buffers */
465 createpb->userDataPtr = NIL;
466 if (PBControlSync ((ParmBlkPtr) &stream->pb)) fatal ("TCPRelease lossage");
467 /* free its buffer */
468 fs_give ((void **) &createpb->rcvBuff);
469 /* flush host names */
470 fs_give ((void **) &stream->host);
471 fs_give ((void **) &stream->localhost);
472 fs_give ((void **) &stream); /* flush the stream */
473 }
474
475 /* TCP/IP return host for this stream
476 * Accepts: TCP/IP stream
477 * Returns: host name for this stream
478 */
479
tcp_host(TCPSTREAM * stream)480 char *tcp_host (TCPSTREAM *stream)
481 {
482 return stream->host; /* return host name */
483 }
484
485
486 /* TCP/IP return remote host for this stream
487 * Accepts: TCP/IP stream
488 * Returns: host name for this stream
489 */
490
tcp_remotehost(TCPSTREAM * stream)491 char *tcp_remotehost (TCPSTREAM *stream)
492 {
493 return stream->host; /* return host name */
494 }
495
496
497 /* TCP/IP return port for this stream
498 * Accepts: TCP/IP stream
499 * Returns: port number for this stream
500 */
501
tcp_port(TCPSTREAM * stream)502 unsigned long tcp_port (TCPSTREAM *stream)
503 {
504 return stream->port; /* return port number */
505 }
506
507
508 /* TCP/IP return local host for this stream
509 * Accepts: TCP/IP stream
510 * Returns: local host name for this stream
511 */
512
tcp_localhost(TCPSTREAM * stream)513 char *tcp_localhost (TCPSTREAM *stream)
514 {
515 return stream->localhost; /* return local host name */
516 }
517
518 /* TCP/IP return canonical form of host name
519 * Accepts: host name
520 * Returns: canonical form of host name
521 */
522
tcp_canonical(char * name)523 char *tcp_canonical (char *name)
524 {
525 int i;
526 struct hostInfo hst;
527 /* look like domain literal? */
528 if (name[0] == '[' && name[i = (strlen (name))-1] == ']')
529 return cpystr (name);
530 if (StrToAddr (name,&hst,tcp_dns_upp,NIL)) {
531 while (hst.rtnCode == cacheFault && wait ());
532 /* kludge around MacTCP bug */
533 if (hst.rtnCode == outOfMemory) {
534 mm_log ("Re-initializing domain resolver",WARN);
535 CloseResolver (); /* bop it on the head and try again */
536 OpenResolver (NIL); /* note this will leak 12K */
537 StrToAddr (name,&hst,tcp_dns_upp,NIL);
538 while (hst.rtnCode == cacheFault && wait ());
539 }
540 /* still have error status? */
541 if (hst.rtnCode) return cpystr (name);
542 }
543 return cpystr (hst.cname);
544 }
545
546
547 /* TCP/IP get client host name (server calls only)
548 * Returns: client host name
549 */
550
tcp_clienthost()551 char *tcp_clienthost ()
552 {
553 return "UNKNOWN";
554 }
555