mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
a8911cf90f
still as broken as before. [originally from svn r189]
564 lines
16 KiB
C
564 lines
16 KiB
C
/* $Id: mactcp.c,v 1.1.2.1 1999/08/02 08:06:32 ben Exp $ */
|
|
/*
|
|
* Copyright (c) 1999 Ben Harris
|
|
* All rights reserved.
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person
|
|
* obtaining a copy of this software and associated documentation
|
|
* files (the "Software"), to deal in the Software without
|
|
* restriction, including without limitation the rights to use,
|
|
* copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the
|
|
* Software is furnished to do so, subject to the following
|
|
* conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
|
|
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
/*
|
|
* macnet.c -- PuTTY-to-MacTCP glue
|
|
*/
|
|
|
|
#include <MacTypes.h>
|
|
#include <AddressXlation.h>
|
|
#include <Devices.h>
|
|
#include <MacTCP.h>
|
|
#include <MixedMode.h>
|
|
#include <OSUtils.h>
|
|
#include <Processes.h>
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "putty.h"
|
|
|
|
/*
|
|
* The theory behind this stuff:
|
|
*
|
|
* net_recv attempts to deliver any incoming data waiting on the
|
|
* queue. Since MacTCP maintains a buffer for incoming data, there's
|
|
* no need for us to run asynchronous TCPRcvs, and we just do a
|
|
* synchronous one if we detect some data waiting. Since TCPRcv can't
|
|
* be given a timeout of zero, we use TCPStatus to work out if there's
|
|
* anything waiting first.
|
|
*
|
|
* Sending data is trickier. TCPSend reserves the right to block
|
|
* until everything we've sent is ACKed, which means we have to use it
|
|
* asynchronously. In order to make life easier for backends, and to
|
|
* save a proliferation of circular buffers, we guarantee to take data
|
|
* off the hands of the backend as soon as it gives it to us. This is
|
|
* reasonable because currently there's no way for the backend to say
|
|
* it can't take data, and once it's got them, it may as well give
|
|
* them to us.
|
|
*
|
|
* Anyway, in order to avoid a fixed-size buffer overflowing, the send
|
|
* buffer is kept as a queue of blocks. When net_send is called, we
|
|
* malloc a new block and stick it on the queue. If the queue was
|
|
* empty, we kick off a new asynchronous TCPSend to handle our block.
|
|
*
|
|
*/
|
|
|
|
typedef struct Socket {
|
|
TCPiopb iopb; /* current MacTCP operation */
|
|
TCPiopb spareiopb; /* for closing etc */
|
|
hostInfo hostinfo;
|
|
int port;
|
|
ProcessSerialNumber psn;
|
|
Session *s;
|
|
#if TARGET_CPU_68K && !TARGET_RT_CFM
|
|
long a5;
|
|
#endif
|
|
QHdr sendq; /* Blocks waiting to be sent */
|
|
} Socket;
|
|
|
|
typedef struct {
|
|
QElem qelem;
|
|
int flags;
|
|
wdsEntry wds;
|
|
short wdsterm;
|
|
} Send_Buffer;
|
|
|
|
/*
|
|
* Yes, I know the struct QElem has a short[1] to represent the user
|
|
* data. I'm ignoring it because it makes my code prettier and
|
|
* improves the alignment.
|
|
*/
|
|
|
|
typedef struct {
|
|
QElem qelem;
|
|
Socket *sock;
|
|
Net_Event_Type type;
|
|
} NetEvent;
|
|
|
|
#define TCPBUF_SIZE 8192
|
|
|
|
static QHdr macnet_eventq;
|
|
static QHdr macnet_freeq;
|
|
|
|
static short mtcp_refnum;
|
|
static int mtcp_initted = FALSE;
|
|
|
|
static OSErr macnet_init(void);
|
|
static pascal void macnet_resolved(hostInfo *, char *);
|
|
static void macnet_completed_open(TCPiopb*);
|
|
static void macnet_completed_send(TCPiopb*);
|
|
static void macnet_sent(Socket *);
|
|
static void macnet_startsend(Socket *);
|
|
static void macnet_completed_close(TCPiopb*);
|
|
static pascal void macnet_asr(StreamPtr, unsigned short, Ptr, unsigned short,
|
|
ICMPReport *);
|
|
static void macnet_sendevent(Socket *, Net_Event_Type);
|
|
|
|
#if TARGET_RT_MAC_CFM
|
|
static RoutineDescriptor macnet_resolved_upp =
|
|
BUILD_ROUTINE_DESCRIPTOR(uppResultProcInfo, (ProcPtr)macnet_resolved);
|
|
static RoutineDescriptor macnet_completed_open_upp =
|
|
BUILD_ROUTINE_DESCRIPTOR(uppTCPIOCompletionProcInfo,
|
|
(ProcPtr)macnet_completed_open);
|
|
static RoutineDescriptor macnet_complete_send_upp =
|
|
BUILD_ROUTINE_DESCRIPTOR(uppTCPIOCompletionProcInfo,
|
|
(ProcPtr)macnet_completed_send);
|
|
static RoutineDescriptor macnet_completed_close_upp =
|
|
BUILD_ROUTINE_DESCRIPTOR(uppTCPIOCompletionProcInfo,
|
|
(ProcPtr)macnet_completed_close);
|
|
static RoutineDescriptor macnet_asr_upp =
|
|
BUILD_ROUTINE_DESCRIPTOR(uppTCPNotifyProcInfo, (ProcPtr)macnet_asr);
|
|
#else
|
|
/* These handle A5 switching to thwart the optimiser. */
|
|
static pascal void macnet_resolved_upp(hostInfo *, char *);
|
|
static void macnet_completed_open_upp(TCPiopb *);
|
|
static void macnet_completed_send_upp(TCPiopb *);
|
|
static void macnet_completed_close_upp(TCPiopb *);
|
|
/* ASRs apparently get their A5 world set up for them. */
|
|
#define macnet_asr_upp macnet_asr
|
|
#endif
|
|
|
|
/*
|
|
* Number of outstanding network events allowed.
|
|
*/
|
|
#define NUM_EVENTS 16
|
|
|
|
/*
|
|
* Initialise networking. Set mtcp_initted if it goes OK.
|
|
*/
|
|
static OSErr macnet_init(void) {
|
|
OSErr err;
|
|
NetEvent *eventblock;
|
|
int i;
|
|
|
|
/*
|
|
* FIXME: This is hideously broken, in that we're meant to faff
|
|
* with unit numbers and stuff, and we blatantly don't.
|
|
* On the other hand, neither does NCSA Telnet. Hmm.
|
|
*/
|
|
err = opendriver(".IPP", &mtcp_refnum);
|
|
if (err != noErr)
|
|
return err;
|
|
err = OpenResolver(NULL);
|
|
if (err != noErr)
|
|
return err;
|
|
/* Set up the event queues, and fill the free queue with events */
|
|
macnet_eventq.qFlags = 0;
|
|
macnet_eventq.qHead = macnet_eventq.qTail = NULL;
|
|
macnet_freeq.qFlags = 0;
|
|
macnet_freeq.qHead = macnet_eventq.qTail = NULL;
|
|
eventblock = smalloc(NUM_EVENTS * sizeof(*eventblock));
|
|
for (i = 0; i < NUM_EVENTS; i++)
|
|
Enqueue(&eventblock[i].qelem, &macnet_freeq);
|
|
mtcp_initted = TRUE;
|
|
return 0;
|
|
}
|
|
|
|
Socket *net_open(Session *s, char *host, int port) {
|
|
ip_addr a;
|
|
OSErr err = noErr;
|
|
Socket *sock;
|
|
void *tcpbuf;
|
|
|
|
/*
|
|
* First, get hold of all the memory we'll need (a lot of the
|
|
* later stuff happens at interrupt time)
|
|
*/
|
|
sock = smalloc(sizeof(*sock));
|
|
memset(sock, 0, sizeof(*sock));
|
|
tcpbuf = smalloc(TCPBUF_SIZE);
|
|
|
|
/* Make a note of anything we don't want to forget */
|
|
sock->port = port;
|
|
GetCurrentProcess(&sock->psn);
|
|
#if TARGET_CPU_68K && !TARGET_RT_CFM
|
|
sock->a5 = SetCurrentA5();
|
|
#endif
|
|
|
|
/* Get MacTCP running if it's not already */
|
|
if (!mtcp_initted)
|
|
if ((err = macnet_init()) != noErr)
|
|
fatalbox("Couldn't init network (%d)", err);
|
|
|
|
/* Get ourselves a TCP stream to play with */
|
|
sock->iopb.ioCRefNum = mtcp_refnum;
|
|
sock->iopb.ioCompletion = NULL;
|
|
sock->iopb.csCode = TCPCreate;
|
|
sock->iopb.csParam.create.rcvBuff = tcpbuf;
|
|
sock->iopb.csParam.create.rcvBuffLen = TCPBUF_SIZE;
|
|
sock->iopb.csParam.create.notifyProc = macnet_asr_upp;
|
|
sock->iopb.csParam.create.userDataPtr = (Ptr)sock;
|
|
/* This could be done asynchronously, but I doubt it'll take long. */
|
|
err = PBControlSync((ParmBlkPtr)&sock->iopb);
|
|
if (err != noErr)
|
|
fatalbox("TCP stream create failed (%d)", err);
|
|
|
|
err = StrToAddr(host, &sock->hostinfo, &macnet_resolved_upp, (char *)sock);
|
|
/*
|
|
* A cache fault indicates that the DNR will call us back when
|
|
* it's found the host for us.
|
|
*/
|
|
if (err != cacheFault)
|
|
macnet_resolved(&sock->hostinfo, (char *)sock);
|
|
return sock;
|
|
}
|
|
|
|
#if TARGET_CPU_68K && !TARGET_RT_CFM
|
|
static pascal void macnet_resolved_upp(hostInfo *hi, char *cookie) {
|
|
Socket *sock = (Socket *)cookie;
|
|
long olda5;
|
|
|
|
olda5 = SetA5(sock->a5);
|
|
macnet_resolved(hi, cookie);
|
|
SetA5(olda5);
|
|
}
|
|
#endif
|
|
|
|
static pascal void macnet_resolved(hostInfo *hi, char *cookie) {
|
|
Socket *sock = (Socket *)cookie;
|
|
OSErr err;
|
|
|
|
/*
|
|
* We've resolved a name, so now we'd like to connect to it (or
|
|
* report an error).
|
|
*/
|
|
switch (sock->hostinfo.rtnCode) {
|
|
case noErr:
|
|
/* Open a connection */
|
|
sock->iopb.ioCompletion = macnet_completed_open_upp;
|
|
sock->iopb.csCode = TCPActiveOpen;
|
|
memset(&sock->iopb.csParam, 0, sizeof(sock->iopb.csParam));
|
|
sock->iopb.csParam.open.validityFlags = typeOfService;
|
|
sock->iopb.csParam.open.remoteHost = sock->hostinfo.addr[0]; /*XXX*/
|
|
sock->iopb.csParam.open.remotePort = sock->port;
|
|
sock->iopb.csParam.open.tosFlags = lowDelay;
|
|
sock->iopb.csParam.open.userDataPtr = (char *)sock;
|
|
err = PBControlAsync((ParmBlkPtr)&sock->iopb);
|
|
if (err != noErr)
|
|
macnet_sendevent(sock, NE_NOOPEN);
|
|
break;
|
|
default: /* Something went wrong */
|
|
macnet_sendevent(sock, NE_NOHOST);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if TARGET_CPU_68K && !TARGET_RT_CFM
|
|
static void macnet_completed_open_upp(TCPiopb *iopb) {
|
|
Socket *sock = (Socket *)iopb->csParam.open.userDataPtr;
|
|
long olda5;
|
|
|
|
olda5 = SetA5(sock->a5);
|
|
macnet_completed_open(iopb);
|
|
SetA5(olda5);
|
|
}
|
|
#endif
|
|
|
|
static void macnet_completed_open(TCPiopb *iopb) {
|
|
Socket *sock = (Socket *)iopb->csParam.open.userDataPtr;
|
|
|
|
switch (iopb->ioResult) {
|
|
case noErr:
|
|
macnet_sendevent(sock, NE_OPEN);
|
|
break;
|
|
default:
|
|
macnet_sendevent(sock, NE_NOOPEN);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static pascal void macnet_asr(StreamPtr tcpstream, unsigned short eventcode,
|
|
Ptr cookie, unsigned short terminreason,
|
|
ICMPReport *icmpmsg) {
|
|
Socket *sock = (Socket *)cookie;
|
|
|
|
switch (eventcode) {
|
|
case TCPClosing:
|
|
macnet_sendevent(sock, NE_CLOSING);
|
|
break;
|
|
case TCPULPTimeout:
|
|
macnet_sendevent(sock, NE_TIMEOUT);
|
|
break;
|
|
case TCPTerminate:
|
|
switch (terminreason) {
|
|
case TCPRemoteAbort:
|
|
macnet_sendevent(sock, NE_ABORT);
|
|
break;
|
|
default:
|
|
macnet_sendevent(sock, NE_DIED);
|
|
break;
|
|
}
|
|
break;
|
|
case TCPDataArrival:
|
|
macnet_sendevent(sock, NE_DATA);
|
|
break;
|
|
case TCPUrgent:
|
|
macnet_sendevent(sock, NE_URGENT);
|
|
break;
|
|
case TCPICMPReceived:
|
|
switch (icmpmsg->reportType) {
|
|
case portUnreach:
|
|
macnet_sendevent(sock, NE_REFUSED);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Send a block of data.
|
|
*/
|
|
|
|
int net_send(Socket *sock, void *buf, int buflen, int flags) {
|
|
OSErr err;
|
|
Send_Buffer *buff;
|
|
|
|
buff = smalloc(sizeof(Send_Buffer) + buflen);
|
|
buff->flags = flags;
|
|
buff->wds.length = buflen;
|
|
buff->wds.ptr = (Ptr)&buff[1]; /* after the end of the struct */
|
|
buff->wdsterm = 0;
|
|
memcpy(&buff[1], buf, buflen);
|
|
Enqueue(&buff->qelem, &sock->sendq);
|
|
/* Kick off the transmit if the queue was empty */
|
|
if (sock->sendq.qHead == &buff->qelem)
|
|
macnet_startsend(sock);
|
|
}
|
|
|
|
/*
|
|
* This is called once every time round the event loop to check for
|
|
* network events and handle them.
|
|
*/
|
|
void macnet_eventcheck() {
|
|
NetEvent *ne;
|
|
|
|
if (!mtcp_initted)
|
|
return;
|
|
ne = (NetEvent *)macnet_eventq.qHead;
|
|
if (ne == NULL)
|
|
return;
|
|
Dequeue(&ne->qelem, &macnet_eventq);
|
|
switch (ne->type) {
|
|
case NE_SENT:
|
|
macnet_sent(ne->sock);
|
|
break;
|
|
default:
|
|
(ne->sock->s->back->msg)(ne->sock->s, ne->sock, ne->type);
|
|
break;
|
|
}
|
|
Enqueue(&ne->qelem, &macnet_freeq);
|
|
}
|
|
|
|
/*
|
|
* The block at the head of the send queue has finished sending, so we
|
|
* can free it. Kick off the next transmission if there is one.
|
|
*/
|
|
static void macnet_sent(Socket *sock) {
|
|
Send_Buffer *buff;
|
|
|
|
assert(sock->sendq.qHead != NULL);
|
|
buff = (Send_Buffer *)sock->sendq.qHead;
|
|
Dequeue(&buff->qelem, &sock->sendq);
|
|
sfree(buff);
|
|
if (sock->sendq.qHead != NULL)
|
|
macnet_startsend(sock);
|
|
}
|
|
|
|
/*
|
|
* There's a block on the head of the send queue which needs to be
|
|
* sent.
|
|
*/
|
|
|
|
static void macnet_startsend(Socket *sock) {
|
|
Send_Buffer *buff;
|
|
OSErr err;
|
|
|
|
buff = (Send_Buffer *)sock->sendq.qHead;
|
|
sock->iopb.ioCompletion = macnet_completed_send_upp;
|
|
sock->iopb.csCode = TCPSend;
|
|
memset(&sock->iopb.csParam, 0, sizeof(sock->iopb.csParam));
|
|
sock->iopb.csParam.send.validityFlags = 0;
|
|
sock->iopb.csParam.send.pushFlag = buff->flags & SEND_PUSH ? true : false;
|
|
sock->iopb.csParam.send.urgentFlag = buff->flags & SEND_URG ? true : false;
|
|
sock->iopb.csParam.send.wdsPtr = (Ptr)&buff->wds;
|
|
sock->iopb.csParam.send.userDataPtr = (char *)sock;
|
|
err = PBControlAsync((ParmBlkPtr)&sock->iopb);
|
|
}
|
|
|
|
#if TARGET_CPU_68K && !TARGET_RT_CFM
|
|
static void macnet_completed_send_upp(TCPiopb *iopb) {
|
|
Socket *sock = (Socket *)iopb->csParam.send.userDataPtr;
|
|
long olda5;
|
|
|
|
olda5 = SetA5(sock->a5);
|
|
macnet_completed_send(iopb);
|
|
SetA5(olda5);
|
|
}
|
|
#endif
|
|
|
|
static void macnet_completed_send(TCPiopb *iopb) {
|
|
Socket *sock = (Socket *)iopb->csParam.send.userDataPtr;
|
|
|
|
switch (iopb->ioResult) {
|
|
case noErr:
|
|
macnet_sendevent(sock, NE_SENT);
|
|
break;
|
|
case connectionClosing:
|
|
case connectionTerminated:
|
|
/* We'll get an ASR, so ignore it here. */
|
|
break;
|
|
default:
|
|
macnet_sendevent(sock, NE_DIED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
int net_recv(Socket *sock, void *buf, int buflen, int flags) {
|
|
TCPiopb iopb;
|
|
OSErr err;
|
|
int avail, want, got;
|
|
|
|
memcpy(&iopb, &sock->iopb, sizeof(TCPiopb));
|
|
/* Work out if there's anything to recieve (we don't want to block) */
|
|
iopb.ioCompletion = NULL;
|
|
iopb.csCode = TCPStatus;
|
|
memset(&sock->iopb.csParam, 0, sizeof(sock->iopb.csParam));
|
|
err = PBControlSync((ParmBlkPtr)&iopb);
|
|
if (err != noErr)
|
|
return 0; /* macnet_asr should catch it anyway */
|
|
avail = iopb.csParam.status.amtUnreadData;
|
|
if (avail == 0)
|
|
return 0;
|
|
want = avail < buflen ? avail : buflen;
|
|
iopb.ioCompletion = NULL;
|
|
iopb.csCode = TCPRcv;
|
|
memset(&sock->iopb.csParam, 0, sizeof(sock->iopb.csParam));
|
|
iopb.csParam.receive.rcvBuff = buf;
|
|
iopb.csParam.receive.rcvBuffLen = want;
|
|
err = PBControlSync((ParmBlkPtr)&iopb);
|
|
if (err != noErr)
|
|
return 0;
|
|
return iopb.csParam.receive.rcvBuffLen;
|
|
}
|
|
|
|
|
|
void net_close(Socket *sock) {
|
|
OSErr err;
|
|
|
|
/*
|
|
* This might get called in the middle of processing another
|
|
* request on the socket, so we have a spare parameter block for
|
|
* this purpose (allocating one dynamically would mean having to
|
|
* free it, which we can't do at interrupt time).
|
|
*/
|
|
memcpy(&sock->spareiopb, &sock->iopb, sizeof(TCPiopb));
|
|
memset(&sock->spareiopb.csParam, 0, sizeof(sock->spareiopb.csParam));
|
|
sock->spareiopb.ioCompletion = macnet_completed_close_upp;
|
|
sock->spareiopb.csCode = TCPClose;
|
|
sock->spareiopb.csParam.close.validityFlags = 0;
|
|
sock->spareiopb.csParam.close.userDataPtr = (char *)sock;
|
|
err = PBControlAsync((ParmBlkPtr)&sock->spareiopb);
|
|
switch (err) {
|
|
case noErr:
|
|
case connectionClosing:
|
|
case connectionTerminated: /* We'll get an ASR */
|
|
break;
|
|
default:
|
|
macnet_sendevent(sock, NE_DIED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if TARGET_CPU_68K && !TARGET_RT_CFM
|
|
static void macnet_completed_close_upp(TCPiopb* iopb) {
|
|
Socket *sock = (Socket *)iopb->csParam.close.userDataPtr;
|
|
long olda5;
|
|
|
|
olda5 = SetA5(sock->a5);
|
|
macnet_completed_close(iopb);
|
|
SetA5(olda5);
|
|
}
|
|
#endif
|
|
|
|
static void macnet_completed_close(TCPiopb* iopb) {
|
|
Socket *sock = (Socket *)iopb->csParam.close.userDataPtr;
|
|
|
|
switch (iopb->ioResult) {
|
|
case noErr:
|
|
macnet_sendevent(sock, NE_CLOSED);
|
|
break;
|
|
case connectionClosing:
|
|
case connectionTerminated:
|
|
break;
|
|
default:
|
|
macnet_sendevent(sock, NE_DIED);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Free all the data structures associated with a socket and tear down
|
|
* any connection through it.
|
|
*/
|
|
void net_destroy(Socket *sock) {
|
|
TCPiopb iopb;
|
|
OSErr err;
|
|
|
|
/*
|
|
* Yes, we need _another_ iopb, as there may be a send _and_ a
|
|
* close outstanding. Luckily, destroying a socket is
|
|
* synchronous, so we can allocate this one dynamically.
|
|
*/
|
|
memcpy(&iopb, &sock->iopb, sizeof(TCPiopb));
|
|
iopb.ioCompletion = NULL;
|
|
iopb.csCode = TCPRelease;
|
|
memset(&iopb.csParam, 0, sizeof(iopb.csParam));
|
|
err = PBControlSync((ParmBlkPtr)&iopb);
|
|
sfree(iopb.csParam.create.rcvBuff);
|
|
sfree(sock);
|
|
}
|
|
|
|
static void macnet_sendevent(Socket *sock, Net_Event_Type type) {
|
|
NetEvent *ne;
|
|
|
|
ne = (NetEvent *)macnet_freeq.qHead;
|
|
if (ne == NULL) return; /* It's a disaster, but how do we tell anyone? */
|
|
Dequeue(&ne->qelem, &macnet_freeq);
|
|
ne->sock = sock;
|
|
ne->type = type;
|
|
Enqueue(&ne->qelem, &macnet_eventq);
|
|
WakeUpProcess(&sock->psn);
|
|
}
|
|
|
|
/*
|
|
* Local Variables:
|
|
* c-file-style: "simon"
|
|
* End:
|
|
*/
|