jcs
/wallops
/amendments
/1
wallops: Initial import
jcs made amendment 1 over 2 years ago
--- AddressXlation.h Fri Sep 18 08:57:06 2020
+++ AddressXlation.h Fri Sep 18 08:57:06 2020
@@ -0,0 +1,74 @@
+/*
+ AddressXlation.h
+ MacTCP name to address translation routines.
+
+ Copyright Apple Computer, Inc. 1988
+ All rights reserved
+
+*/
+
+#ifndef __ADDRESSXLATION__
+#define __ADDRESSXLATION__
+
+#include "MacTCP.h"
+
+#define NUM_ALT_ADDRS 4
+
+typedef struct hostInfo {
+ int _pad; /* XXX: i don't know why this is needed, but without it,
+ * StrToAddrProcPtr() returns everything shifted 2 bytes */
+ int rtnCode;
+ char cname[255];
+ unsigned long addr[NUM_ALT_ADDRS];
+};
+
+typedef enum AddrClasses {
+ A = 1,
+ NS,
+ CNAME = 5,
+ lastClass = 65535
+};
+
+typedef struct cacheEntryRecord {
+ char *cname;
+ unsigned short type;
+ enum AddrClasses class;
+ unsigned long ttl;
+ union {
+ char *name;
+ ip_addr addr;
+ } rdata;
+};
+
+typedef pascal void (*EnumResultProcPtr)(struct cacheEntryRecord *cacheEntryRecordPtr,
+ char *userDataPtr);
+typedef pascal void (*ResultProcPtr)(struct hostInfo *hostInfoPtr,
+ char *userDataPtr);
+
+extern OSErr OpenResolver(char *fileName);
+extern OSErr StrToAddr(char *hostName, struct hostInfo *hostInfoPtr,
+ ResultProcPtr resultProc, char *userDataPtr);
+extern OSErr AddrToStr(unsigned long addr, char *addrStr);
+extern OSErr EnumCache(EnumResultProcPtr enumResultProc, char *userDataPtr);
+extern OSErr AddrToName(ip_addr addr, struct hostInfo *hostInfoPtr,
+ ResultProcPtr resultProc, char *userDataPtr);
+extern OSErr CloseResolver(void);
+
+/*
+ * The above functions call into the dnr resource and pass the function id
+ * and function-specific arguments, so the compiler needs to know the types
+ */
+typedef OSErr (*OpenResolverProcPtr)(UInt32, char *);
+typedef OSErr (*CloseResolverProcPtr)(UInt32);
+typedef OSErr (*StrToAddrProcPtr)(UInt32, char *hostName,
+ struct hostInfo *hostInfoPtr, ResultProcPtr resultProc,
+ char *userDataPtr);
+typedef OSErr (*AddrToStrProcPtr)(UInt32, unsigned long addr,
+ char *addrStr);
+typedef OSErr (*EnumCacheProcPtr)(UInt32, EnumResultProcPtr enumResultProc,
+ char *userDataPtr);
+typedef OSErr (*AddrToNameProcPtr)(UInt32, ip_addr addr,
+ struct hostInfo *hostInfoPtr, ResultProcPtr resultProc,
+ char *userDataPtr);
+
+#endif
--- chatter.c Sun Jan 30 13:44:20 2022
+++ chatter.c Sun Jan 30 13:44:20 2022
@@ -0,0 +1,406 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include "chatter.h"
+#include "irc.h"
+#include "util.h"
+
+#define NICK_LIST_WIDTH 100
+
+void chatter_update_titlebar(struct chatter *chatter);
+void chatter_layout(struct chatter *chatter, bool init);
+void chatter_key_down(struct focusable *focusable, EventRecord *event);
+void chatter_mouse_down(struct focusable *focusable, EventRecord *event);
+void chatter_idle(struct focusable *focusable, EventRecord *event);
+void chatter_update(struct focusable *focusable, EventRecord *event);
+void chatter_close(struct focusable *focusable, EventRecord *event);
+void chatter_atexit(struct focusable *focusable);
+
+void
+chatter_init(const char *server, const unsigned short port,
+ const char *nick)
+{
+ struct focusable *focusable;
+ struct chatter *chatter;
+ char title[64];
+ Rect bounds = { 0 }, control_bounds = { 0 };
+ Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
+ Point cell_size = { 0 };
+ Cell cell = { 0 };
+
+ if (_TCPInit() != 0)
+ panic("TCPInit failed");
+
+ chatter = xmalloczero(sizeof(struct chatter));
+ chatter->irc_state = IRC_STATE_UNINITED;
+ chatter->irc_hostname = xstrdup(server);
+ chatter->irc_port = port;
+ chatter->irc_nick = xstrdup(nick);
+
+ bounds.left = 10;
+ bounds.top = screenBits.bounds.top + 10 + (GetMBarHeight() * 2) - 1;
+ bounds.right = screenBits.bounds.right - 11;
+ bounds.bottom = screenBits.bounds.bottom - 11;
+
+ snprintf(title, sizeof(title), "%s: Disconnected", PROGRAM_NAME);
+ chatter->win = NewWindow(0L, &bounds, CtoPstr(title), false,
+ documentProc, (WindowPtr)-1L, true, 0);
+ if (!chatter->win)
+ panic("Can't create chatter window");
+
+ SetPort(chatter->win);
+ TextFont(applFont);
+ TextSize(10);
+
+ /* initially draw everything off-screen until we chatter_layout() */
+ bounds.left = chatter->win->portRect.right + 10;
+ bounds.top = chatter->win->portRect.top + 10;
+ bounds.right = bounds.left + 20;
+ bounds.bottom = bounds.top + 20;
+ chatter->win_init_bounds = bounds;
+
+ /* input */
+ chatter->input_te = TENew(&bounds, &bounds);
+
+ /* nick list */
+ chatter->nick_list = LNew(&bounds, &data_bounds, cell_size, 0,
+ chatter->win, true, true, false, true);
+ if (!chatter->nick_list)
+ panic("Can't create mailboxes list");
+ (*(chatter->nick_list))->selFlags = lOnlyOne | lNoNilHilite;
+
+ /* nick scroller */
+ chatter->nick_scroller = NewControl(chatter->win, &bounds, "\p", true,
+ 1, 1, 1, scrollBarProc, 0L);
+
+ /* messages */
+ chatter->messages_te = TENew(&bounds, &bounds);
+
+ /* messages scrollbar */
+ chatter->messages_scroller = NewControl(chatter->win, &bounds, "\p",
+ true, 1, 1, 1, scrollBarProc, 0L);
+
+ focusable = xmalloczero(sizeof(struct focusable));
+ focusable->win = chatter->win;
+ focusable->cookie = chatter;
+ focusable->idle = chatter_idle;
+ focusable->key_down = chatter_key_down;
+ focusable->mouse_down = chatter_mouse_down;
+ focusable->update = chatter_update;
+ focusable->close = chatter_close;
+ focusable->atexit = chatter_atexit;
+ show_focusable(focusable);
+ chatter_layout(chatter, false);
+
+ UpdateScrollbarForTE(chatter->messages_scroller, chatter->messages_te,
+ false);
+
+ chatter_output(chatter, "*** Welcome to %s\r", PROGRAM_NAME);
+}
+
+void
+chatter_layout(struct chatter *chatter, bool init)
+{
+ Rect bounds, win_bounds;
+
+ if (init)
+ win_bounds = chatter->win_init_bounds;
+ else
+ win_bounds = chatter->win->portRect;
+
+ /* input */
+ bounds.left = 0;
+ bounds.right = win_bounds.right - SCROLLBAR_WIDTH;
+ bounds.top = win_bounds.bottom - SCROLLBAR_WIDTH;
+ bounds.bottom = win_bounds.bottom;
+ (*(chatter->input_te))->viewRect = bounds;
+ InsetRect(&bounds, 3, 1);
+ (*(chatter->input_te))->destRect = bounds;
+ TECalText(chatter->input_te);
+
+ /* nick scrollbar */
+ bounds.top = -1;
+ bounds.right = win_bounds.right + 1;
+ bounds.left = bounds.right - SCROLLBAR_WIDTH;
+ bounds.bottom = (*(chatter->input_te))->viewRect.top - 1;
+ (*(chatter->nick_scroller))->contrlRect = bounds;
+
+ /* nick list */
+ bounds.top = 0;
+ bounds.right = (*(chatter->nick_scroller))->contrlRect.left;
+ bounds.left = bounds.right - NICK_LIST_WIDTH;
+ bounds.bottom = (*(chatter->input_te))->viewRect.top - 2;
+ (*(chatter->nick_list))->rView = bounds;
+
+ /* messages scrollbar */
+ bounds.top = -1;
+ bounds.right = (*(chatter->nick_list))->rView.left;
+ bounds.left = bounds.right - SCROLLBAR_WIDTH;
+ bounds.bottom = (*(chatter->input_te))->viewRect.top - 1;
+ (*(chatter->messages_scroller))->contrlRect = bounds;
+
+ /* messages */
+ bounds.right = (*(chatter->messages_scroller))->contrlRect.left;
+ bounds.left = 0;
+ bounds.top = 0;
+ bounds.bottom = (*(chatter->input_te))->viewRect.top - 2;
+ (*(chatter->messages_te))->viewRect = bounds;
+ InsetRect(&bounds, 4, 4);
+ (*(chatter->messages_te))->destRect = bounds;
+ TECalText(chatter->messages_te);
+
+ InvalRect(chatter->win->visRgn);
+}
+
+void
+chatter_close(struct focusable *focusable, EventRecord *event)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+
+ irc_close(chatter);
+ close_focusable(focusable);
+}
+
+void
+chatter_atexit(struct focusable *focusable)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+
+ irc_abort(chatter);
+}
+
+void
+chatter_update_titlebar(struct chatter *chatter)
+{
+ char title[64];
+
+ snprintf(title, sizeof(title), "%s: %s@%s", PROGRAM_NAME,
+ chatter->irc_nick, chatter->irc_hostname);
+ SetWTitle(chatter->win, CtoPstr(title));
+}
+
+void
+chatter_idle(struct focusable *focusable, EventRecord *event)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+
+ TEIdle(chatter->messages_te);
+ TEIdle(chatter->input_te);
+ irc_process(chatter);
+}
+
+void
+chatter_update(struct focusable *focusable, EventRecord *event)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+ GrafPtr old_port;
+ Rect r;
+ short what = -1;
+
+ GetPort(&old_port);
+
+ if (event != NULL)
+ what = event->what;
+
+ switch (what) {
+ case -1:
+ case updateEvt:
+ TextFont(applFont);
+ TextSize(10);
+
+ EraseRect(&chatter->win->portRect);
+
+ r = (*(chatter->nick_list))->rView;
+ EraseRect(&r);
+ LUpdate(chatter->win->visRgn, chatter->nick_list);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ r = (*(chatter->messages_te))->viewRect;
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ TEUpdate(&(*(chatter->messages_te))->viewRect,
+ chatter->messages_te);
+ TEUpdate(&(*(chatter->input_te))->viewRect, chatter->messages_te);
+
+ DrawControls(chatter->win);
+ break;
+ case activateEvt:
+ if (event->modifiers & activeFlag) {
+ LActivate(true, chatter->nick_list);
+ TEActivate(chatter->input_te);
+ } else {
+ LActivate(false, chatter->nick_list);
+ TEDeactivate(chatter->input_te);
+ }
+ break;
+ }
+}
+
+void
+chatter_mouse_down(struct focusable *focusable, EventRecord *event)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+ Point p;
+ Cell selected = { 0 }, now = { 0 }, t = { 0 };
+ ControlHandle control;
+ Rect r;
+ int val, adj, page, ret;
+ short part;
+
+ p = event->where;
+ GlobalToLocal(&p);
+
+ r = (*(chatter->nick_list))->rView;
+ r.right += SCROLLBAR_WIDTH;
+ if (PtInRect(p, &r)) {
+ /* store what is selected now */
+ ret = LGetSelect(true, &selected, chatter->nick_list);
+
+ /* possibly highlight a new cell */
+ LClick(p, event->modifiers, chatter->nick_list);
+
+ if (selected.v != now.v) {
+ LSetSelect(false, selected, chatter->nick_list);
+ /* TODO */
+ }
+
+ return;
+ }
+
+ r = (*(chatter->messages_te))->viewRect;
+ if (PtInRect(p, &r)) {
+ TEClick(p, ((event->modifiers & shiftKey) != 0),
+ chatter->messages_te);
+ return;
+ }
+
+ r = (*(chatter->input_te))->viewRect;
+ if (PtInRect(p, &r)) {
+ TEClick(p, ((event->modifiers & shiftKey) != 0),
+ chatter->input_te);
+ return;
+ }
+
+ switch (part = FindControl(p, chatter->win, &control)) {
+ case inUpButton:
+ case inDownButton:
+ case inPageUp:
+ case inPageDown:
+ if (control != chatter->messages_scroller)
+ break;
+ SetTrackControlTE(chatter->messages_te);
+ TrackControl(control, p, TrackMouseDownInControl);
+ break;
+ case inThumb:
+ val = GetCtlValue(control);
+ if (TrackControl(control, p, 0L) == 0)
+ break;
+ adj = val - GetCtlValue(control);
+ if (adj != 0) {
+ val -= adj;
+ if (control == chatter->messages_scroller)
+ TEScroll(0, adj * TEGetHeight(0, 0, chatter->messages_te),
+ chatter->messages_te);
+ SetCtlValue(control, val);
+ }
+ break;
+ }
+}
+
+#if 0
+void
+chatter_grow(Point p)
+{
+ GrafPtr old_port;
+ long res;
+ Rect r;
+
+ GetPort(&old_port);
+
+ SetPort(chatter_win);
+ SetRect(&r, 80, 80, screenBits.bounds.right, screenBits.bounds.bottom);
+ res = GrowWindow(chatter_win, p, &r);
+ if (res != 0) {
+ SizeWindow(chatter_win, LoWord(res), HiWord(res), false);
+ InvalRect(&chatter_win->portRect);
+ chatter_layout();
+ }
+
+ SetPort(old_port);
+}
+#endif
+
+void
+chatter_key_down(struct focusable *focusable, EventRecord *event)
+{
+ struct chatter *chatter = (struct chatter *)(focusable->cookie);
+ char k;
+
+ k = (event->message & charCodeMask);
+ TEKey(k, chatter->input_te);
+}
+
+size_t
+chatter_output(struct chatter *chatter, const char *format, ...)
+{
+ va_list argptr;
+ size_t len = 0, last_pos, last_line, i;
+ short vlines, tries;
+ char didup = 0;
+
+ for (tries = 1; tries <= 3; tries++) {
+ if (chatter->message_line_size == 0) {
+ chatter->message_line_size = 1024 * tries;
+ chatter->message_line = xNewHandle(chatter->message_line_size);
+ }
+
+ va_start(argptr, format);
+ HLock(chatter->message_line);
+ /* TODO: vsnprintf */
+ len = vsprintf(*(chatter->message_line), format, argptr);
+ va_end(argptr);
+ HUnlock(chatter->message_line);
+
+ if (len < chatter->message_line_size)
+ break;
+
+ DisposHandle(chatter->message_line);
+ chatter->message_line = NULL;
+ chatter->message_line_size = 0;
+ }
+
+ if (chatter->message_line_size == 0)
+ panic("Can't build message line of size %lu", len);
+
+ HLock(chatter->message_line);
+ TESetSelect(1024 * 32, 1024 * 32, chatter->messages_te);
+ TEInsert(*(chatter->message_line), len, chatter->messages_te);
+ HUnlock(chatter->message_line);
+
+ if (tries > 1) {
+ /* shrink */
+ DisposHandle(chatter->message_line);
+ chatter->message_line = NULL;
+ chatter->message_line_size = 0;
+ }
+
+ return len;
+}
--- chatter.h Sun Jan 30 14:35:31 2022
+++ chatter.h Sun Jan 30 14:35:31 2022
@@ -0,0 +1,55 @@
+#include <stdio.h>
+
+#include "tcp.h"
+#include "util.h"
+
+#define PROGRAM_NAME "wallops"
+
+struct focusable {
+ WindowPtr win;
+ void *cookie;
+ void (*idle)(struct focusable *focusable, EventRecord *event);
+ void (*update)(struct focusable *focusable, EventRecord *event);
+ void (*key_down)(struct focusable *focusable, EventRecord *event);
+ void (*mouse_down)(struct focusable *focusable, EventRecord *event);
+ void (*close)(struct focusable *focusable, EventRecord *event);
+ void (*suspend)(struct focusable *focusable, EventRecord *event);
+ void (*resume)(struct focusable *focusable, EventRecord *event);
+ void (*atexit)(struct focusable *focusable);
+};
+extern struct focusable *cur_focusable;
+extern struct focusable *last_focusable;
+
+struct chatter {
+ WindowPtr win;
+ Rect win_init_bounds;
+ TEHandle messages_te;
+ ControlHandle messages_scroller;
+ TEHandle input_te;
+ ListHandle nick_list;
+ ControlHandle nick_scroller;
+ Handle message_line;
+ size_t message_line_size;
+
+ short irc_state;
+ char *irc_hostname;
+ short irc_port;
+ char *irc_nick;
+ TCPiopb irc_rcv_pb, irc_send_pb, irc_close_pb;
+ TCPStatusPB irc_status_pb;
+ StreamPtr irc_stream;
+ wdsEntry irc_wds[2];
+ unsigned char irc_obuf[512];
+ unsigned char irc_ibuf[512]; /* RFC2812 says max line will be 512 */
+ short irc_obuflen;
+ short irc_ibuflen;
+ /* docs say 4*MTU+1024, but MTU will probably be <1500 */
+ unsigned char irc_tcp_buf[(4 * 1500) + 1024];
+};
+
+void show_focusable(struct focusable *focusable);
+void close_focusable(struct focusable *focusable);
+
+void chatter_init(const char *server, const unsigned short port,
+ const char *nick);
+size_t chatter_output(struct chatter *chatter, const char *format, ...);
--- dnr.c Thu Sep 17 14:11:02 2020
+++ dnr.c Thu Sep 17 14:11:02 2020
@@ -0,0 +1,308 @@
+/*
+ * DNR.c - DNR library for MPW
+ *
+ * (c) Copyright 1988 by Apple Computer. All rights reserved
+ *
+ * Modifications by Jim Matthews, Dartmouth College, 5/91
+ */
+
+#include <OSUtils.h>
+#include <Files.h>
+#include <Folders.h>
+#include <GestaltEqu.h>
+#include <Traps.h>
+
+#include "AddressXlation.h"
+
+#define OPENRESOLVER 1
+#define CLOSERESOLVER 2
+#define STRTOADDR 3
+#define ADDRTOSTR 4
+#define ENUMCACHE 5
+#define ADDRTONAME 6
+#define HINFO 7
+#define MXINFO 8
+
+TrapType GetTrapType(unsigned long theTrap);
+Boolean TrapAvailable(unsigned long trap);
+void GetSystemFolder(short *vRefNumP, long *dirIDP);
+void GetCPanelFolder(short *vRefNumP, long *dirIDP);
+short SearchFolderForDNRP(long targetType, long targetCreator,
+ short vRefNum, long dirID);
+short OpenOurRF(void);
+
+Handle dnr = nil;
+
+TrapType
+GetTrapType(unsigned long theTrap)
+{
+ if (BitAnd(theTrap, 0x0800) > 0)
+ return (ToolTrap);
+
+ return (OSTrap);
+}
+
+Boolean
+TrapAvailable(unsigned long trap)
+{
+ TrapType trapType = ToolTrap;
+ unsigned long numToolBoxTraps;
+
+ if (NGetTrapAddress(_InitGraf, ToolTrap) ==
+ NGetTrapAddress(0xAA6E, ToolTrap))
+ numToolBoxTraps = 0x200;
+ else
+ numToolBoxTraps = 0x400;
+
+ trapType = GetTrapType(trap);
+ if (trapType == ToolTrap) {
+ trap = BitAnd(trap, 0x07FF);
+ if (trap >= numToolBoxTraps)
+ trap = _Unimplemented;
+ }
+
+ return (NGetTrapAddress(trap, trapType) !=
+ NGetTrapAddress(_Unimplemented, ToolTrap));
+}
+
+void
+GetSystemFolder(short *vRefNumP, long *dirIDP)
+{
+ SysEnvRec info;
+ long wdProcID;
+
+ SysEnvirons(1, &info);
+ if (GetWDInfo(info.sysVRefNum, vRefNumP, dirIDP, &wdProcID) != noErr) {
+ *vRefNumP = 0;
+ *dirIDP = 0;
+ }
+}
+
+void
+GetCPanelFolder(short *vRefNumP, long *dirIDP)
+{
+ Boolean hasFolderMgr = false;
+ long feature;
+
+ if (TrapAvailable(_GestaltDispatch) &&
+ Gestalt(gestaltFindFolderAttr, &feature) == noErr)
+ hasFolderMgr = true;
+
+ if (!hasFolderMgr) {
+ GetSystemFolder(vRefNumP, dirIDP);
+ return;
+ }
+
+ if (FindFolder(kOnSystemDisk, kControlPanelFolderType,
+ kDontCreateFolder, vRefNumP, dirIDP) != noErr) {
+ *vRefNumP = 0;
+ *dirIDP = 0;
+ }
+}
+
+/*
+ * SearchFolderForDNRP is called to search a folder for files that might
+ * contain the 'dnrp' resource
+ */
+short
+SearchFolderForDNRP(long targetType, long targetCreator, short vRefNum,
+ long dirID)
+{
+ HParamBlockRec fi;
+ Str255 filename;
+ short refnum;
+
+ fi.fileParam.ioCompletion = nil;
+ fi.fileParam.ioNamePtr = filename;
+ fi.fileParam.ioVRefNum = vRefNum;
+ fi.fileParam.ioDirID = dirID;
+ fi.fileParam.ioFDirIndex = 1;
+
+ while (PBHGetFInfo(&fi, false) == noErr) {
+ /* scan system folder for driver resource files of specific type & creator */
+ if (fi.fileParam.ioFlFndrInfo.fdType == targetType &&
+ fi.fileParam.ioFlFndrInfo.fdCreator == targetCreator) {
+ /* found the MacTCP driver file? */
+ refnum = HOpenResFile(vRefNum, dirID, filename, fsRdPerm);
+ if (Get1IndResource('dnrp', 1))
+ return refnum;
+ CloseResFile(refnum);
+ }
+
+ /* check next file in system folder */
+ fi.fileParam.ioFDirIndex++;
+ fi.fileParam.ioDirID = dirID; /* PBHGetFInfo() clobbers ioDirID */
+ }
+ return -1;
+}
+
+/* OpenOurRF is called to open the MacTCP driver resources */
+short
+OpenOurRF(void)
+{
+ short refnum;
+ short vRefNum;
+ long dirID;
+
+ /* first search Control Panels for MacTCP 1.1 */
+ GetCPanelFolder(&vRefNum, &dirID);
+ refnum = SearchFolderForDNRP('cdev', 'ztcp', vRefNum, dirID);
+ if (refnum != -1)
+ return (refnum);
+
+ /* next search System Folder for MacTCP 1.0.x */
+ GetSystemFolder(&vRefNum, &dirID);
+ refnum = SearchFolderForDNRP('cdev', 'mtcp', vRefNum, dirID);
+ if (refnum != -1)
+ return (refnum);
+
+ /* finally, search Control Panels for MacTCP 1.0.x */
+ GetCPanelFolder(&vRefNum, &dirID);
+ refnum = SearchFolderForDNRP('cdev', 'mtcp', vRefNum, dirID);
+ if (refnum != -1)
+ return (refnum);
+
+ return -1;
+}
+
+OSErr
+OpenResolver(char *fileName)
+{
+ short refnum;
+ OSErr rc;
+
+ if (dnr != nil)
+ /* resolver already loaded in */
+ return (noErr);
+
+ /*
+ * Open the MacTCP driver to get DNR resources. Search for it based on
+ * creator & type rather than simply file name.
+ */
+ refnum = OpenOurRF();
+
+ /*
+ * Ignore failures since the resource may have been installed in the
+ * System file if running on a Mac 512Ke.
+ */
+
+ /* load in the DNR resource package */
+ ResrvMem(16000L); /* s-dorner@uiuc.edu, 9/5/91 */
+ dnr = Get1IndResource('dnrp', 1);
+ if (dnr == nil) {
+ /* can't open DNR */
+ return (ResError());
+ }
+
+ DetachResource(dnr);
+ if (refnum != -1) {
+ CloseWD(refnum);
+ CloseResFile(refnum);
+ }
+
+ /* lock the DNR resource since it cannot be relocated while opened */
+ MoveHHi(dnr);
+ HLock(dnr);
+
+ /* call open resolver */
+ rc = ((OpenResolverProcPtr)*dnr)(OPENRESOLVER, fileName);
+ if (rc != noErr) {
+ /* problem with open resolver, flush it */
+ HUnlock(dnr);
+ DisposHandle(dnr);
+ dnr = nil;
+ }
+
+ return (rc);
+}
+
+OSErr
+CloseResolver(void)
+{
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ /* call close resolver */
+ (void)((CloseResolverProcPtr)*dnr)(CLOSERESOLVER);
+
+ /* release the DNR resource package */
+ HUnlock(dnr);
+ DisposHandle(dnr);
+ dnr = nil;
+
+ return (noErr);
+}
+
+OSErr
+StrToAddr(char *hostName, struct hostInfo *rtnStruct,
+ ResultProcPtr resultproc, char *userDataPtr)
+{
+ OSErr err;
+
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ err = ((StrToAddrProcPtr)*dnr)(STRTOADDR, hostName, rtnStruct,
+ resultproc, userDataPtr);
+ return err;
+}
+
+OSErr
+AddrToStr(unsigned long addr, char *addrStr)
+{
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ (void)((AddrToStrProcPtr)*dnr)(ADDRTOSTR, addr, addrStr);
+ return (noErr);
+}
+
+OSErr
+EnumCache(EnumResultProcPtr enumResultProc, char *userDataPtr)
+{
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ return ((EnumCacheProcPtr)*dnr)(ENUMCACHE, enumResultProc,
+ userDataPtr);
+}
+
+OSErr
+AddrToName(ip_addr addr, struct hostInfo *hostInfoPtr,
+ ResultProcPtr resultProc, char *userDataPtr)
+{
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ return ((AddrToNameProcPtr)*dnr)(ADDRTONAME, addr, hostInfoPtr,
+ resultProc, userDataPtr);
+}
+
+#if 0
+OSErr
+HInfo(char *hostName, struct returnRec *returnRecPtr, long resultProc,
+ char *userDataPtr)
+{
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ return ((*dnr)(HINFO, hostName, returnRecPtr, resultProc, userDataPtr));
+}
+
+OSErr
+MXInfo(char *hostName, struct returnRec *returnRecPtr, long resultProc,
+ char *userDataPtr)
+{
+ if (dnr == nil)
+ /* resolver not loaded error */
+ return (notOpenErr);
+
+ return ((*dnr)(MXINFO, hostName, returnRecPtr, resultProc, userDataPtr));
+}
+#endif
--- irc.c Sun Jan 30 14:33:57 2022
+++ irc.c Sun Jan 30 14:33:57 2022
@@ -0,0 +1,255 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+
+#include "chatter.h"
+#include "irc.h"
+
+void irc_init(struct chatter *chatter);
+short irc_verify_state(struct chatter *chatter, int state);
+short irc_recv(struct chatter *chatter);
+short irc_send(struct chatter *chatter);
+void irc_process_ibuf(struct chatter *chatter);
+
+void
+irc_process(struct chatter *chatter)
+{
+ if (chatter->irc_state >= IRC_STATE_CONNECTED)
+ irc_recv(chatter);
+
+ switch (chatter->irc_state) {
+ case IRC_STATE_UNINITED:
+ chatter->irc_state = IRC_STATE_CONNECTING;
+ irc_init(chatter);
+ break;
+ case IRC_STATE_LOGIN:
+ /* TODO */
+ chatter->irc_state = IRC_STATE_CONNECTED;
+ break;
+ case IRC_STATE_CONNECTED:
+ if (chatter->irc_ibuflen)
+ irc_process_ibuf(chatter);
+ break;
+ }
+}
+
+short
+irc_verify_state(struct chatter *chatter, int state)
+{
+ if (chatter->irc_state != state) {
+ warn("Bad IRC state (in %d, expected %d)", chatter->irc_state,
+ state);
+ chatter->irc_state = IRC_STATE_DISCONNECTED;
+ return 0;
+ }
+
+ return 1;
+}
+
+void
+irc_init(struct chatter *chatter)
+{
+ char ip_str[] = "255.255.255.255";
+ char *retname;
+ ip_addr ip = 0, local_ip = 0;
+ ip_port port, local_port = 0;
+ short err;
+
+ if (!irc_verify_state(chatter, IRC_STATE_CONNECTING))
+ return;
+
+ chatter_output(chatter, "*** Connecting to %s:%d...\r",
+ chatter->irc_hostname, chatter->irc_port);
+
+ if ((err = _TCPCreate(&chatter->irc_send_pb, &chatter->irc_stream,
+ (Ptr)chatter->irc_tcp_buf, sizeof(chatter->irc_tcp_buf),
+ nil, nil, nil, false)) != noErr) {
+ warn("TCPCreate failed: %d", err);
+ chatter->irc_state = IRC_STATE_DISCONNECTED;
+ return;
+ }
+
+ if ((err = TCPResolveName(&chatter->irc_hostname, &ip)) != 0) {
+ warn("Couldn't resolve host %s (%d)", chatter->irc_hostname, err);
+ chatter->irc_state = IRC_STATE_DISCONNECTED;
+ return;
+ }
+
+ long2ip(ip, ip_str);
+
+ if ((err = _TCPActiveOpen(&chatter->irc_send_pb, chatter->irc_stream,
+ ip, chatter->irc_port, &local_ip, &local_port, nil, nil,
+ false)) != noErr) {
+ warn("Failed connecting to %s (%s) port %d: %d",
+ chatter->irc_hostname, ip_str, chatter->irc_port, err);
+ chatter->irc_state = IRC_STATE_DISCONNECTED;
+ return;
+ }
+
+ chatter_output(chatter, "*** Connected to %s (%s) port %d\r",
+ chatter->irc_hostname, ip_str, chatter->irc_port);
+
+ chatter->irc_state = IRC_STATE_LOGIN;
+}
+
+void
+irc_abort(struct chatter *chatter)
+{
+ _TCPClose(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
+ false);
+ _TCPRelease(&chatter->irc_close_pb, chatter->irc_stream, nil, nil,
+ false);
+}
+
+void
+irc_close(struct chatter *chatter)
+{
+ if (chatter->irc_state == IRC_STATE_DISCONNECTED)
+ return;
+
+ chatter_output(chatter, "*** Disconnecting\r");
+ irc_abort(chatter);
+ chatter->irc_state = IRC_STATE_DISCONNECTED;
+}
+
+void
+irc_process_ibuf(struct chatter *chatter)
+{
+ size_t n;
+
+ if (chatter->irc_ibuflen == 0)
+ return;
+
+ /* get one line */
+look_for_line:
+ for (n = 0; n < chatter->irc_ibuflen - 1; n++) {
+ if (chatter->irc_ibuf[n] != '\r')
+ continue;
+ if (chatter->irc_ibuf[n + 1] != '\n')
+ continue;
+
+ chatter->irc_ibuf[n] = '\0';
+ chatter->irc_ibuf[n + 1] = '\0';
+ chatter_output(chatter, "*** %s\r", chatter->irc_ibuf);
+
+ if (n == chatter->irc_ibuflen - 2) {
+ chatter->irc_ibuflen = 0;
+ break;
+ } else {
+ memmove(chatter->irc_ibuf, chatter->irc_ibuf + n + 2,
+ chatter->irc_ibuflen - n - 2);
+ goto look_for_line;
+ }
+ }
+}
+
+short
+irc_recv(struct chatter *chatter)
+{
+ unsigned short rlen;
+ short error, n;
+
+ if (chatter->irc_ibuflen >= sizeof(chatter->irc_ibuf))
+ return 0;
+
+ if (chatter->irc_rcv_pb.ioResult > 0)
+ /* previous _TCPSend/_TCPRecv has not completed yet */
+ return 0;
+
+ error = _TCPStatus(&chatter->irc_rcv_pb, chatter->irc_stream,
+ &chatter->irc_status_pb, nil, nil, false);
+ if (error) {
+ irc_close(chatter);
+ return 0;
+ }
+
+ if (chatter->irc_status_pb.amtUnreadData == 0)
+ return 0;
+
+ rlen = chatter->irc_status_pb.amtUnreadData;
+ if (chatter->irc_ibuflen + rlen >= sizeof(chatter->irc_ibuf))
+ rlen = sizeof(chatter->irc_ibuf) - chatter->irc_ibuflen;
+
+ error = _TCPRcv(&chatter->irc_rcv_pb, chatter->irc_stream,
+ (Ptr)(chatter->irc_ibuf + chatter->irc_ibuflen), &rlen, nil, nil,
+ false);
+ if (error) {
+ chatter_output(chatter, "*** TCPRecv failed: %d\r", error);
+ irc_close(chatter);
+ return 0;
+ }
+
+ chatter->irc_ibuflen += rlen;
+ return rlen;
+}
+
+short
+irc_send(struct chatter *chatter)
+{
+ short error;
+
+ if (chatter->irc_obuflen == 0 ||
+ chatter->irc_state == IRC_STATE_DISCONNECTED)
+ return 0;
+
+ if (chatter->irc_send_pb.ioResult > 0)
+ /* previous _TCPSend has not completed yet */
+ return 0;
+
+process_result:
+ if (chatter->irc_wds[0].length) {
+ /* previous _TCPSend completed, shift out those bytes */
+ chatter->irc_obuflen -= chatter->irc_wds[0].length;
+ if (chatter->irc_obuflen < 0) {
+ warn("bogus obuflen %d", chatter->irc_obuflen);
+ chatter->irc_obuflen = 0;
+ } else if (chatter->irc_obuflen > 0)
+ memmove(chatter->irc_obuf,
+ chatter->irc_obuf + chatter->irc_wds[0].length,
+ chatter->irc_obuflen);
+
+ chatter->irc_wds[0].length = 0;
+
+ if (chatter->irc_obuflen == 0)
+ return 0;
+ }
+
+ /*
+ * _TCPSend only knows how many wds pointers were passed in when it
+ * reads the next one and its pointer is zero (or size is zero?), so
+ * even though we're only sending one wds, memory after wds[0]
+ * has to be zeroed out.
+ */
+ memset(&chatter->irc_wds, 0, sizeof(chatter->irc_wds));
+ chatter->irc_wds[0].ptr = (Ptr)&chatter->irc_obuf;
+ chatter->irc_wds[0].length = chatter->irc_obuflen;
+
+ error = _TCPSend(&chatter->irc_send_pb, chatter->irc_stream,
+ chatter->irc_wds, nil, nil, true);
+ if (error) {
+ warn("TCPSend failed: %d", error);
+ irc_close(chatter);
+ return 0;
+ }
+
+ /* if we sent quickly enough, we won't have to cycle again */
+ if (chatter->irc_send_pb.ioResult <= 0)
+ goto process_result;
+
+ return 0;
+}
+
--- irc.h Sun Jan 30 14:20:47 2022
+++ irc.h Sun Jan 30 14:20:47 2022
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __IRC_H__
+#define __IRC_H__
+
+enum {
+ IRC_STATE_UNINITED = 0,
+ IRC_STATE_DISCONNECTED,
+ IRC_STATE_CONNECTING,
+ IRC_STATE_CONNECTED,
+ IRC_STATE_LOGIN
+};
+
+void irc_process(struct chatter *chatter);
+void irc_abort(struct chatter *chatter);
+void irc_close(struct chatter *chatter);
+
+#endif
--- MacTCP.h Thu Sep 17 17:48:48 2020
+++ MacTCP.h Thu Sep 17 17:48:48 2020
@@ -0,0 +1,863 @@
+/*
+ File: MacTCP.h
+
+ Contains: TCP Manager Interfaces.
+
+ Version: Technology: MacTCP 2.0.6
+ Package: Universal Interfaces 2.1ß1 in “MPW Prerelease” on ETO #17
+
+ Copyright: © 1984-1995 by Apple Computer, Inc.
+ All rights reserved.
+
+ Bugs?: If you find a problem with this file, use the Apple Bug Reporter
+ stack. Include the file and version information (from above)
+ in the problem description and send to:
+ Internet: apple.bugs@applelink.apple.com
+ AppleLink: APPLE.BUGS
+
+*/
+
+#ifndef __MACTCP__
+#define __MACTCP__
+
+
+#ifndef __TYPES__
+#include <Types.h>
+#endif
+
+#if 0
+#ifndef __APPLETALK__
+#include <AppleTalk.h>
+#endif
+#endif
+
+enum {
+ kPascalStackBased = 0,
+ kCStackBased = 0,
+ kThinkCStackBased = 0
+};
+#define SIZE_CODE(size) (0)
+#define RESULT_SIZE(sizeCode) (0)
+#define STACK_ROUTINE_PARAMETER(whichParam, sizeCode) (0)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#if PRAGMA_ALIGN_SUPPORTED
+#pragma options align=mac68k
+#endif
+
+#if PRAGMA_IMPORT_SUPPORTED
+#pragma import on
+#endif
+
+/*
+Developer Notes:
+ 0. This MacTCP header replaces what used to be defined in the following header files
+ MacTCPCommonTypes.h
+ GetMyIPAddr.h
+ MiscIPPB.h
+ TCPPB.h
+ UDPPB.h
+
+ When the various control calls are made to the ip driver, you must set up a
+ NewRoutineDescriptor for every non-nil completion routine and/or notifyProc parameter.
+ Otherwise, the 68K driver code, will not correctly call your routine.
+ 1. For ipctlGetAddr Control calls, use NewGetIPIOCompletionProc
+ to set up a GetIPIOCompletionUPP universal procptr to pass as
+ the ioCompletion parameter.
+ 2. For the ipctlEchoICMP and ipctlLAPStats Control calls, use
+ NewIPIOCompletion to set up a IPIOCompletionUPP universal procptr
+ to pass in the ioCompletion field of the parameter block.
+ 3. For TCPCreatePB Control calls, use NewTCPNotifyProc to set up a
+ TCPNotifyUPP universal procptr to pass in the notifyProc field
+ of the parameter block
+ 4. For all of the TCP Control calls using the TCPiopb parameter block,
+ use NewTCPIOCompletionProc to set up a TCPIOCompletionUPP
+ universal procptr to pass in the ioCompletion field of the paramter
+ block.
+ 5. For UDBCreatePB Control calls, use NewUDPNotifyProc to set up a
+ UDPNotifyUPP universal procptr to pass in the notifyProc field
+ of the parameter block
+ 6. For all of the UDP Control calls using the UDPiopb parameter block,
+ use NewUDPIOCompletionProc to set up a UDPIOCompletionUPP
+ universal procptr to pass in the ioCompletion field of the paramter
+ block.
+ 7. For all calls implementing a notifyProc or ioCompletion routine
+ which was set up using a NewTCPRoutineProc call, do not call
+ DisposeRoutineSDescriptor on the universal procptr until
+ after the completion or notify proc has completed.
+*/
+/* MacTCP return Codes in the range -23000 through -23049 */
+
+enum {
+ inProgress = 1, /* I/O in progress */
+ ipBadLapErr = -23000, /* bad network configuration */
+ ipBadCnfgErr = -23001, /* bad IP configuration error */
+ ipNoCnfgErr = -23002, /* missing IP or LAP configuration error */
+ ipLoadErr = -23003, /* error in MacTCP load */
+ ipBadAddr = -23004, /* error in getting address */
+ connectionClosing = -23005, /* connection is closing */
+ invalidLength = -23006,
+ connectionExists = -23007, /* request conflicts with existing connection */
+ connectionDoesntExist = -23008, /* connection does not exist */
+ insufficientResources = -23009, /* insufficient resources to perform request */
+ invalidStreamPtr = -23010,
+ streamAlreadyOpen = -23011,
+ connectionTerminated = -23012,
+ invalidBufPtr = -23013,
+ invalidRDS = -23014,
+ invalidWDS = -23014,
+ openFailed = -23015,
+ commandTimeout = -23016,
+ duplicateSocket = -23017
+};
+
+/* Error codes from internal IP functions */
+enum {
+ ipDontFragErr = -23032, /* Packet too large to send w/o fragmenting */
+ ipDestDeadErr = -23033, /* destination not responding */
+ icmpEchoTimeoutErr = -23035, /* ICMP echo timed-out */
+ ipNoFragMemErr = -23036, /* no memory to send fragmented pkt */
+ ipRouteErr = -23037, /* can't route packet off-net */
+ nameSyntaxErr = -23041,
+ cacheFault = -23042,
+ noResultProc = -23043,
+ noNameServer = -23044,
+ authNameErr = -23045,
+ noAnsErr = -23046,
+ dnrErr = -23047,
+ outOfMemory = -23048
+};
+
+enum {
+ BYTES_16WORD = 2, /* bytes per = 16, bit ip word */
+ BYTES_32WORD = 4, /* bytes per = 32, bit ip word */
+ BYTES_64WORD = 8 /* bytes per = 64, bit ip word */
+};
+
+/* 8-bit quantity */
+typedef unsigned char UInt8;
+typedef signed char SInt8;
+typedef UInt8 b_8;
+
+/* 16-bit quantity */
+typedef unsigned short UInt16;
+typedef signed short SInt16;
+typedef UInt16 b_16;
+
+/* 32-bit quantity */
+typedef unsigned long UInt32;
+typedef signed long SInt32;
+typedef UInt32 b_32;
+
+/* IP address is 32-bits */
+typedef b_32 ip_addr;
+
+struct ip_addrbytes {
+ union {
+ b_32 addr;
+ UInt8 byte[4];
+ } a;
+};
+typedef struct ip_addrbytes ip_addrbytes;
+
+struct wdsEntry {
+ unsigned short length; /* length of buffer */
+ Ptr ptr; /* pointer to buffer */
+};
+typedef struct wdsEntry wdsEntry;
+
+struct rdsEntry {
+ unsigned short length; /* length of buffer */
+ Ptr ptr; /* pointer to buffer */
+};
+typedef struct rdsEntry rdsEntry;
+
+typedef unsigned long BufferPtr;
+
+typedef unsigned long StreamPtr;
+
+
+enum {
+ netUnreach = 0,
+ hostUnreach = 1,
+ protocolUnreach = 2,
+ portUnreach = 3,
+ fragReqd = 4,
+ sourceRouteFailed = 5,
+ timeExceeded = 6,
+ parmProblem = 7,
+ missingOption = 8,
+ lastICMPMsgType = 32767
+};
+
+typedef unsigned short ICMPMsgType;
+
+typedef b_16 ip_port;
+
+struct ICMPReport {
+ StreamPtr streamPtr;
+ ip_addr localHost;
+ ip_port localPort;
+ ip_addr remoteHost;
+ ip_port remotePort;
+ short reportType;
+ unsigned short optionalAddlInfo;
+ unsigned long optionalAddlInfoPtr;
+};
+typedef struct ICMPReport ICMPReport;
+
+/* csCode to get our IP address */
+
+enum {
+ ipctlGetAddr = 15
+};
+
+typedef void (*GetIPIOCompletionProcPtr)(struct GetAddrParamBlock *iopb);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr GetIPIOCompletionUPP;
+#else
+typedef GetIPIOCompletionProcPtr GetIPIOCompletionUPP;
+#endif
+
+#define GetIPParamBlockHeader \
+ struct QElem* qLink; \
+ short qType; \
+ short ioTrap; \
+ Ptr ioCmdAddr; \
+ GetIPIOCompletionUPP ioCompletion; \
+ OSErr ioResult; \
+ StringPtr ioNamePtr; \
+ short ioVRefNum; \
+ short ioCRefNum; \
+ short csCode
+struct GetAddrParamBlock {
+ struct QElem *qLink;
+ short qType;
+ short ioTrap;
+ Ptr ioCmdAddr;
+ GetIPIOCompletionUPP ioCompletion;
+ OSErr ioResult;
+ StringPtr ioNamePtr;
+ short ioVRefNum;
+ short ioCRefNum;
+ short csCode; /* standard I/O header */
+ ip_addr ourAddress; /* our IP address */
+ long ourNetMask; /* our IP net mask */
+};
+typedef struct GetAddrParamBlock GetAddrParamBlock;
+
+/* control codes */
+
+enum {
+ ipctlEchoICMP = 17, /* send icmp echo */
+ ipctlLAPStats = 19 /* get lap stats */
+};
+
+typedef void (*IPIOCompletionProcPtr)(struct ICMPParamBlock *iopb);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr IPIOCompletionUPP;
+#else
+typedef IPIOCompletionProcPtr IPIOCompletionUPP;
+#endif
+
+#define IPParamBlockHeader \
+ struct QElem* qLink; \
+ short qType; \
+ short ioTrap; \
+ Ptr ioCmdAddr; \
+ IPIOCompletionUPP ioCompletion; \
+ OSErr ioResult; \
+ StringPtr ioNamePtr; \
+ short ioVRefNum; \
+ short ioCRefNum; \
+ short csCode
+struct ICMPParamBlock {
+ struct QElem *qLink;
+ short qType;
+ short ioTrap;
+ Ptr ioCmdAddr;
+ IPIOCompletionUPP ioCompletion;
+ OSErr ioResult;
+ StringPtr ioNamePtr;
+ short ioVRefNum;
+ short ioCRefNum;
+ short csCode; /* standard I/O header */
+ short params[11];
+ struct {
+ unsigned long echoRequestOut; /* time in ticks of when the echo request went out */
+ unsigned long echoReplyIn; /* time in ticks of when the reply was received */
+ struct rdsEntry echoedData; /* data received in responce */
+ Ptr options;
+ unsigned long userDataPtr;
+ } icmpEchoInfo;
+};
+typedef pascal void (*ICMPEchoNotifyProcPtr)(struct ICMPParamBlock *iopb);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr ICMPEchoNotifyUPP;
+#else
+typedef ICMPEchoNotifyProcPtr ICMPEchoNotifyUPP;
+#endif
+
+struct IPParamBlock {
+ struct QElem *qLink;
+ short qType;
+ short ioTrap;
+ Ptr ioCmdAddr;
+ IPIOCompletionUPP ioCompletion;
+ OSErr ioResult;
+ StringPtr ioNamePtr;
+ short ioVRefNum;
+ short ioCRefNum;
+ short csCode; /* standard I/O header */
+ union {
+ struct {
+ ip_addr dest; /* echo to IP address */
+ wdsEntry data;
+ short timeout;
+ Ptr options;
+ unsigned short optLength;
+ ICMPEchoNotifyUPP icmpCompletion;
+ unsigned long userDataPtr;
+ } IPEchoPB;
+ struct {
+ struct LAPStats *lapStatsPtr;
+ } LAPStatsPB;
+ } csParam;
+};
+union LAPStatsAddrXlation {
+ struct arp_entry *arp_table;
+ struct nbp_entry *nbp_table;
+};
+struct LAPStats {
+ short ifType;
+ char *ifString;
+ short ifMaxMTU;
+ long ifSpeed;
+ short ifPhyAddrLength;
+ char *ifPhysicalAddress;
+ union LAPStatsAddrXlation AddrXlation;
+ short slotNumber;
+};
+typedef struct LAPStats LAPStats;
+
+#ifdef __APPLETALK__
+struct nbp_entry {
+ ip_addr ip_address; /* IP address */
+ AddrBlock at_address; /* matching AppleTalk address */
+ Boolean gateway; /* TRUE if entry for a gateway */
+ Boolean valid; /* TRUE if LAP address is valid */
+ Boolean probing; /* TRUE if NBP lookup pending */
+ SInt8 afiller; /* Filler for proper byte alignment */
+ long age; /* ticks since cache entry verified */
+ long access; /* ticks since last access */
+ SInt8 filler[116]; /* for internal use only !!! */
+};
+#endif
+struct Enet_addr {
+ b_16 en_hi;
+ b_32 en_lo;
+};
+typedef struct Enet_addr Enet_addr;
+
+struct arp_entry {
+ short age; /* cache aging field */
+ b_16 protocol; /* Protocol type */
+ ip_addr ip_address; /* IP address */
+ Enet_addr en_address; /* matching Ethernet address */
+};
+typedef struct arp_entry arp_entry;
+
+/* number of ARP table entries */
+
+enum {
+ ARP_TABLE_SIZE = 20
+};
+
+enum {
+ NBP_TABLE_SIZE = 20, /* number of NBP table entries */
+ NBP_MAX_NAME_SIZE = 16 + 10 + 2
+};
+
+/* Command codes */
+enum {
+ TCPCreate = 30,
+ TCPPassiveOpen = 31,
+ TCPActiveOpen = 32,
+ TCPSend = 34,
+ TCPNoCopyRcv = 35,
+ TCPRcvBfrReturn = 36,
+ TCPRcv = 37,
+ TCPClose = 38,
+ TCPAbort = 39,
+ TCPStatus = 40,
+ TCPExtendedStat = 41,
+ TCPRelease = 42,
+ TCPGlobalInfo = 43,
+ TCPCtlMax = 49
+};
+
+enum {
+ TCPClosing = 1,
+ TCPULPTimeout = 2,
+ TCPTerminate = 3,
+ TCPDataArrival = 4,
+ TCPUrgent = 5,
+ TCPICMPReceived = 6,
+ lastEvent = 32767
+};
+
+typedef unsigned short TCPEventCode;
+
+
+enum {
+ TCPRemoteAbort = 2,
+ TCPNetworkFailure = 3,
+ TCPSecPrecMismatch = 4,
+ TCPULPTimeoutTerminate = 5,
+ TCPULPAbort = 6,
+ TCPULPClose = 7,
+ TCPServiceError = 8,
+ lastReason = 32767
+};
+
+typedef unsigned short TCPTerminationReason;
+
+typedef pascal void (*TCPNotifyProcPtr)(StreamPtr tcpStream, unsigned short eventCode, Ptr userDataPtr, unsigned short terminReason, struct ICMPReport *icmpMsg);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr TCPNotifyUPP;
+#else
+typedef TCPNotifyProcPtr TCPNotifyUPP;
+#endif
+
+typedef unsigned short tcp_port;
+
+/* ValidityFlags */
+
+enum {
+ timeoutValue = 0x80,
+ timeoutAction = 0x40,
+ typeOfService = 0x20,
+ precedence = 0x10
+};
+
+/* TOSFlags */
+enum {
+ lowDelay = 0x01,
+ throughPut = 0x02,
+ reliability = 0x04
+};
+
+struct TCPCreatePB {
+ Ptr rcvBuff;
+ unsigned long rcvBuffLen;
+ TCPNotifyUPP notifyProc;
+ Ptr userDataPtr;
+};
+typedef struct TCPCreatePB TCPCreatePB;
+
+struct TCPOpenPB {
+ SInt8 ulpTimeoutValue;
+ SInt8 ulpTimeoutAction;
+ SInt8 validityFlags;
+ SInt8 commandTimeoutValue;
+ ip_addr remoteHost;
+ tcp_port remotePort;
+ ip_addr localHost;
+ tcp_port localPort;
+ SInt8 tosFlags;
+ SInt8 precedence;
+ Boolean dontFrag;
+ SInt8 timeToLive;
+ SInt8 security;
+ SInt8 optionCnt;
+ SInt8 options[40];
+ Ptr userDataPtr;
+};
+typedef struct TCPOpenPB TCPOpenPB;
+
+struct TCPSendPB {
+ SInt8 ulpTimeoutValue;
+ SInt8 ulpTimeoutAction;
+ SInt8 validityFlags;
+ Boolean pushFlag;
+ Boolean urgentFlag;
+ SInt8 filler; /* Filler for proper byte alignment */
+ Ptr wdsPtr;
+ unsigned long sendFree;
+ unsigned short sendLength;
+ Ptr userDataPtr;
+};
+typedef struct TCPSendPB TCPSendPB;
+
+/* for receive and return rcv buff calls */
+/* Note: the filler in the following structure is in a different location than */
+/* that specified in the Programmer's Guide. */
+struct TCPReceivePB {
+ SInt8 commandTimeoutValue;
+ Boolean markFlag;
+ Boolean urgentFlag;
+ SInt8 filler; /* Filler for proper byte alignment */
+ Ptr rcvBuff;
+ unsigned short rcvBuffLen;
+ Ptr rdsPtr;
+ unsigned short rdsLength;
+ unsigned short secondTimeStamp;
+ Ptr userDataPtr;
+};
+typedef struct TCPReceivePB TCPReceivePB;
+
+struct TCPClosePB {
+ SInt8 ulpTimeoutValue;
+ SInt8 ulpTimeoutAction;
+ SInt8 validityFlags;
+ SInt8 filler; /* Filler for proper byte alignment */
+ Ptr userDataPtr;
+};
+typedef struct TCPClosePB TCPClosePB;
+
+struct HistoBucket {
+ unsigned short value;
+ unsigned long counter;
+};
+typedef struct HistoBucket HistoBucket;
+
+
+enum {
+ NumOfHistoBuckets = 7
+};
+
+struct TCPConnectionStats {
+ unsigned long dataPktsRcvd;
+ unsigned long dataPktsSent;
+ unsigned long dataPktsResent;
+ unsigned long bytesRcvd;
+ unsigned long bytesRcvdDup;
+ unsigned long bytesRcvdPastWindow;
+ unsigned long bytesSent;
+ unsigned long bytesResent;
+ unsigned short numHistoBuckets;
+ struct HistoBucket sentSizeHisto[NumOfHistoBuckets];
+ unsigned short lastRTT;
+ unsigned short tmrSRTT;
+ unsigned short rttVariance;
+ unsigned short tmrRTO;
+ SInt8 sendTries;
+ SInt8 sourchQuenchRcvd;
+};
+typedef struct TCPConnectionStats TCPConnectionStats;
+
+struct TCPStatusPB {
+ SInt8 ulpTimeoutValue;
+ SInt8 ulpTimeoutAction;
+ long unused;
+ ip_addr remoteHost;
+ tcp_port remotePort;
+ ip_addr localHost;
+ tcp_port localPort;
+ SInt8 tosFlags;
+ SInt8 precedence;
+ SInt8 connectionState;
+ SInt8 filler; /* Filler for proper byte alignment */
+ unsigned short sendWindow;
+ unsigned short rcvWindow;
+ unsigned short amtUnackedData;
+ unsigned short amtUnreadData;
+ Ptr securityLevelPtr;
+ unsigned long sendUnacked;
+ unsigned long sendNext;
+ unsigned long congestionWindow;
+ unsigned long rcvNext;
+ unsigned long srtt;
+ unsigned long lastRTT;
+ unsigned long sendMaxSegSize;
+ struct TCPConnectionStats *connStatPtr;
+ Ptr userDataPtr;
+};
+typedef struct TCPStatusPB TCPStatusPB;
+
+struct TCPAbortPB {
+ Ptr userDataPtr;
+};
+typedef struct TCPAbortPB TCPAbortPB;
+
+struct TCPParam {
+ unsigned long tcpRtoA;
+ unsigned long tcpRtoMin;
+ unsigned long tcpRtoMax;
+ unsigned long tcpMaxSegSize;
+ unsigned long tcpMaxConn;
+ unsigned long tcpMaxWindow;
+};
+typedef struct TCPParam TCPParam;
+
+struct TCPStats {
+ unsigned long tcpConnAttempts;
+ unsigned long tcpConnOpened;
+ unsigned long tcpConnAccepted;
+ unsigned long tcpConnClosed;
+ unsigned long tcpConnAborted;
+ unsigned long tcpOctetsIn;
+ unsigned long tcpOctetsOut;
+ unsigned long tcpOctetsInDup;
+ unsigned long tcpOctetsRetrans;
+ unsigned long tcpInputPkts;
+ unsigned long tcpOutputPkts;
+ unsigned long tcpDupPkts;
+ unsigned long tcpRetransPkts;
+};
+typedef struct TCPStats TCPStats;
+
+typedef StreamPtr *StreamPPtr;
+
+struct TCPGlobalInfoPB {
+ struct TCPParam *tcpParamPtr;
+ struct TCPStats *tcpStatsPtr;
+ StreamPPtr tcpCDBTable[1];
+ Ptr userDataPtr;
+ unsigned short maxTCPConnections;
+};
+typedef struct TCPGlobalInfoPB TCPGlobalInfoPB;
+
+typedef void (*TCPIOCompletionProcPtr)(struct TCPiopb *iopb);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr TCPIOCompletionUPP;
+#else
+typedef TCPIOCompletionProcPtr TCPIOCompletionUPP;
+#endif
+
+struct TCPiopb {
+ SInt8 fill12[12];
+ TCPIOCompletionUPP ioCompletion;
+ short ioResult;
+ Ptr ioNamePtr;
+ short ioVRefNum;
+ short ioCRefNum;
+ short csCode;
+ StreamPtr tcpStream;
+ union {
+ struct TCPCreatePB create;
+ struct TCPOpenPB open;
+ struct TCPSendPB send;
+ struct TCPReceivePB receive;
+ struct TCPClosePB close;
+ struct TCPAbortPB abort;
+ struct TCPStatusPB status;
+ struct TCPGlobalInfoPB globalInfo;
+ } csParam;
+};
+typedef struct TCPiopb TCPiopb;
+
+
+enum {
+ UDPCreate = 20,
+ UDPRead = 21,
+ UDPBfrReturn = 22,
+ UDPWrite = 23,
+ UDPRelease = 24,
+ UDPMaxMTUSize = 25,
+ UDPStatus = 26,
+ UDPMultiCreate = 27,
+ UDPMultiSend = 28,
+ UDPMultiRead = 29,
+ UDPCtlMax = 29
+};
+
+enum {
+ UDPDataArrival = 1,
+ UDPICMPReceived = 2,
+ lastUDPEvent = 32767
+};
+
+typedef unsigned short UDPEventCode;
+
+typedef pascal void (*UDPNotifyProcPtr)(StreamPtr udpStream, unsigned short eventCode, Ptr userDataPtr, struct ICMPReport *icmpMsg);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr UDPNotifyUPP;
+#else
+typedef UDPNotifyProcPtr UDPNotifyUPP;
+#endif
+
+typedef unsigned short udp_port;
+
+/* for create and release calls */
+struct UDPCreatePB {
+ Ptr rcvBuff;
+ unsigned long rcvBuffLen;
+ UDPNotifyUPP notifyProc;
+ unsigned short localPort;
+ Ptr userDataPtr;
+ udp_port endingPort;
+};
+typedef struct UDPCreatePB UDPCreatePB;
+
+struct UDPSendPB {
+ unsigned short reserved;
+ ip_addr remoteHost;
+ udp_port remotePort;
+ Ptr wdsPtr;
+ Boolean checkSum;
+ SInt8 filler; /* Filler for proper byte alignment */
+ unsigned short sendLength;
+ Ptr userDataPtr;
+ udp_port localPort;
+};
+typedef struct UDPSendPB UDPSendPB;
+
+/* for receive and buffer return calls */
+struct UDPReceivePB {
+ unsigned short timeOut;
+ ip_addr remoteHost;
+ udp_port remotePort;
+ Ptr rcvBuff;
+ unsigned short rcvBuffLen;
+ unsigned short secondTimeStamp;
+ Ptr userDataPtr;
+ ip_addr destHost; /* only for use with multi rcv */
+ udp_port destPort; /* only for use with multi rcv */
+};
+typedef struct UDPReceivePB UDPReceivePB;
+
+struct UDPMTUPB {
+ unsigned short mtuSize;
+ ip_addr remoteHost;
+ Ptr userDataPtr;
+};
+typedef struct UDPMTUPB UDPMTUPB;
+
+typedef void (*UDPIOCompletionProcPtr)(struct UDPiopb *iopb);
+
+#if GENERATINGCFM
+typedef UniversalProcPtr UDPIOCompletionUPP;
+#else
+typedef UDPIOCompletionProcPtr UDPIOCompletionUPP;
+#endif
+
+struct UDPiopb {
+ SInt8 fill12[12];
+ UDPIOCompletionUPP ioCompletion;
+ short ioResult;
+ Ptr ioNamePtr;
+ short ioVRefNum;
+ short ioCRefNum;
+ short csCode;
+ StreamPtr udpStream;
+ union {
+ struct UDPCreatePB create;
+ struct UDPSendPB send;
+ struct UDPReceivePB receive;
+ struct UDPMTUPB mtu;
+ } csParam;
+};
+typedef struct UDPiopb UDPiopb;
+
+enum {
+ uppGetIPIOCompletionProcInfo = kCStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct GetAddrParamBlock*))),
+ uppIPIOCompletionProcInfo = kCStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct ICMPParamBlock*))),
+ uppICMPEchoNotifyProcInfo = kPascalStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct ICMPParamBlock*))),
+ uppTCPNotifyProcInfo = kPascalStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(StreamPtr)))
+ | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(unsigned short)))
+ | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Ptr)))
+ | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(unsigned short)))
+ | STACK_ROUTINE_PARAMETER(5, SIZE_CODE(sizeof(struct ICMPReport*))),
+ uppTCPIOCompletionProcInfo = kCStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct TCPiopb*))),
+ uppUDPNotifyProcInfo = kPascalStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(StreamPtr)))
+ | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(unsigned short)))
+ | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(Ptr)))
+ | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(struct ICMPReport*))),
+ uppUDPIOCompletionProcInfo = kCStackBased
+ | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(struct UDPiopb*)))
+};
+
+#if GENERATINGCFM
+#define NewGetIPIOCompletionProc(userRoutine) \
+ (GetIPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppGetIPIOCompletionProcInfo, GetCurrentArchitecture())
+#define NewIPIOCompletionProc(userRoutine) \
+ (IPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppIPIOCompletionProcInfo, GetCurrentArchitecture())
+#define NewICMPEchoNotifyProc(userRoutine) \
+ (ICMPEchoNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppICMPEchoNotifyProcInfo, GetCurrentArchitecture())
+#define NewTCPNotifyProc(userRoutine) \
+ (TCPNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppTCPNotifyProcInfo, GetCurrentArchitecture())
+#define NewTCPIOCompletionProc(userRoutine) \
+ (TCPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppTCPIOCompletionProcInfo, GetCurrentArchitecture())
+#define NewUDPNotifyProc(userRoutine) \
+ (UDPNotifyUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppUDPNotifyProcInfo, GetCurrentArchitecture())
+#define NewUDPIOCompletionProc(userRoutine) \
+ (UDPIOCompletionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), uppUDPIOCompletionProcInfo, GetCurrentArchitecture())
+#else
+#define NewGetIPIOCompletionProc(userRoutine) \
+ ((GetIPIOCompletionUPP) (userRoutine))
+#define NewIPIOCompletionProc(userRoutine) \
+ ((IPIOCompletionUPP) (userRoutine))
+#define NewICMPEchoNotifyProc(userRoutine) \
+ ((ICMPEchoNotifyUPP) (userRoutine))
+#define NewTCPNotifyProc(userRoutine) \
+ ((TCPNotifyUPP) (userRoutine))
+#define NewTCPIOCompletionProc(userRoutine) \
+ ((TCPIOCompletionUPP) (userRoutine))
+#define NewUDPNotifyProc(userRoutine) \
+ ((UDPNotifyUPP) (userRoutine))
+#define NewUDPIOCompletionProc(userRoutine) \
+ ((UDPIOCompletionUPP) (userRoutine))
+#endif
+
+#if GENERATINGCFM
+#define CallGetIPIOCompletionProc(userRoutine, iopb) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppGetIPIOCompletionProcInfo, (iopb))
+#define CallIPIOCompletionProc(userRoutine, iopb) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppIPIOCompletionProcInfo, (iopb))
+#define CallICMPEchoNotifyProc(userRoutine, iopb) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppICMPEchoNotifyProcInfo, (iopb))
+#define CallTCPNotifyProc(userRoutine, tcpStream, eventCode, userDataPtr, terminReason, icmpMsg) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppTCPNotifyProcInfo, (tcpStream), (eventCode), (userDataPtr), (terminReason), (icmpMsg))
+#define CallTCPIOCompletionProc(userRoutine, iopb) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppTCPIOCompletionProcInfo, (iopb))
+#define CallUDPNotifyProc(userRoutine, udpStream, eventCode, userDataPtr, icmpMsg) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppUDPNotifyProcInfo, (udpStream), (eventCode), (userDataPtr), (icmpMsg))
+#define CallUDPIOCompletionProc(userRoutine, iopb) \
+ CallUniversalProc((UniversalProcPtr)(userRoutine), uppUDPIOCompletionProcInfo, (iopb))
+#else
+#define CallGetIPIOCompletionProc(userRoutine, iopb) \
+ (*(userRoutine))((iopb))
+#define CallIPIOCompletionProc(userRoutine, iopb) \
+ (*(userRoutine))((iopb))
+#define CallICMPEchoNotifyProc(userRoutine, iopb) \
+ (*(userRoutine))((iopb))
+#define CallTCPNotifyProc(userRoutine, tcpStream, eventCode, userDataPtr, terminReason, icmpMsg) \
+ (*(userRoutine))((tcpStream), (eventCode), (userDataPtr), (terminReason), (icmpMsg))
+#define CallTCPIOCompletionProc(userRoutine, iopb) \
+ (*(userRoutine))((iopb))
+#define CallUDPNotifyProc(userRoutine, udpStream, eventCode, userDataPtr, icmpMsg) \
+ (*(userRoutine))((udpStream), (eventCode), (userDataPtr), (icmpMsg))
+#define CallUDPIOCompletionProc(userRoutine, iopb) \
+ (*(userRoutine))((iopb))
+#endif
+
+
+#if PRAGMA_IMPORT_SUPPORTED
+#pragma import off
+#endif
+
+#if PRAGMA_ALIGN_SUPPORTED
+#pragma options align=reset
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MACTCP__ */
--- main.c Sun Jan 30 14:13:53 2022
+++ main.c Sun Jan 30 14:13:53 2022
@@ -0,0 +1,341 @@
+/*
+ * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <string.h>
+#include "chatter.h"
+#include "irc.h"
+#include "tcp.h"
+#include "util.h"
+
+#define MBAR_ID 128
+#define APPLE_MENU_ID 128
+#define APPLE_MENU_ABOUT_ID 1
+#define FILE_MENU_ID 129
+#define FILE_MENU_CONNECT_ID 1
+#define FILE_MENU_QUIT_ID 3
+#define EDIT_MENU_ID 130
+
+#define SOCKS_PROXY_IP_ID 1005
+#define SOCKS_PROXY_PORT_ID 1006
+
+#define CONNECT_DLOG_ID 129
+#define CONNECT_SERVER_ID 2
+#define CONNECT_PORT_ID 3
+#define CONNECT_NICK_ID 4
+
+MenuHandle apple_menu, file_menu;
+struct focusable *cur_focusable = NULL;
+struct focusable *last_focusable = NULL;
+ip_addr socks_proxy_ip = 0;
+ip_port socks_proxy_port = 0;
+
+void update_menu(void);
+void handle_exit(void);
+short handle_menu(long menu_id);
+short show_connect_dialog(void);
+
+int
+main(void)
+{
+ Handle mbar;
+ EventRecord event;
+ WindowPtr event_win;
+ GrafPtr old_port;
+ AppFile finder_file;
+ short event_in, n;
+ char key, *socks_proxy_host;
+
+ InitGraf(&thePort);
+ InitFonts();
+ FlushEvents(everyEvent, 0);
+ InitWindows();
+ InitMenus();
+ TEInit();
+ InitDialogs(0);
+ InitCursor();
+ MaxApplZone();
+
+ _atexit(handle_exit);
+
+ if (!(mbar = GetNewMBar(MBAR_ID)))
+ panic("no mbar");
+ SetMenuBar(mbar);
+ if (!(apple_menu = GetMHandle(APPLE_MENU_ID)))
+ panic("no apple menu");
+ AddResMenu(apple_menu, 'DRVR');
+ if (!(file_menu = GetMHandle(FILE_MENU_ID)))
+ panic("no file menu");
+ update_menu();
+ DrawMenuBar();
+
+ socks_proxy_host = xGetStringAsChar(SOCKS_PROXY_IP_ID);
+ if (socks_proxy_host[0] != '\0')
+ socks_proxy_ip = ip2long(socks_proxy_host);
+ free(socks_proxy_host);
+ socks_proxy_port = (ip_port)xGetStringAsLong(SOCKS_PROXY_PORT_ID);
+
+chatter_init("irc.libera.chat", 6667, "jcs[mac]");
+
+ for (;;) {
+ WaitNextEvent(everyEvent, &event, 5L, 0L);
+
+ switch (event.what) {
+ case nullEvent:
+ /* TODO: idle all chatters, not just current */
+ if (cur_focusable && cur_focusable->idle)
+ cur_focusable->idle(cur_focusable, &event);
+ break;
+ case keyDown:
+ case autoKey:
+ key = event.message & charCodeMask;
+ if ((event.modifiers & cmdKey) != 0 &&
+ handle_menu(MenuKey(key)) == 1)
+ break;
+ if (cur_focusable && cur_focusable->key_down)
+ cur_focusable->key_down(cur_focusable, &event);
+ break;
+ case mouseDown:
+ event_in = FindWindow(event.where, &event_win);
+
+ switch (event_in) {
+ case inMenuBar:
+ handle_menu(MenuSelect(event.where));
+ break;
+ case inSysWindow:
+ SystemClick(&event, event_win);
+ break;
+ case inDrag:
+ DragWindow(event_win, event.where, &screenBits.bounds);
+ break;
+ case inGoAway:
+ if (TrackGoAway(event_win, event.where) && cur_focusable &&
+ cur_focusable->close)
+ cur_focusable->close(cur_focusable, &event);
+ break;
+ case inContent:
+ if (event_win != FrontWindow()) {
+ SelectWindow(event_win);
+ if (last_focusable &&
+ last_focusable->win == event_win) {
+ struct focusable *fcur;
+ fcur = cur_focusable;
+ cur_focusable = last_focusable;
+ last_focusable = fcur;
+ }
+ }
+ if (cur_focusable && cur_focusable->win == event_win &&
+ cur_focusable->mouse_down)
+ cur_focusable->mouse_down(cur_focusable, &event);
+ break;
+ }
+ break;
+ case updateEvt:
+ case activateEvt:
+ event_win = (WindowPtr)event.message;
+
+ if (event.what == updateEvt) {
+ GetPort(&old_port);
+ SetPort(event_win);
+ BeginUpdate(event_win);
+ }
+
+ if (cur_focusable && cur_focusable->update)
+ cur_focusable->update(cur_focusable, &event);
+
+ if (event.what == updateEvt) {
+ EndUpdate(event_win);
+ SetPort(old_port);
+ }
+ break;
+ case app4Evt:
+ if (HiWord(event.message) & (1 << 8)) {
+ /* multifinder suspend/resume */
+ switch (event.message & (1 << 0)) {
+ case 0:
+ /* suspend */
+ if (cur_focusable && cur_focusable->suspend)
+ cur_focusable->suspend(cur_focusable, &event);
+ break;
+ case 1:
+ /* resume */
+ if (cur_focusable && cur_focusable->resume)
+ cur_focusable->resume(cur_focusable, &event);
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+}
+
+short
+show_connect_dialog(void)
+{
+ Str255 server, nick, ports;
+ DialogPtr dlg;
+ bool done = false, connect = false;
+ short hit, itype;
+ long port;
+ Handle ihandle;
+ Rect irect;
+
+ if ((dlg = GetNewDialog(CONNECT_DLOG_ID, nil, (WindowPtr)-1)) == NULL)
+ panic("Can't find connection DLOG %d", CONNECT_DLOG_ID);
+
+ ShowWindow(dlg);
+
+ while (!done) {
+get_input:
+ ModalDialog(ModalDialogFilter, &hit);
+ switch (hit) {
+ case -1:
+ done = true;
+ break;
+ case OK:
+ done = true;
+ connect = true;
+ break;
+ }
+
+ if (connect) {
+ GetDItem(dlg, CONNECT_SERVER_ID, &itype, &ihandle, &irect);
+ GetIText(ihandle, server);
+ PtoCstr(server);
+ if (server[0] == '\0') {
+ warn("Server name cannot be blank");
+ connect = false;
+ continue;
+ }
+
+ GetDItem(dlg, CONNECT_NICK_ID, &itype, &ihandle, &irect);
+ GetIText(ihandle, nick);
+ PtoCstr(nick);
+ if (nick[0] == '\0') {
+ warn("Nick cannot be blank");
+ connect = false;
+ continue;
+ }
+
+ GetDItem(dlg, CONNECT_PORT_ID, &itype, &ihandle, &irect);
+ GetIText(ihandle, ports);
+ PtoCstr(ports);
+ if (sscanf((char *)ports, "%d", &port) != 1 ||
+ port < 1 || port > 65535) {
+ warn("Port must be between 1-65535");
+ connect = false;
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ DisposeDialog(dlg);
+
+ if (connect)
+ chatter_init((char *)&server, port, (char *)&nick);
+}
+
+short
+handle_menu(long menu_id)
+{
+ short ret = 0;
+
+ switch (HiWord(menu_id)) {
+ case APPLE_MENU_ID:
+ switch (LoWord(menu_id)) {
+ case APPLE_MENU_ABOUT_ID: {
+ VersRecHndl vers;
+ char vers_s[255];
+ char short_vers[255] = { 0 };
+ short vlen;
+
+ if ((vers = (VersRecHndl)GetResource('vers', 1))) {
+ /*
+ * vers "long version string" is a pascal string after the
+ * short version pascal string
+ */
+ HLock(vers);
+ vlen = (*vers)->shortVersion[0];
+ memcpy(short_vers, (*vers)->shortVersion + vlen + 1,
+ sizeof((*vers)->shortVersion) - vlen - 1);
+ PtoCstr(short_vers);
+ sprintf(vers_s, "%s %s", PROGRAM_NAME, short_vers);
+ ReleaseResource(vers);
+ note("%s", vers_s);
+ } else
+ warnx("Can't find version number!");
+
+ ret = 1;
+ break;
+ }
+ }
+ break;
+ case FILE_MENU_ID:
+ switch (LoWord(menu_id)) {
+ case FILE_MENU_CONNECT_ID:
+ show_connect_dialog();
+ break;
+ case FILE_MENU_QUIT_ID:
+ exit(0);
+ }
+ break;
+ case EDIT_MENU_ID:
+ /* let each focusable handle this */
+ break;
+ }
+
+ HiliteMenu(0);
+ return ret;
+}
+
+void
+update_menu(void)
+{
+}
+
+void
+handle_exit(void)
+{
+ /* TODO: close all chatters, not just current */
+ if (cur_focusable)
+ cur_focusable->atexit(cur_focusable);
+}
+
+void
+show_focusable(struct focusable *focusable)
+{
+ if (cur_focusable)
+ last_focusable = cur_focusable;
+ cur_focusable = focusable;
+
+ ShowWindow(focusable->win);
+}
+
+void
+close_focusable(struct focusable *focusable)
+{
+ if (cur_focusable == focusable) {
+ cur_focusable = NULL;
+ if (last_focusable)
+ cur_focusable = last_focusable;
+ } else if (last_focusable == focusable)
+ last_focusable = NULL;
+
+ CloseWindow(focusable->win);
+}
--- tcp.c Tue Oct 5 00:19:37 2021
+++ tcp.c Tue Oct 5 00:19:37 2021
@@ -0,0 +1,528 @@
+#include <stdio.h>
+#include <string.h>
+#include "tcp.h"
+#include "util.h"
+
+#define RCV_BUFFER_SIZE 1024
+#define TCP_BUFFER_SIZE 8192
+#define OPEN_TIMEOUT 60
+
+short gIPPDriverRefNum;
+
+pascal void StrToAddrMarkDone(struct hostInfo *hi, char *data);
+
+unsigned long resolved_ip;
+
+OSErr
+_TCPInit(void)
+{
+ ParamBlockRec pb;
+ OSErr osErr;
+
+ memset(&pb, 0, sizeof(pb));
+
+ gIPPDriverRefNum = -1;
+
+ pb.ioParam.ioCompletion = 0;
+ pb.ioParam.ioNamePtr = "\p.IPP";
+ pb.ioParam.ioPermssn = fsCurPerm;
+
+ osErr = PBOpen(&pb, false);
+ if (noErr == osErr) {
+ gIPPDriverRefNum = pb.ioParam.ioRefNum;
+ }
+
+ return osErr;
+}
+
+OSErr
+_TCPGetOurIP(ip_addr *ip, long *netMask)
+{
+ OSErr osErr;
+ GetAddrParamBlock pb;
+ int i;
+
+ memset(&pb, 0, sizeof(pb));
+
+ pb.csCode = ipctlGetAddr;
+ pb.ioCRefNum = gIPPDriverRefNum;
+
+ osErr = PBControl((ParmBlkPtr)&pb, true);
+ while (pb.ioResult > 0)
+ ;
+
+ if (pb.ioResult != noErr)
+ return pb.ioResult;
+
+ if (ip != NULL)
+ *ip = pb.ourAddress;
+ if (netMask != NULL)
+ *netMask = pb.ourNetMask;
+
+ return osErr;
+}
+
+OSErr
+_TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr, long rcvBufLen,
+ TCPNotifyProc aNotifyProc, Ptr userDataPtr,
+ TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPCreate;
+
+ pb->csParam.create.rcvBuff = rcvBufPtr;
+ pb->csParam.create.rcvBuffLen = rcvBufLen;
+ pb->csParam.create.notifyProc = aNotifyProc;
+ pb->csParam.create.userDataPtr = userDataPtr;
+
+ osErr = PBControl((ParmBlkPtr)pb, async);
+ if (!async && (noErr == osErr)) {
+ *stream = pb->tcpStream;
+ }
+
+ return osErr;
+}
+
+/* listen for an incoming connection */
+OSErr
+_TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP,
+ tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+ short index;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPPassiveOpen;
+ pb->tcpStream = stream;
+
+ pb->csParam.open.ulpTimeoutValue = 0;
+ pb->csParam.open.ulpTimeoutAction = 1;
+ pb->csParam.open.validityFlags = 0xC0;
+ pb->csParam.open.commandTimeoutValue = 30;
+ pb->csParam.open.remoteHost = *remoteIP;
+ pb->csParam.open.remotePort = *remotePort;
+ pb->csParam.open.localHost = 0;
+ pb->csParam.open.localPort = *localPort;
+ pb->csParam.open.tosFlags = 0;
+ pb->csParam.open.precedence = 0;
+ pb->csParam.open.dontFrag = 0;
+ pb->csParam.open.timeToLive = 0;
+ pb->csParam.open.security = 0;
+ pb->csParam.open.optionCnt = 0;
+ for (index = 0; index < sizeof(pb->csParam.open.options); ++index)
+ pb->csParam.open.options[index] = 0;
+ pb->csParam.open.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr) pb, async);
+ if (!async && (noErr == osErr)) {
+ *remoteIP = pb->csParam.open.remoteHost;
+ *remotePort = pb->csParam.open.remotePort;
+ *localIP = pb->csParam.open.localHost;
+ *localPort = pb->csParam.open.localPort;
+ }
+
+ return osErr;
+}
+
+/* make an outgoing connection */
+OSErr
+_TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP,
+ tcp_port remotePort, ip_addr *localIP, tcp_port *localPort,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+ short index;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPActiveOpen;
+ pb->tcpStream = stream;
+
+ pb->csParam.open.ulpTimeoutValue = 30;
+ pb->csParam.open.ulpTimeoutAction = 1;
+ pb->csParam.open.validityFlags = 0xC0;
+#if 0
+ /* not available with this csCode */
+ pb->csParam.open.commandTimeoutValue = 30;
+#endif
+ pb->csParam.open.remoteHost = remoteIP;
+ pb->csParam.open.remotePort = remotePort;
+ pb->csParam.open.localHost = 0;
+ pb->csParam.open.localPort = *localPort;
+ pb->csParam.open.tosFlags = 0;
+ pb->csParam.open.precedence = 0;
+ pb->csParam.open.dontFrag = 0;
+ pb->csParam.open.timeToLive = 0;
+ pb->csParam.open.security = 0;
+ pb->csParam.open.optionCnt = 0;
+ for (index = 0; index < sizeof(pb->csParam.open.options); ++index)
+ pb->csParam.open.options[index] = 0;
+ pb->csParam.open.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr) pb, async);
+ if (!async && (noErr == osErr)) {
+ *localIP = pb->csParam.open.localHost;
+ *localPort = pb->csParam.open.localPort;
+ }
+
+ return osErr;
+}
+
+OSErr
+_TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPSend;
+ pb->tcpStream = stream;
+
+ pb->csParam.send.ulpTimeoutValue = 30;
+ pb->csParam.send.ulpTimeoutAction = 1;
+ pb->csParam.send.validityFlags = 0xC0;
+ pb->csParam.send.pushFlag = 1; /* XXX */
+ pb->csParam.send.urgentFlag = 0;
+ pb->csParam.send.wdsPtr = (Ptr)wdsPtr;
+ pb->csParam.send.sendFree = 0;
+ pb->csParam.send.sendLength = 0;
+ pb->csParam.send.userDataPtr = userData;
+
+ return PBControl((ParmBlkPtr)pb, async);
+}
+
+OSErr
+_TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr,
+ unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion,
+ Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPNoCopyRcv;
+ pb->tcpStream = stream;
+
+ pb->csParam.receive.commandTimeoutValue = 30;
+ pb->csParam.receive.urgentFlag = 0;
+ pb->csParam.receive.markFlag = 0;
+ pb->csParam.receive.rdsPtr = rdsPtr;
+ pb->csParam.receive.rdsLength = rdsLength;
+ pb->csParam.receive.userDataPtr = userData;
+
+ return PBControl((ParmBlkPtr)pb, async);
+}
+
+OSErr
+_TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr,
+ unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion,
+ Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPRcv;
+ pb->tcpStream = stream;
+
+ pb->csParam.receive.commandTimeoutValue = 30;
+ pb->csParam.receive.urgentFlag = 0;
+ pb->csParam.receive.markFlag = 0;
+ pb->csParam.receive.rcvBuff = rcvBufPtr;
+ pb->csParam.receive.rcvBuffLen = *rcvBufLen;
+ pb->csParam.receive.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr)pb, async);
+ if (!async)
+ *rcvBufLen = pb->csParam.receive.rcvBuffLen;
+
+ return osErr;
+}
+
+OSErr
+_TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPRcvBfrReturn;
+ pb->tcpStream = stream;
+
+ pb->csParam.receive.rdsPtr = rdsPtr;
+ pb->csParam.receive.userDataPtr = userData;
+
+ return PBControl((ParmBlkPtr)pb, async);
+}
+
+OSErr
+_TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPClose;
+ pb->tcpStream = stream;
+
+ pb->csParam.close.ulpTimeoutValue = 30;
+ pb->csParam.close.ulpTimeoutAction = 1;
+ pb->csParam.close.validityFlags = 0xC0;
+ pb->csParam.close.userDataPtr = userData;
+
+ return PBControl((ParmBlkPtr)pb, async);
+}
+
+OSErr
+_TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPAbort;
+ pb->tcpStream = stream;
+
+ pb->csParam.abort.userDataPtr = userData;
+
+ return PBControl((ParmBlkPtr)pb, async);
+}
+
+OSErr
+_TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPStatus;
+ pb->tcpStream = stream;
+ pb->csParam.status.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr)pb, async);
+ if (!async && (noErr == osErr)) {
+ *status = pb->csParam.status;
+ }
+
+ return osErr;
+}
+
+OSErr
+_TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async)
+{
+ OSErr osErr;
+
+ memset(pb, 0, sizeof(*pb));
+
+ pb->ioCompletion = ioCompletion;
+ pb->ioCRefNum = gIPPDriverRefNum;
+ pb->csCode = TCPRelease;
+ pb->tcpStream = stream;
+
+ pb->csParam.status.userDataPtr = userData;
+
+ osErr = PBControl((ParmBlkPtr)pb, async);
+
+ return osErr;
+}
+
+/* convenience functions */
+
+static pascal void
+StrToAddrMarkDone(struct hostInfo *hi, char *data)
+{
+ volatile int *done = (int *)data;
+ *done = 1;
+}
+
+OSErr
+TCPResolveName(char **name, unsigned long *ipAddress)
+{
+ OSErr osErr;
+ struct hostInfo aHostInfo;
+ volatile int done = 0;
+
+ osErr = OpenResolver(nil);
+ if (osErr)
+ return osErr;
+
+ osErr = StrToAddr(*name, &aHostInfo, (ResultProcPtr)StrToAddrMarkDone,
+ (char *)&done);
+
+ if (osErr == cacheFault) {
+ /* StrToAddrMarkDone will set done when DNS resolution finishes */
+ while (!done)
+ ;
+ }
+
+ if ((aHostInfo.rtnCode == noErr) || (aHostInfo.rtnCode == cacheFault)) {
+ /* use the first IP address for this host */
+ *ipAddress = aHostInfo.addr[0];
+ osErr = noErr;
+ } else
+ osErr = aHostInfo.rtnCode;
+
+ CloseResolver();
+ return osErr;
+}
+
+long
+ip2long(char *ip)
+{
+ long address = 0;
+ int dotcount = 0, i;
+ unsigned int b = 0;
+
+ for (i = 0; ip[i] != 0; i++) {
+ if (ip[i] == '.') {
+ if (++dotcount > 3)
+ return (0);
+ address <<= 8;
+ address |= b;
+ b = 0;
+ } else if (ip[i] >= '0' && ip[i] <= '9') {
+ b *= 10;
+ b += (ip[i] - '0');
+ if (b > 255)
+ return (0);
+ } else
+ return (0);
+ }
+
+ if (dotcount != 3)
+ return (0);
+ address <<= 8;
+ address |= b;
+ return address;
+}
+
+void
+long2ip(unsigned long num, char *ip)
+{
+ unsigned char *tmp = (unsigned char *)#
+ (void)sprintf(ip, (const char *)"%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]);
+}
+
+#define SOCKS_VERSION_SOCKS5 0x5
+#define SOCKS_METHOD_AUTH_NONE 0x0
+#define SOCKS_REQUEST_CONNECT 0x1
+#define SOCKS_REQUEST_ATYP_DOMAINNAME 0x3
+#define SOCKS_REPLY_SUCCESS 0x0
+
+OSErr
+SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip,
+ tcp_port socks_port, char *remote_host, tcp_port remote_port,
+ ip_addr *local_ip, tcp_port *local_port, Ptr user_data,
+ TCPIOCompletionProc io_completion, Boolean async)
+{
+ OSErr err;
+ TCPStatusPB status_pb;
+ wdsEntry wds[2];
+ char data[255] = { 0 };
+ unsigned short len, remote_host_len;
+
+ remote_host_len = strlen(remote_host);
+ if (remote_host_len + 7 > sizeof(data))
+ return -1;
+
+ err = _TCPActiveOpen(pb, stream, socks_ip, socks_port, local_ip,
+ local_port, user_data, io_completion, async);
+ if (err != noErr)
+ return err;
+
+ data[0] = SOCKS_VERSION_SOCKS5;
+ data[1] = 1; /* nmethods */
+ data[2] = SOCKS_METHOD_AUTH_NONE;
+
+ memset(&wds, 0, sizeof(wds));
+ wds[0].ptr = (Ptr)&data;
+ wds[0].length = 3;
+
+ err = _TCPSend(pb, stream, wds, nil, nil, false);
+ if (err)
+ goto fail;
+
+ for (;;) {
+ err = _TCPStatus(pb, stream, &status_pb, nil, nil, false);
+ if (err != noErr)
+ goto fail;
+
+ if (status_pb.amtUnreadData >= 2)
+ break;
+ }
+
+ len = 2;
+ err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false);
+ if (err != noErr)
+ goto fail;
+
+ if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_METHOD_AUTH_NONE)
+ goto fail;
+
+ len = 0;
+ data[len++] = SOCKS_VERSION_SOCKS5;
+ data[len++] = SOCKS_REQUEST_CONNECT;
+ data[len++] = 0; /* reserved */
+ data[len++] = SOCKS_REQUEST_ATYP_DOMAINNAME;
+ data[len++] = remote_host_len;
+ memcpy(data + len, remote_host, remote_host_len);
+ len += remote_host_len;
+ data[len++] = (remote_port >> 8);
+ data[len++] = (remote_port & 0xff);
+
+ memset(&wds, 0, sizeof(wds));
+ wds[0].ptr = (Ptr)&data;
+ wds[0].length = len;
+
+ err = _TCPSend(pb, stream, wds, nil, nil, false);
+ if (err)
+ goto fail;
+
+ for (;;) {
+ err = _TCPStatus(pb, stream, &status_pb, nil, nil, false);
+ if (err != noErr)
+ goto fail;
+
+ if (status_pb.amtUnreadData >= 7)
+ break;
+ }
+
+ len = status_pb.amtUnreadData;
+ if (len > sizeof(data))
+ len = sizeof(data);
+ err = _TCPRcv(pb, stream, (Ptr)&data, &len, nil, nil, false);
+ if (err != noErr)
+ goto fail;
+
+ if (data[0] != SOCKS_VERSION_SOCKS5 || data[1] != SOCKS_REPLY_SUCCESS)
+ goto fail;
+
+ return noErr;
+
+fail:
+ _TCPClose(pb, stream, nil, nil, false);
+ return err;
+}
--- tcp.h Fri Nov 13 14:05:09 2020
+++ tcp.h Fri Nov 13 14:05:09 2020
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1990-1992 by the University of Illinois Board of Trustees
+ */
+
+#include "MacTCP.h"
+#include "AddressXlation.h"
+
+#ifndef __TCP_H__
+#define __TCP_H__
+
+typedef struct
+{
+ Handle next;
+ struct hostInfo hi;
+} HostInfoQ, *HostInfoQPtr, **HostInfoQHandle;
+
+typedef ProcPtr TCPNotifyProc;
+typedef void (*TCPIOCompletionProc)(struct TCPiopb *iopb);
+
+OSErr _TCPInit(void);
+OSErr _TCPGetOurIP(ip_addr *ip, long *netMask);
+OSErr _TCPCreate(TCPiopb *pb, StreamPtr *stream, Ptr rcvBufPtr,
+ long rcvBufLen, TCPNotifyProc aNotifyProc, Ptr userDataPtr,
+ TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPPassiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr *remoteIP,
+ tcp_port *remotePort, ip_addr *localIP, tcp_port *localPort,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr remoteIP,
+ tcp_port remotePort, ip_addr *localIP, tcp_port *localPort,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPSend(TCPiopb *pb, StreamPtr stream, wdsEntry *wdsPtr,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPNoCopyRcv(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr,
+ unsigned short rdsLength, Ptr userData, TCPIOCompletionProc ioCompletion,
+ Boolean async);
+OSErr _TCPRcv(TCPiopb *pb, StreamPtr stream, Ptr rcvBufPtr,
+ unsigned short *rcvBufLen, Ptr userData, TCPIOCompletionProc ioCompletion,
+ Boolean async);
+OSErr _TCPBfrReturn(TCPiopb *pb, StreamPtr stream, Ptr rdsPtr, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPClose(TCPiopb *pb, StreamPtr stream, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPAbort(TCPiopb *pb, StreamPtr stream, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPStatus(TCPiopb *pb, StreamPtr stream, struct TCPStatusPB *status,
+ Ptr userData, TCPIOCompletionProc ioCompletion, Boolean async);
+OSErr _TCPRelease(TCPiopb *pb, StreamPtr stream, Ptr userData,
+ TCPIOCompletionProc ioCompletion, Boolean async);
+
+OSErr TCPResolveName(char **name, unsigned long *ipAddress);
+
+long ip2long(char *ip);
+void long2ip(unsigned long num, char *ip);
+
+OSErr SOCKS5TCPActiveOpen(TCPiopb *pb, StreamPtr stream, ip_addr socks_ip,
+ tcp_port socks_port, char *remote_host, tcp_port remote_port,
+ ip_addr *local_ip, tcp_port *local_port, Ptr user_data,
+ TCPIOCompletionProc io_completion, Boolean async);
+
+#endif
--- util.c Sun Jan 30 11:18:22 2022
+++ util.c Sun Jan 30 11:18:22 2022
@@ -0,0 +1,1099 @@
+/*
+ * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "util.h"
+
+/* ALRT resources */
+#define ASK_ALERT_ID 130
+
+#define ERROR_STRING_SIZE 1024
+static char err_str[ERROR_STRING_SIZE];
+
+/* basic DITL with an ok button (1), text area (2), and icon (3) */
+#define ALERT_DITL_ICON 3
+static char alert_ditl[] = {
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
+ 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x02,
+ 0x4F, 0x4B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,
+ 0x00, 0x32, 0x00, 0x40, 0x01, 0x21, 0x08, 0x02,
+ 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A,
+ 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A, 0xA0, 0x02,
+ 0x00, 0x02
+};
+static Handle alert_ditl_h = NULL;
+
+/* DITL with a Yes button (1), No button (2), text (3), and icon (4) */
+static char ask_ditl[] = {
+ 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
+ 0x00, 0xE6, 0x00, 0x5A, 0x01, 0x20, 0x04, 0x03,
+ 0x59, 0x65, 0x73, 0x21, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x46, 0x00, 0xA0, 0x00, 0x5A, 0x00, 0xDA,
+ 0x04, 0x02, 0x4E, 0x6F, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0A, 0x00, 0x32, 0x00, 0x41, 0x01, 0x22,
+ 0x08, 0x02, 0x5E, 0x30, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x0A, 0x00, 0x0A, 0x00, 0x2A, 0x00, 0x2A,
+ 0xA0, 0x02, 0x00, 0x01
+};
+static Handle ask_ditl_h = NULL;
+
+/* DITL with just a text view */
+static char progress_ditl[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14,
+ 0x00, 0x1E, 0x00, 0x32, 0x01, 0x3B, 0x08, 0x02,
+ 0x5E, 0x30
+};
+static Handle progress_ditl_h = NULL;
+static DialogPtr progress_dialog = NULL;
+
+enum {
+ STOP_ALERT,
+ CAUTION_ALERT,
+ NOTE_ALERT
+};
+
+static TEHandle track_control_te = NULL;
+
+void vwarn(short alert_func, const char *format, va_list ap);
+
+/*
+ * Memory functions
+ */
+
+void *
+xmalloc(size_t size)
+{
+ void *ptr;
+
+ if (size == 0)
+ panic("xmalloc: zero size");
+ ptr = malloc(size);
+ if (ptr == NULL)
+ panic("xmalloc(%lu) failed", size);
+ return ptr;
+}
+
+void *
+xmalloczero(size_t size)
+{
+ void *ptr = xmalloc(size);
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+void *
+xcalloc(size_t nmemb, size_t size)
+{
+ void *ptr;
+
+ ptr = calloc(nmemb, size);
+ if (ptr == NULL)
+ panic("xcalloc(%lu, %lu) failed", nmemb, size);
+ return ptr;
+}
+
+void *
+xrealloc(void *src, size_t size)
+{
+ void *ret;
+
+ ret = realloc(src, size);
+ if (ret == NULL)
+ panic("realloc(%lu) failed", size);
+ return ret;
+}
+
+#define MUL_NO_OVERFLOW ((size_t)1 << (sizeof(size_t) * 4))
+
+void *
+xmallocarray(size_t nmemb, size_t size)
+{
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size)
+ panic("xmallocarray(%lu, %lu) failed", nmemb, size);
+ return xmalloc(size * nmemb);
+}
+
+void *
+xreallocarray(void *optr, size_t nmemb, size_t size)
+{
+ void *new_ptr;
+
+ if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
+ nmemb > 0 && SIZE_MAX / nmemb < size)
+ panic("xreallocarray(%lu, %lu) failed", nmemb, size);
+ return xrealloc(optr, size * nmemb);
+}
+
+char *
+xstrdup(const char *str)
+{
+ char *cp;
+ size_t len;
+
+ len = strlen(str);
+
+ cp = xmalloc(len + 1);
+ strlcpy(cp, str, len + 1);
+
+ return cp;
+}
+
+/*
+ * String functions
+ */
+
+short
+getline(char *str, size_t len, char **ret)
+{
+ short i;
+
+ for (i = 0; i < len; i++) {
+ if (str[i] == '\r' || i == len - 1) {
+ if (*ret == NULL)
+ *ret = xmalloc(i + 1);
+ memcpy(*ret, str, i + 1);
+ (*ret)[i] = '\0';
+ return i + 1;
+ }
+ }
+
+ return 0;
+}
+
+const char *
+ordinal(unsigned short n)
+{
+ switch (n % 100) {
+ case 11:
+ case 12:
+ case 13:
+ return "th";
+ default:
+ switch (n % 10) {
+ case 1:
+ return "st";
+ case 2:
+ return "nd";
+ case 3:
+ return "rd";
+ default:
+ return "th";
+ }
+ }
+}
+
+long
+strpos_quoted(char *str, char c)
+{
+ long pos;
+ unsigned char quot = 0;
+
+ for (pos = 0; str[pos] != '\0'; pos++) {
+ if (quot) {
+ if (str[pos] == '\\') {
+ pos++;
+ continue;
+ }
+ if (str[pos] == quot)
+ quot = 0;
+ continue;
+ } else {
+ if (str[pos] == '"') {
+ quot = str[pos];
+ continue;
+ }
+ if (str[pos] == c)
+ return pos;
+ }
+ }
+
+ return -1;
+}
+
+char *
+OSTypeToString(OSType type)
+{
+ static char ostype_s[5];
+
+ ostype_s[0] = (unsigned char)((type >> 24) & 0xff);
+ ostype_s[1] = (unsigned char)((type >> 16) & 0xff);
+ ostype_s[2] = (unsigned char)((type >> 8) & 0xff);
+ ostype_s[3] = (unsigned char)(type & 0xff);
+ ostype_s[4] = 0;
+
+ return ostype_s;
+}
+
+/*
+ * BSD err(3) and warn(3) functions
+ */
+
+void
+vwarn(short alert_func, const char *format, va_list ap)
+{
+ Rect bounds, irect;
+ short quit = 0, height, width, hit;
+ WindowPtr win, dialog;
+ OSType itype;
+ Handle ihandle;
+
+ GetPort(&win);
+
+ vsnprintf(err_str, ERROR_STRING_SIZE, format, ap);
+
+ width = 300;
+ height = 100;
+ bounds.left = (screenBits.bounds.right - width) / 2;
+ bounds.right = bounds.left + width;
+ bounds.top = GetMBarHeight() +
+ ((screenBits.bounds.bottom - height) / 2.5);
+ bounds.bottom = bounds.top + height;
+
+ ParamText(CtoPstr(err_str), "\p", "\p", "\p");
+
+ alert_ditl_h = xNewHandle(sizeof(alert_ditl));
+ HLock(alert_ditl_h);
+ memcpy(*alert_ditl_h, alert_ditl, sizeof(alert_ditl));
+ HUnlock(alert_ditl_h);
+
+ dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc,
+ (WindowPtr)-1L, false, 0, alert_ditl_h);
+
+#if 0
+ /* XXX: why doesn't changing this work? */
+ GetDItem(dialog, ALERT_DITL_ICON, &itype, &ihandle, &irect);
+ switch (alert_func) {
+ case CAUTION_ALERT:
+ ihandle = GetIcon(cautionIcon);
+ break;
+ case NOTE_ALERT:
+ ihandle = GetIcon(noteIcon);
+ break;
+ default:
+ ihandle = GetIcon(stopIcon);
+ }
+ ihandle = GetIcon(cautionIcon);
+ SetDItem(dialog, ALERT_DITL_ICON, itype, ihandle, &irect);
+#endif
+
+ ShowWindow(dialog);
+ for (;;) {
+ ModalDialog(0, &hit);
+ if (hit == ok)
+ break;
+ }
+ DisposDialog(dialog);
+ DisposHandle(alert_ditl_h);
+
+ SetPort(win);
+
+ if (quit)
+ ExitToShell();
+}
+
+void
+panic(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vwarn(STOP_ALERT, format, ap);
+ va_end(ap);
+
+ ExitToShell();
+}
+
+void
+err(short ret, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vwarn(STOP_ALERT, format, ap);
+ va_end(ap);
+
+ ExitToShell();
+}
+
+void
+warn(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vwarn(CAUTION_ALERT, format, ap);
+ va_end(ap);
+}
+
+void
+warnx(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vwarn(CAUTION_ALERT, format, ap);
+ va_end(ap);
+}
+
+void
+note(const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ vwarn(NOTE_ALERT, format, ap);
+ va_end(ap);
+}
+
+short
+ask(const char *format, ...)
+{
+ Rect bounds, irect;
+ short height, width, hit;
+ WindowPtr win, dialog;
+ OSType itype;
+ Handle ihandle;
+ va_list ap;
+
+ GetPort(&win);
+
+ va_start(ap, format);
+ vsnprintf(err_str, ERROR_STRING_SIZE, format, ap);
+ va_end(ap);
+
+ width = 300;
+ height = 100;
+ bounds.left = (screenBits.bounds.right - width) / 2;
+ bounds.right = bounds.left + width;
+ bounds.top = GetMBarHeight() +
+ ((screenBits.bounds.bottom - height) / 2.5);
+ bounds.bottom = bounds.top + height;
+
+ ParamText(CtoPstr(err_str), "\p", "\p", "\p");
+
+ ask_ditl_h = xNewHandle(sizeof(ask_ditl));
+ HLock(ask_ditl_h);
+ memcpy(*ask_ditl_h, ask_ditl, sizeof(ask_ditl));
+ HUnlock(ask_ditl_h);
+
+ dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc,
+ (WindowPtr)-1L, false, 0, ask_ditl_h);
+
+ ShowWindow(dialog);
+ for (;;) {
+ ModalDialog(0, &hit);
+ if (hit == 1 || hit == 2)
+ break;
+ }
+ DisposDialog(dialog);
+ DisposHandle(alert_ditl_h);
+
+ SetPort(win);
+
+ return (hit == 1);
+}
+
+void
+progress(char *format, ...)
+{
+ static Str255 progress_s;
+ Handle thandle;
+ va_list argptr;
+ Rect bounds = { 100, 90, 160, 420 }; /* tlbr */
+ Rect trect;
+ static WindowPtr progress_win;
+ short ttype;
+
+ if (format == NULL) {
+ if (progress_dialog != NULL) {
+ DisposDialog(progress_dialog);
+ DisposHandle(progress_ditl_h);
+ progress_dialog = NULL;
+ }
+ SetPort(progress_win);
+ return;
+ }
+
+ va_start(argptr, format);
+ vsnprintf((char *)progress_s, 256, format, argptr);
+ va_end(argptr);
+ CtoPstr(progress_s);
+
+ if (progress_dialog == NULL) {
+ GetPort(&progress_win);
+
+ progress_ditl_h = xNewHandle(sizeof(progress_ditl));
+ HLock(progress_ditl_h);
+ memcpy(*progress_ditl_h, progress_ditl, sizeof(progress_ditl));
+ HUnlock(progress_ditl_h);
+
+ progress_dialog = NewDialog(nil, &bounds, "\p", false, dBoxProc,
+ (WindowPtr)-1L, false, 0, progress_ditl_h);
+ }
+
+ GetDItem(progress_dialog, 1, &ttype, &thandle, &trect);
+ SetIText(thandle, progress_s);
+
+ ShowWindow(progress_dialog);
+ DrawDialog(progress_dialog);
+}
+
+/*
+ * General Mac-specific non-GUI functions
+ */
+static unsigned long _xorshift_state = 0;
+unsigned long
+xorshift32(void)
+{
+ unsigned long x = _xorshift_state;
+ if (x == 0)
+ x = Ticks;
+ x ^= x << 13;
+ x ^= x >> 17;
+ x ^= x << 5;
+ return _xorshift_state = x;
+}
+
+
+/*
+ * Error checking wrappers for Mac toolkit functions
+ */
+
+Handle
+xNewHandle(size_t size)
+{
+ Handle h;
+
+ if (size == 0)
+ panic("Zero xNewHandle size");
+
+ h = NewHandle(size);
+ if (h == NULL)
+ panic("Failed to NewHandle(%lu)", size);
+
+ return h;
+}
+
+Handle
+xGetResource(ResType type, short id)
+{
+ Handle h;
+
+ h = GetResource(type, id);
+ if (h == NULL)
+ panic("Failed to find resource %d", id);
+
+ return h;
+}
+
+StringHandle
+xGetString(short id)
+{
+ StringHandle h;
+
+ h = GetString(id);
+ if (h == NULL)
+ panic("Failed to find STR resource %d", id);
+
+ return h;
+}
+
+char *
+xGetStringAsChar(short id)
+{
+ StringHandle h = xGetString(id);
+ char *out;
+ size_t l;
+
+ HLock(h);
+
+ l = (*h)[0];
+ out = xmalloc(l + 1);
+ memcpy((void *)out, (void *)(*h + 1), l);
+ out[l] = '\0';
+
+ DisposHandle(h);
+ return out;
+}
+
+long
+xGetStringAsLong(short id)
+{
+ char *c;
+ long r;
+
+ c = xGetStringAsChar(id);
+ r = atol(c);
+ free(c);
+ return r;
+}
+
+void
+xSetHandleSize(Handle h, Size s)
+{
+ SetHandleSize(h, s);
+ if (MemError())
+ panic("Failed to SetHandleSize to %ld", s);
+}
+
+/*
+ * Filesystem utilities
+ */
+
+short
+getpath(short vRefNum, Str255 fileName, Str255 *ret, bool include_file)
+{
+ WDPBRec wdir;
+ HVolumeParam wvol;
+ DirInfo wcinfo;
+ Str255 name;
+ size_t retlen = 0, len;
+ char tmpret[256], tmp[256];
+ char *lastcolon;
+
+ if (strchr((char *)fileName + 1, ':') != NULL) {
+ /* already a full path */
+ memcpy(*ret, fileName, 256);
+ if (!include_file) {
+ PtoCstr(*ret);
+ lastcolon = strrchr((char *)*ret, ':');
+ lastcolon[0] = '\0';
+ CtoPstr(*ret);
+ }
+ return 0;
+ }
+
+ wdir.ioVRefNum = wdir.ioWDVRefNum = vRefNum;
+ wdir.ioWDIndex = 0;
+ wdir.ioWDProcID = 0;
+ wdir.ioNamePtr = (StringPtr)&name;
+ if (PBGetWDInfo(&wdir, 0) != noErr) {
+ warn("Failed looking up directory");
+ return 1;
+ }
+
+ wvol.ioNamePtr = (StringPtr)&name;
+ wvol.ioVRefNum = vRefNum;
+ wvol.ioVolIndex = 0;
+
+ if (PBHGetVInfoSync((HParmBlkPtr)&wvol) != noErr) {
+ warn("Failed getting volume info");
+ return 1;
+ }
+
+ if (wvol.ioVSigWord != 0x4244) {
+ warn("Unknown filesystem type 0x%x", wvol.ioVSigWord);
+ return 1;
+ }
+
+ wcinfo.ioVRefNum = vRefNum;
+ wcinfo.ioNamePtr = (StringPtr)&name;
+ wcinfo.ioFDirIndex = -1;
+ wcinfo.ioDrParID = wdir.ioWDDirID;
+ wcinfo.ioDrDirID = wdir.ioWDDirID;
+
+ /* go backwards, prepending each folder's parent */
+ while (wcinfo.ioDrParID != 1) {
+ wcinfo.ioDrDirID = wcinfo.ioDrParID; /* .. */
+
+ if (PBGetCatInfo((CInfoPBPtr)&wcinfo, 0) != noErr)
+ break;
+
+ len = name[0];
+ PtoCstr(name);
+ if (retlen == 0) {
+ retlen = len;
+ strlcpy(tmpret, (char *)name, sizeof(tmpret));
+ } else {
+ strlcpy(tmp, tmpret, sizeof(tmp));
+ snprintf(tmpret, sizeof(tmpret), "%s:%s", name, tmp);
+ }
+ }
+
+ if (include_file) {
+ /* append the original path */
+ memcpy(name, fileName, sizeof(name));
+ PtoCstr(name);
+ if (retlen == 0)
+ strlcpy(tmpret, (char *)name, sizeof(tmpret));
+ else {
+ strlcat(tmpret, ":", sizeof(tmpret));
+ strlcat(tmpret, (char *)name, sizeof(tmpret));
+ }
+ } else if (retlen == 0) {
+ (*ret)[0] = 0;
+ return 0;
+ }
+
+ CtoPstr(tmpret);
+ memcpy(*ret, tmpret, sizeof(tmpret));
+
+ return 0;
+}
+
+short
+stat(char *path, struct stat *sb)
+{
+ char *ppath;
+ short ret;
+
+ ppath = xstrdup(path);
+ CtoPstr(ppath);
+ ret = FStat((unsigned char *)ppath, sb);
+ free(ppath);
+
+ return ret;
+}
+
+short
+FStat(Str255 path, struct stat *sb)
+{
+ CInfoPBRec catblock = { 0 };
+ short ret;
+
+ catblock.hFileInfo.ioNamePtr = path;
+ ret = PBGetCatInfo(&catblock, 0);
+ if (ret != noErr)
+ return -1;
+
+ /* data fork logical length + resource fork logical length */
+ sb->st_size = catblock.hFileInfo.ioFlLgLen +
+ catblock.hFileInfo.ioFlRLgLen;
+ sb->st_ctime = catblock.hFileInfo.ioFlCrDat;
+ sb->st_mtime = catblock.hFileInfo.ioFlMdDat;
+ sb->st_flags = catblock.hFileInfo.ioFlAttrib;
+
+ return 0;
+}
+
+bool
+FIsDir(Str255 path)
+{
+ struct stat st;
+ short ret;
+
+ if ((ret = FStat(path, &st)) != 0)
+ return ret;
+
+ /* bit 4 is set in ioFlAttrib if the item is a directory */
+ if (st.st_flags & (1 << 4))
+ return true;
+
+ return false;
+}
+
+OSErr
+copy_file(Str255 source, Str255 dest, bool overwrite)
+{
+ FInfo fi;
+ IOParam pb;
+ short error, source_ref, dest_ref;
+
+ /* copy data fork */
+
+ error = GetFInfo(source, 0, &fi);
+ if (error)
+ return error;
+
+ error = Create(dest, 0, fi.fdCreator, fi.fdType);
+ if (error == dupFNErr && overwrite) {
+ error = FSDelete(dest, 0);
+ if (error)
+ return error;
+ error = Create(dest, 0, fi.fdCreator, fi.fdType);
+ }
+ if (error)
+ return error;
+
+ memset(&pb, 0, sizeof(pb));
+ pb.ioNamePtr = source;
+ pb.ioPermssn = fsRdPerm;
+ error = PBOpen(&pb, false);
+ if (error)
+ return error;
+ source_ref = pb.ioRefNum;
+
+ error = FSOpen(dest, 0, &dest_ref);
+ if (error) {
+ FSClose(source_ref);
+ return error;
+ }
+
+ error = copy_file_contents(source_ref, dest_ref);
+
+ FSClose(source_ref);
+ FSClose(dest_ref);
+
+ if (error)
+ return error;
+
+ /*
+ * Copy resource fork, open source as shared read/write in case it's
+ * an open resource file.
+ */
+ source_ref = OpenRFPerm(source, 0, fsRdWrShPerm);
+
+ if (source_ref == -1 && ResError() == eofErr) {
+ /* no resource fork */
+ FSClose(source_ref);
+ FSClose(dest_ref);
+ return 0;
+ }
+
+ if (source_ref == -1)
+ return ResError();
+
+ CreateResFile(dest);
+ if (ResError()) {
+ FSClose(source_ref);
+ return ResError();
+ }
+ error = OpenRF(dest, 0, &dest_ref);
+ if (error) {
+ FSClose(source_ref);
+ return error;
+ }
+
+ error = copy_file_contents(source_ref, dest_ref);
+
+ FSClose(source_ref);
+ FSClose(dest_ref);
+
+ return error;
+}
+
+OSErr
+copy_file_contents(short source_ref, short dest_ref)
+{
+ char buf[1024];
+ short error;
+ long source_size, count;
+
+ GetEOF(source_ref, &source_size);
+ error = SetFPos(source_ref, fsFromStart, 0);
+ if (error)
+ return error;
+ error = SetEOF(dest_ref, source_size);
+ if (error)
+ return error;
+ error = SetFPos(dest_ref, fsFromStart, 0);
+ if (error)
+ return error;
+ while (source_size > 0) {
+ count = sizeof(buf);
+ if (count > source_size)
+ count = source_size;
+ error = FSRead(source_ref, &count, &buf);
+ if (error && error != eofErr)
+ break;
+ source_size -= count;
+ error = FSWrite(dest_ref, &count, &buf);
+ if (error && error != eofErr)
+ break;
+ }
+
+ if (error && error != eofErr)
+ return error;
+
+ return 0;
+}
+
+/* read a \r-terminated line or the final non-line bytes of an open file */
+OSErr
+FSReadLine(short frefnum, char *buf, size_t buflen)
+{
+ char tbuf;
+ size_t pos, fsize, rlen = 1, total_read = 0;
+ short error, found = -1, i;
+
+ GetFPos(frefnum, &pos);
+ GetEOF(frefnum, &fsize);
+
+ for (; pos <= fsize; pos++) {
+ if (total_read > buflen)
+ return -1;
+
+ error = FSRead(frefnum, &rlen, &tbuf);
+ if (error)
+ return -1;
+
+ if (tbuf == '\r')
+ return total_read;
+
+ buf[total_read++] = tbuf;
+ }
+
+ /* nothing found until the end of the file */
+ return total_read;
+}
+
+
+/*
+ * General Mac-specific GUI functions
+ */
+
+short
+FontHeight(short font_id, short size)
+{
+ FMInput f_in;
+ FMOutput *f_out;
+
+ if (font_id == -1)
+ font_id = thePort->txFont;
+ if (size == -1)
+ size = thePort->txSize;
+
+ f_in.family = font_id;
+ f_in.size = size;
+ f_in.face = 0;
+ f_in.needBits = false;
+ f_in.device = 0;
+ f_in.numer.h = f_in.numer.v = 1;
+ f_in.denom.h = f_in.denom.v = 1;
+
+ f_out = FMSwapFont(&f_in);
+
+ return (((f_out->leading + f_out->ascent + f_out->descent) *
+ f_out->numer.v) / f_out->denom.v);
+}
+
+void
+DrawGrowIconOnly(WindowPtr win)
+{
+ Rect r;
+ RgnHandle tmp;
+
+ GetClip(tmp = NewRgn());
+ r = win->portRect;
+ r.top = r.bottom - SCROLLBAR_WIDTH;
+ r.left = r.right - SCROLLBAR_WIDTH + 1;
+ ClipRect(&r);
+ DrawGrowIcon(win);
+ SetClip(tmp);
+ DisposeRgn(tmp);
+}
+
+/* assumes a fixed-width style */
+short
+TEGetWidth(short off, TEHandle te)
+{
+ TextStyle style;
+ short fheight, fwidth, ascent, old_font, old_size;
+
+ TEGetStyle(off, &style, &fheight, &ascent, te);
+
+ old_font = thePort->txFace;
+ old_size = thePort->txSize;
+ thePort->txFace = style.tsFont;
+ thePort->txSize = style.tsSize;
+ fwidth = CharWidth('m');
+ thePort->txFace = old_font;
+ thePort->txSize = old_size;
+
+ return fwidth;
+}
+
+#define ceildiv(a,b) ((a / b) + (!!(a % b)))
+
+void
+UpdateScrollbarForTE(ControlHandle control, TEHandle te, bool reset)
+{
+ size_t vlines, telines;
+ TERec *ter;
+ short fheight, fwidth, max, val, per_page, per_line, horiz, max_chars,
+ n;
+
+ HLock(te);
+ ter = *te;
+
+ horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) <
+ ((*control)->contrlRect.right - (*control)->contrlRect.left));
+
+ if (horiz) {
+ fwidth = TEGetWidth(0, te);
+ per_line = ((ter->viewRect.right - ter->viewRect.left) /
+ fwidth) - 1;
+ for (max_chars = 0, n = 1; n < ter->nLines; n++) {
+ if (ter->lineStarts[n] - ter->lineStarts[n - 1] > max_chars)
+ max_chars = ter->lineStarts[n] - ter->lineStarts[n - 1];
+ }
+
+ if (max_chars <= per_line)
+ /* don't enable the scrollbar */
+ max = 1;
+ else
+ max = max_chars - per_line + 1;
+
+ if (reset) {
+ val = 1;
+ TESetSelect(0, 0, te);
+ } else {
+ val = (ter->viewRect.left - ter->destRect.left);
+
+ if (val < 0)
+ val = 1;
+ else {
+ val = ceildiv(val, fwidth) + 1;
+ if (val > max)
+ max = val;
+ }
+ }
+ } else {
+ fheight = TEGetHeight(0, 0, te);
+ vlines = (ter->viewRect.bottom - ter->viewRect.top) / fheight;
+ telines = ter->nLines;
+ /* telines is inaccurate if the last line doesn't have any chars */
+ if (telines >= vlines)
+ telines++;
+ max = telines - vlines + 1;
+ if (max < 1)
+ max = 1;
+
+ if (reset) {
+ val = 1;
+ TESetSelect(0, 0, te);
+ } else {
+ val = (ter->viewRect.top - ter->destRect.top);
+ if (val < 0)
+ val = 1;
+ else {
+ val = ceildiv(val, fheight) + 1;
+ if (val > max)
+ max = val;
+ }
+ }
+ }
+ SetCtlMax(control, max);
+ SetCtlValue(control, val);
+
+ HUnlock(te);
+}
+
+void
+SetTrackControlTE(TEHandle te)
+{
+ track_control_te = te;
+}
+
+pascal void
+TrackMouseDownInControl(ControlHandle control, short part)
+{
+ TERec *ter;
+ short page, val, adj, fheight, fwidth, horiz, per_line;
+
+ if (track_control_te == NULL)
+ panic("TrackMouseDownInControl without SetTrackControlTE");
+
+ HLock(track_control_te);
+ ter = *track_control_te;
+
+ horiz = (((*control)->contrlRect.bottom - (*control)->contrlRect.top) <
+ ((*control)->contrlRect.right - (*control)->contrlRect.left));
+
+ if (horiz) {
+ fwidth = TEGetWidth(0, track_control_te);
+ per_line = ((ter->viewRect.right - ter->viewRect.left) /
+ fwidth) - 1;
+ page = ceildiv(GetCtlMax(control) + per_line,
+ ((per_line * 75) / 100)) + 1;
+ } else {
+ /* keep 1 line of context between pages */
+ fheight = TEGetHeight(0, 0, track_control_te);
+ page = ((ter->viewRect.bottom - ter->viewRect.top) / fheight) - 1;
+ }
+
+ adj = 0;
+ switch (part) {
+ case inUpButton:
+ adj = -1;
+ break;
+ case inPageUp:
+ adj = -page;
+ break;
+ case inDownButton:
+ adj = 1;
+ break;
+ case inPageDown:
+ adj = page;
+ break;
+ }
+
+ val = GetCtlValue(control);
+ if (val + adj < GetCtlMin(control))
+ adj = -(val - GetCtlMin(control));
+ if (val + adj > GetCtlMax(control))
+ adj = (GetCtlMax(control) - val);
+ if (adj == 0)
+ return;
+
+ if (horiz)
+ TEScroll(-adj * fwidth, 0, track_control_te);
+ else
+ TEScroll(0, -adj * fheight, track_control_te);
+
+ SetCtlValue(control, val + adj);
+
+ HUnlock(track_control_te);
+}
+
+pascal bool
+ModalDialogFilter(DialogPtr dlg, EventRecord *event, short *hit)
+{
+ char key;
+
+ switch (event->what) {
+ case inGoAway:
+ if (TrackGoAway(dlg, event->where)) {
+ *hit = -1;
+ return true;
+ }
+ break;
+ case keyDown:
+ key = event->message & charCodeMask;
+
+ if (event->modifiers & cmdKey) {
+ switch (key) {
+ case 'x':
+ ZeroScrap();
+ DlgCut(dlg);
+ break;
+ case 'c':
+ ZeroScrap();
+ DlgCopy(dlg);
+ break;
+ case 'v':
+ if (TEFromScrap() == noErr)
+ DlgPaste(dlg);
+ break;
+ }
+ event->what = nullEvent;
+ return false;
+ } else if (key == 13 || key == 3) {
+ /* OK button */
+ *hit = OK;
+ return true;
+ }
+
+ break;
+ }
+
+ return false;
+}
--- util.h Sat Jan 29 16:52:36 2022
+++ util.h Sat Jan 29 16:52:36 2022
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 2020-2022 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __UTIL_H__
+#define __UTIL_H__
+
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+
+#ifndef SIZE_MAX
+#define SIZE_MAX ULONG_MAX
+#endif
+
+#define nitems(what) (sizeof((what)) / sizeof((what)[0]))
+#define member_size(type, member) sizeof(((type *)0)->member)
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+#define BOUND(a, min, max) ((a) > (max) ? (max) : ((a) < (min) ? (min) : (a)))
+
+#define EXPAND_TO_FIT(var, var_size, used_size, add, grow_amount) { \
+ if ((used_size) + (add) >= (var_size)) { \
+ while ((used_size) + (add) >= (var_size)) { \
+ (var_size) += (grow_amount); \
+ } \
+ (var) = xrealloc((var), (var_size)); \
+ } \
+}
+
+#define SCROLLBAR_WIDTH 16
+
+/* GetMBarHeight() is not very useful */
+#define MENUBAR_HEIGHT 20
+
+#define MAX_TEXTEDIT_SIZE 32767L
+
+#ifndef bool
+typedef Boolean bool;
+#endif
+
+typedef signed long off_t;
+typedef signed long ssize_t;
+typedef unsigned char u_char;
+typedef unsigned long u_int;
+
+typedef struct {
+ short push[2], rts;
+ void *addr;
+} tCodeStub;
+
+typedef struct stat {
+ short st_mode;
+ ssize_t st_size;
+ time_t st_ctime;
+ time_t st_mtime;
+ unsigned char st_flags;
+};
+
+void *xmalloc(size_t);
+void *xmalloczero(size_t);
+void *xcalloc(size_t, size_t);
+void *xrealloc(void *src, size_t size);
+void *xmallocarray(size_t nmemb, size_t size);
+void *xreallocarray(void *, size_t, size_t);
+char *xstrdup(const char *);
+
+short getline(char *str, size_t len, char **ret);
+size_t strlcpy(char *dst, const char *src, size_t dsize);
+size_t strlcat(char *dst, const char *src, size_t dsize);
+const char *ordinal(unsigned short n);
+long strpos_quoted(char *str, char c);
+char *OSTypeToString(OSType type);
+
+unsigned long xorshift32(void);
+
+void panic(const char *format, ...);
+void err(short ret, const char *format, ...);
+void warnx(const char *format, ...);
+void warn(const char *format, ...);
+void note(const char *format, ...);
+short ask(const char *format, ...);
+#define ASK_YES 1
+#define ASK_NO 2
+void progress(char *format, ...);
+
+Handle xNewHandle(size_t size);
+Handle xGetResource(ResType type, short id);
+StringHandle xGetString(short id);
+char *xGetStringAsChar(short id);
+long xGetStringAsLong(short id);
+void xSetHandleSize(Handle h, Size s);
+
+short getpath(short vRefNum, Str255 fileName, Str255 *ret,
+ bool include_file);
+bool FIsDir(Str255 path);
+short stat(char *path, struct stat *sb);
+short FStat(Str255 path, struct stat *sb);
+OSErr copy_file(Str255 source, Str255 dest, bool overwrite);
+OSErr copy_file_contents(short source_ref, short dest_ref);
+OSErr FSReadLine(short frefnum, char *buf, size_t buflen);
+
+short FontHeight(short font_id, short size);
+void DrawGrowIconOnly(WindowPtr win);
+short TEGetWidth(short off, TEHandle te);
+void UpdateScrollbarForTE(ControlHandle scroller, TEHandle te, bool reset);
+void SetTrackControlTE(TEHandle te);
+pascal void TrackMouseDownInControl(ControlHandle control, short part);
+pascal bool ModalDialogFilter(DialogPtr dlg, EventRecord *event,
+ short *hit);
+
+#endif