├── BUILDING.md ├── LICENSE ├── Makefile.mk ├── README.md ├── ShellMount.md ├── _config.yml ├── afpbridge.c ├── afpbridge.rez ├── afpcdev.c ├── afpcdev.rez ├── afpinit.asm ├── afpmounter.c ├── afpoptions.c ├── afpoptions.h ├── afpurlparser.c ├── afpurlparser.h ├── asmglue.asm ├── asmglue.h ├── aspinterface.c ├── aspinterface.h ├── atipmapping.c ├── atipmapping.h ├── callat.asm ├── cdevstart.asm ├── cdevutil.h ├── cmdproc.asm ├── cmdproc.h ├── dsi.c ├── dsi.h ├── dsiproto.h ├── dsitest.c ├── dumpcmdtbl.c ├── endian.asm ├── endian.h ├── endiantest.c ├── installcmds.c ├── installcmds.h ├── listsess.c ├── readtcp.c ├── readtcp.h ├── savenames.c ├── session.h ├── strncasecmp.c ├── strncasecmp.h ├── tcpconnection.c └── tcpconnection.h /BUILDING.md: -------------------------------------------------------------------------------- 1 | Building AFPBridge 2 | ================== 3 | 4 | AFPBridge is designed to be built under GNO 2.0.6, with ORCA/C and ORCA/M 5 | installed under `/lang/orca` as described in the GNO documentation. 6 | I am using a custom version of ORCA/C with several patches applied, 7 | but I believe a stock version of ORCA/C 2.1.x should also work. 8 | 9 | You also need to get the `AppleTalk.h` header file, which is included 10 | under `Libraries/APWCInclude` in an ORCA/C installation. Copy it either 11 | to `/lang/orca/Libraries/ORCACDefs` or to the directory with the AFPBridge 12 | source files. The original version of that file does not include 13 | prototypes in its function declarations, which will cause an ORCA/C error 14 | with the settings in the makefile. To avoid this, either remove the 15 | `-w` flag from `CFLAGS` in `Makefile.mk`, or add the prototypes in 16 | `AppleTalk.h`. If adding the prototypes, they should be `RamForbid(void)`, 17 | `RamPermit(void)`, and `_CALLAT(void*)`. 18 | 19 | To build AFPBridge using source files copied directly from the Git repository, 20 | first run: 21 | 22 | make import 23 | 24 | This sets the file types appropriately, converts files to Apple II-style 25 | line endings, and generates required assembly-language macro files. 26 | 27 | Once that is done, you can build the code by running: 28 | 29 | make 30 | 31 | This builds the `AFPBridge` init, the `AFPMounter` CDev, and several 32 | command-line utilities that can be useful for testing and debugging. 33 | 34 | You can also run `make install` to install the init and CDev in your 35 | system folder, or `make clean` to remove the generated files. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Stephen Heumann 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | 26 | The compiled binaries include material from the ORCA/C run-time libraries, to 27 | which the following notice applies: 28 | 29 | This program contains material from the ORCA/C Run-Time Libraries, copyright 30 | 1987-1996 by Byte Works, Inc. Used with permission. 31 | -------------------------------------------------------------------------------- /Makefile.mk: -------------------------------------------------------------------------------- 1 | # Use stock ORCA libraries & headers, not GNO ones 2 | USEORCALIBS = prefix 13 /lang/orca/Libraries 3 | COMMAND = $(!eq,$(CMNDNAME),$(CC) $(CMNDNAME) $(USEORCALIBS)&&$(CC)) $(CMNDARGS) 4 | 5 | CFLAGS = -i -w -O95 6 | 7 | DSITEST_OBJS = dsitest.o aspinterface.o dsi.o readtcp.o endian.o tcpconnection.o atipmapping.o asmglue.o cmdproc.o installcmds.o afpoptions.o strncasecmp.o savenames.o 8 | DSITEST_PROG = dsitest 9 | 10 | LISTSESSIONS_OBJS = listsess.o callat.o 11 | LISTSESSIONS_PROG = listsessions 12 | 13 | MOUNTAFP_OBJS = afpmounter.o callat.o endian.o 14 | MOUNTAFP_PROG = mountafp 15 | 16 | DUMPCMDTBL_OBJS = dumpcmdtbl.o asmglue.o 17 | DUMPCMDTBL_PROG = dumpcmdtbl 18 | 19 | AFPBRIDGE_OBJS = afpinit.o afpbridge.o aspinterface.o dsi.o readtcp.o endian.o tcpconnection.o atipmapping.o asmglue.o installcmds.o cmdproc.o callat.o afpoptions.o strncasecmp.o savenames.o 20 | AFPBRIDGE_RSRC = afpbridge.rez 21 | AFPBRIDGE_PROG = AFPBridge 22 | 23 | AFPMOUNTER_OBJS = cdevstart.o afpcdev.o afpurlparser.o afpoptions.o strncasecmp.o 24 | AFPMOUNTER_RSRC = afpcdev.rez 25 | AFPMOUNTER_CDEV = AFPMounter 26 | 27 | MACROS = asmglue.macros cmdproc.macros 28 | 29 | PROGS = $(AFPBRIDGE_PROG) $(AFPMOUNTER_CDEV) $(DSITEST_PROG) $(MOUNTAFP_PROG) $(DUMPCMDTBL_PROG) $(LISTSESSIONS_PROG) 30 | 31 | .PHONY: default 32 | default: $(PROGS) 33 | 34 | $(DSITEST_PROG): $(DSITEST_OBJS) 35 | $(CC) $(CFLAGS) -o $@ $< 36 | 37 | $(MOUNTAFP_PROG): $(MOUNTAFP_OBJS) 38 | $(CC) $(CFLAGS) -o $@ $< 39 | 40 | $(DUMPCMDTBL_PROG): $(DUMPCMDTBL_OBJS) 41 | $(CC) $(CFLAGS) -o $@ $< 42 | 43 | $(LISTSESSIONS_PROG): $(LISTSESSIONS_OBJS) 44 | $(CC) $(CFLAGS) -o $@ $< 45 | 46 | $(AFPBRIDGE_PROG): $(AFPBRIDGE_OBJS) $(AFPBRIDGE_RSRC) 47 | $(CC) $(CFLAGS) -M -o $@ $(AFPBRIDGE_OBJS) > $@.map 48 | $(REZ) $(AFPBRIDGE_RSRC) -o $@ 49 | chtyp -tpif $@ 50 | 51 | $(AFPMOUNTER_CDEV).obj: $(AFPMOUNTER_OBJS) 52 | $(CC) $(CFLAGS) -o $@ $< 53 | 54 | $(AFPMOUNTER_CDEV): $(AFPMOUNTER_CDEV).obj $(AFPMOUNTER_RSRC) 55 | $(REZ) $(AFPMOUNTER_RSRC) -o $@ 56 | chtyp -tcdv $@ 57 | 58 | %.macros: %.asm 59 | macgen $< $@ /lang/orca/Libraries/ORCAInclude/m16.* > .null 60 | 61 | .PHONY: install 62 | install: $(AFPBRIDGE_PROG) $(AFPMOUNTER_CDEV) 63 | cp $(AFPBRIDGE_PROG) "*/System/System.Setup" 64 | cp $(AFPMOUNTER_CDEV) "*/System/CDevs" 65 | $(RM) "*/System/CDevs/CDev.Data" > .null 66 | 67 | .PHONY: chtyp 68 | chtyp: 69 | chtyp -ttxt *.mk *.md *.txt LICENSE 70 | chtyp -lcc *.c *.h 71 | chtyp -lasm *.asm *.macros 72 | chtyp -lrez *.rez 73 | 74 | .PHONY: import 75 | import: chtyp 76 | udl -g * 77 | dmake $(MACROS) 78 | 79 | .PHONY: clean 80 | clean: 81 | $(RM) $(PROGS) *.o *.root *.obj *.map > .null 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AFPBridge 2 | ========= 3 | By Stephen Heumann 4 | 5 | AFPBridge is a tool that allows an Apple IIgs to connect to an AFP (Apple Filing Protocol) file server over TCP/IP. AFPBridge works by using the existing AppleShare FST, but redirecting its network traffic over TCP/IP rather than AppleTalk. 6 | 7 | __To download the latest version of AFPBridge, visit the [releases page][1].__ 8 | 9 | [1]: https://github.com/sheumann/AFPBridge/releases 10 | 11 | 12 | System Requirements 13 | ------------------- 14 | * An Apple IIgs with AppleTalk enabled in the Control Panel (see below) 15 | * System 6.0.1 or later, with AppleTalk-related components installed (see below) 16 | * Marinetti ([version 3.0b11 or later][Marinetti] recommended for best performance and compatibility) 17 | * A Marinetti-compatible network interface (such as an Ethernet card) 18 | 19 | Although AFPBridge uses TCP/IP rather than AppleTalk for networking, it relies on the AppleShare FST and related system components, and these are designed to work with AppleTalk. Therefore, the system must be configured to use AppleTalk, and the AppleTalk-related components in the system software must be installed and enabled. 20 | 21 | If you do not already have the necessary components installed, you can use the "Network: AppleShare" option in the system software installer to add them. Most of the components installed by this option are necessary in order for AFPBridge to function. EasyMount (which is also installed by this option) is required in order to use the AFP Mounter control panel. The AppleShare control panel is not required and cannot be used to connect to AFP servers over TCP/IP; it may be disabled or removed if desired. 22 | 23 | In order for the AppleShare FST and the components it requires to load and be usable, it is also necessary to configure the system's slots (in the Slots section of the Control Panel) to enable AppleTalk. In a ROM 01 system, slot 7 must be set to "AppleTalk" and slot 1 or 2 must be set to "Your Card." In a ROM 3 system, slot 1 or 2 must be set to "AppleTalk." Note that it is _not_ actually necessary for the system to be connected to an AppleTalk network (although it may be). 24 | 25 | If you wish to use AFPBridge in an emulator, it must be capable of running with AppleTalk enabled as discussed above, although it need not be capable of actually communicating over AppleTalk networks. GSplus, GSport, and perhaps other KEGS-based emulators should work. Sweet16 did not work in my testing, because the system hangs during boot-up when AppleTalk is enabled. 26 | 27 | [Marinetti]: http://www.apple2.org/marinetti/index.html 28 | 29 | 30 | Server Compatibility 31 | -------------------- 32 | Since the AppleShare FST and related system components are used without modification, they constrain the AFP protocol versions and features that are supported, and accordingly what servers can be used. In particular, these components are designed to use AFP version 2.0, so it is best to use them with a server that supports that version. There is an option (described below) to negotiate a connection using AFP version 2.2 instead, which may allow connections to some additional servers, but servers supporting only AFP 3.x cannot be used. 33 | 34 | The forms of authentication that can be used are also limited for similar reasons. The AFP Mounter control panel currently relies on EasyMount to perform authentication, and it supports only guest logins or the 'Randnum exchange' 35 | authentication method. This method uses a password of up to eight characters protected by weak encryption; it may be disabled on some newer servers. 36 | 37 | I recommend using Netatalk 2.x, and in particular the version of it provided by [A2SERVER][2]. This is the primary server platform I have used in my testing, and A2SERVER is designed specifically to work with Apple II systems. Other AFP 2.x servers, such as the one in Mac OS 9, should also work but have not been tested as extensively. 38 | 39 | It is possible to connect to the AFP server in Mac OS X version 10.5 and earlier, but it does not support ProDOS-style file type information, so all files will be shown as having unknown type, and it will generally not be possible to open them in most programs. With appropriate options (see below) it is possible to write files to Mac OS X servers, but their file types will not be preserved. The server in Mac OS X also may not support compatible user authentication methods; I have tried it only using guest access. Later versions of OS X/macOS support only AFP 3.x and cannot be used. 40 | 41 | [2]: http://ivanx.com/a2server/ 42 | 43 | 44 | Installation 45 | ------------ 46 | To install AFPBridge, place the `AFPBridge` file in the `*:System:System.Setup` folder (where `*` indicates your boot disk), and place the `AFPMounter` file in the `*:System:CDevs` folder. You must also have Marinetti and the AppleTalk-related system components installed as discussed above. Reboot the system to complete the installation. 47 | 48 | 49 | Usage 50 | ----- 51 | To connect to an AFP server over TCP/IP, open the __AFP Mounter__ control panel and then enter the URL for a server. You must specify at least a server and a volume name; there is currently no functionality for listing available servers or for listing the volumes that are available on a server. If no user name is specified, a guest login will be attempted, and if that is not possible you will be prompted for a user name and password. If a user name is given without a password, you will be prompted for the password. 52 | 53 | The general form of URLs supported for AFP connections over TCP/IP is: 54 | 55 | afp://[user[:password]@]server[:port]/volume 56 | 57 | The server can be specified using an IP address or host name. A host name can only be used if the DNS server you are using can resolve it; other ways of naming systems on the local network are not supported. 58 | 59 | It is also possible to use the AFP Mounter control panel to connect to AFP servers over AppleTalk (not relying on AFPBridge). The form of URLs for that is: 60 | 61 | afp:/at/[user[:password]@]server[:zone]/volume 62 | 63 | Click the __Connect__ button to connect to the server, or the __Save Alias...__ button to save an alias to it. An alias allows you to connect by double-clicking it in the Finder, as long as EasyMount is installed. 64 | 65 | There are also several advanced options available in the drop-down menu at the top right of the window. These apply only to AFP connections over TCP/IP. 66 | The current options will be saved in any alias, as well as applying to connections made directly in the AFP Mounter control panel. 67 | 68 | * The __Use Large Reads__ option allows the IIgs to read a larger amount at once than would be allowed with AppleTalk, which improves the speed of large reads from files on the server. (AppleTalk normally limits the amount of data transmitted at once, requiring multiple network requests to read or write a large amount.) This option is safe to use with current versions of the AppleShare FST, but in theory it could cause memory corruption or other problems when used with other programs that perform low-level accesses to AFP servers. I am not aware of any programs that actually cause such problems, so this option should generally be safe to use. 69 | 70 | * The __Use Large Writes__ option allows the IIgs to write a larger amount at once than would be allowed with AppleTalk. This improves write performance, but causes problems with some servers. This can be safely enabled with Netatalk, but it can cause errors when used with Apple's servers in classic Mac OS or Mac OS X. 71 | 72 | * The __Force AFP Version 2.2__ option forces the IIgs to request AFP version 2.2 rather than version 2.0 when connecting to the server. This can allow connections to some servers that do not support AFP version 2.0. The AppleShare FST is designed to use AFP version 2.0, so this may cause problems due to the version mismatch. In practice, these two AFP versions are similar enough that this generally seems to work, but I cannot rule out the possibility of unexpected behavior. 73 | 74 | * The __Fake Sleep to Keep Alive__ option may help the IIgs stay connected to the server in cases where it is unexpectedly being disconnected. This can occur because the server expects the client to periodically send a 'tickle' to indicate that it is still connected. In some cases (such as when running text-based shells or other non-desktop programs) AFPBridge is not able to do this. This option works by making the IIgs report to the server that it is going to sleep following each AFP request it sends. This may cause the server to stay connected without expecting 'tickles,' since a sleeping computer cannot send them. This option slows down the connection a bit and does not work with all servers. To use it with Netatalk, you must use AFP version 2.2. 75 | 76 | * The __Ignore Errors Setting File Types__ option causes the IIgs to ignore errors when trying to set the ProDOS-style types of files on the server. This allows the IIgs to write files to servers that do not support ProDOS-style file types (such as the one in old versions of Mac OS X) without being stopped by these errors. Note that file types will not be correctly set when writing to such servers, even when using this option. 77 | 78 | It is also possible to mount AFP volumes from the command line in a shell. For instructions on doing that, see [here][3]. 79 | 80 | [3]: https://sheumann.github.io/AFPBridge/ShellMount 81 | 82 | 83 | Building AFPBridge 84 | ------------------ 85 | 86 | The full source code for AFPBridge is available [on GitHub][4]. For instructions on building it, see [here][5]. 87 | 88 | [4]: https://github.com/sheumann/AFPBridge 89 | [5]: https://sheumann.github.io/AFPBridge/BUILDING 90 | 91 | 92 | Contact 93 | ------- 94 | If you have questions, comments, bug reports, or feature requests, you can contact me at stephen.heumann@gmail.com or submit them to the [issues page][6] on GitHub. 95 | 96 | [6]: https://github.com/sheumann/AFPBridge/issues 97 | -------------------------------------------------------------------------------- /ShellMount.md: -------------------------------------------------------------------------------- 1 | Mounting AFP Volumes in a Shell 2 | =============================== 3 | 4 | It is possible to mount AFP volumes from an ORCA, GNO, or APW shell command line. To do this, you should get the `Choose` utility produced by Apple. It is available on certain old Apple developer CDs, and in the Golden Orchard and Golden Grail software collections available for download [here][1]. On the Golden Orchard CD, the `Choose` command is available under `Programming:Apple:APW & ORCA Commands:Gregs.APW.Utils`. (The actual command is in the `Utilities` directory, and documentation is in the `Documentation` and `Utilities:Help` directories.) 5 | 6 | `Choose` takes an argument of the form `zone:server:volume` to specify the server and volume to connect to. To connect to a server over TCP/IP, the server name is an IP address or host name, and a special zone name must be used. This is `AFP over TCP`, optionally followed by a parenthesized list of option codes as given in the table below. Multiple option codes should be separated by commas. 7 | 8 | | Code | Option | 9 | |:-----|:---------------------------------| 10 | | `LR` | Use Large Reads | 11 | | `LW` | Use Large Writes | 12 | | `22` | Force AFP Version 2.2 | 13 | | `FS` | Fake Sleep to Keep Alive | 14 | | `IE` | Ignore Errors Setting File Types | 15 | 16 | As an example, the following command could be used to connect to a server on the local network as a guest, using large reads and large writes: 17 | 18 | Choose -guest "AFP over TCP (LR,LW):192.168.1.10:A2FILES" 19 | 20 | See the documentation on `Choose` for more information about its arguments, including how to specify a username and password. 21 | 22 | Note that AFPBridge is currently not able to send regular 'tickle' messages to keep AFP connections alive when a text-based shell is active. This may cause the server to terminate the connection if there is no activity for some length of time. You can avoid this by using the `FS` (Fake Sleep to Keep Alive) option, if it works with your server. 23 | 24 | AFP volumes can be unmounted using the `Eject` command, available in the same place as `Choose`. 25 | 26 | [1]: http://digisoft.callapple.org 27 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky -------------------------------------------------------------------------------- /afpbridge.c: -------------------------------------------------------------------------------- 1 | #pragma rtl 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "installcmds.h" 13 | #include "aspinterface.h" 14 | #include "asmglue.h" 15 | #include "cmdproc.h" 16 | 17 | const char bootInfoString[] = "AFPBridge v1.0b1"; 18 | LongWord version = 0x01006001; /* in rVersion format */ 19 | 20 | const char versionMessageString[] = "\pSTH~AFPBridge~Version~"; 21 | 22 | const char requestNameString[] = "\pTCP/IP~STH~AFPBridge~"; 23 | 24 | typedef struct VersionMessageRec { 25 | Word blockLen; 26 | char nameString[23]; 27 | LongWord dataBlock; 28 | } VersionMessageRec; 29 | 30 | extern Word *unloadFlagPtr; 31 | extern void resetRoutine(void); 32 | 33 | void PatchAttentionVector(void); 34 | void pollTask(void); 35 | void notificationProc(void); 36 | static pascal Word requestProc(Word reqCode, Long dataIn, Long dataOut); 37 | 38 | static struct RunQRec { 39 | Long reserved1; 40 | Word period; 41 | Word signature; 42 | Long reserved2; 43 | Byte jml; 44 | void (*proc)(void); 45 | } runQRec; 46 | 47 | #define NOTIFY_SHUTDOWN 0x20 48 | #define NOTIFY_GSOS_SWITCH 0x04 49 | 50 | static struct NotificationProcRec { 51 | Long reserved1; 52 | Word reserved2; 53 | Word Signature; 54 | Long Event_flags; 55 | Long Event_code; 56 | Byte jml; 57 | void (*proc)(void); 58 | } notificationProcRec; 59 | 60 | #define SoftResetPtr ((LongWord *)0xE11010) 61 | extern LongWord oldSoftReset; 62 | 63 | #define busyFlagPtr ((Byte*)0xE100FF) 64 | 65 | #define JML 0x5C 66 | 67 | void setUnloadFlag(void) { 68 | if (*unloadFlagPtr == 0) 69 | *unloadFlagPtr = 1; 70 | } 71 | 72 | int main(void) { 73 | unsigned int i; 74 | FSTInfoRecGS fstInfoRec; 75 | NotifyProcRecGS addNotifyProcRec; 76 | VersionMessageRec versionMessageRec; 77 | 78 | /* 79 | * Check for presence of AppleShare FST. We error out and unload 80 | * if it's not present. Our code doesn't directly depend on the 81 | * AppleShare FST, but in practice it's not useful without it. 82 | * This also ensures lower-level AppleTalk stuff is present. 83 | */ 84 | fstInfoRec.pCount = 2; 85 | fstInfoRec.fileSysID = 0; 86 | for (i = 1; fstInfoRec.fileSysID != appleShareFSID; i++) { 87 | fstInfoRec.fstNum = i; 88 | GetFSTInfoGS(&fstInfoRec); 89 | if (toolerror() == paramRangeErr) 90 | goto error; 91 | } 92 | 93 | /* 94 | * Load Marinetti. 95 | * We may get an error if the TCPIP init isn't loaded yet, but we ignore it. 96 | * The tool stub is still loaded in that case, which is enough for now. 97 | */ 98 | LoadOneTool(54, 0x0200); 99 | if (toolerror() && toolerror() != terrINITNOTFOUND) 100 | goto error; 101 | 102 | versionMessageRec.blockLen = sizeof(versionMessageRec); 103 | memcpy(versionMessageRec.nameString, versionMessageString, 104 | sizeof(versionMessageRec.nameString)); 105 | versionMessageRec.dataBlock = version; 106 | MessageByName(TRUE, (Pointer)&versionMessageRec); 107 | if (toolerror()) 108 | goto error; 109 | 110 | ShowBootInfo(bootInfoString, NULL); 111 | 112 | /* 113 | * Put Marinetti in the default TPT so its tool stub won't be unloaded, 114 | * even if UnloadOneTool is called on it. Programs may still call 115 | * TCPIPStartUp and TCPIPShutDown, but those don't actually do 116 | * anything, so the practical effect is that Marinetti will always 117 | * be available once its init has loaded (which may not have happened 118 | * yet when this init loads). 119 | */ 120 | SetDefaultTPT(); 121 | 122 | RamForbid(); 123 | installCmds(); 124 | RamPermit(); 125 | 126 | runQRec.period = 1; 127 | runQRec.signature = 0xA55A; 128 | runQRec.jml = JML; 129 | runQRec.proc = pollTask; 130 | AddToRunQ((Pointer)&runQRec); 131 | 132 | notificationProcRec.Signature = 0xA55A; 133 | notificationProcRec.Event_flags = NOTIFY_SHUTDOWN | NOTIFY_GSOS_SWITCH; 134 | notificationProcRec.jml = JML; 135 | notificationProcRec.proc = notificationProc; 136 | addNotifyProcRec.pCount = 1; 137 | addNotifyProcRec.procPointer = (ProcPtr)¬ificationProcRec; 138 | AddNotifyProcGS(&addNotifyProcRec); 139 | 140 | AcceptRequests(requestNameString, userid(), &requestProc); 141 | 142 | oldSoftReset = *SoftResetPtr; 143 | *SoftResetPtr = ((LongWord)&resetRoutine << 8) | JML; 144 | 145 | PatchAttentionVector(); 146 | 147 | return 0; 148 | 149 | error: 150 | setUnloadFlag(); 151 | } 152 | 153 | /* 154 | * Install our own attention vector that bypasses the one in the 155 | * ATalk driver in problematic cases (for session number > 8). 156 | */ 157 | void PatchAttentionVector(void) { 158 | PFIHooksRec pfiHooksRec; 159 | 160 | pfiHooksRec.async = 0; 161 | pfiHooksRec.command = pfiHooksCommand; 162 | pfiHooksRec.hookFlag = 0; /* get hooks */ 163 | _CALLAT(&pfiHooksRec); 164 | if (pfiHooksRec.result == 0) { 165 | jmlOldAttentionVec = (pfiHooksRec.attentionVector << 8) | JML; 166 | pfiHooksRec.attentionVector = (LongWord)&attentionVec; 167 | pfiHooksRec.hookFlag = pfiHooksSetHooks; /* set hooks, GS/OS mode */ 168 | _CALLAT(&pfiHooksRec); 169 | } 170 | } 171 | 172 | 173 | #pragma databank 1 174 | void pollTask(void) { 175 | Word stateReg; 176 | 177 | IncBusyFlag(); 178 | stateReg = ForceRomIn(); 179 | 180 | if (OS_KIND == KIND_GSOS) 181 | PollAllSessions(); 182 | runQRec.period = 4*60; 183 | 184 | RestoreStateReg(stateReg); 185 | DecBusyFlag(); 186 | } 187 | #pragma databank 0 188 | 189 | 190 | /* 191 | * Notification procedure called at shutdown time or when switching to GS/OS. 192 | * 193 | * We try to notify the servers that we're closing the connections at shutdown. 194 | * This only works if Marinetti is still active, i.e. if its own shutdown 195 | * notification procedure hasn't run yet. 196 | * 197 | * When switching back from P8, we reinstall our attention vector patch. 198 | */ 199 | #pragma databank 1 200 | void notificationProc(void) { 201 | Word stateReg; 202 | 203 | IncBusyFlag(); 204 | stateReg = ForceRomIn(); 205 | 206 | if (notificationProcRec.Event_code & NOTIFY_GSOS_SWITCH) { 207 | PatchAttentionVector(); 208 | } 209 | if (notificationProcRec.Event_code & NOTIFY_SHUTDOWN) { 210 | CloseAllSessions(aspAttenClosed, TRUE); 211 | } 212 | 213 | RestoreStateReg(stateReg); 214 | DecBusyFlag(); 215 | } 216 | #pragma databank 0 217 | 218 | #pragma databank 1 219 | void handleDisconnect(void) { 220 | Word stateReg; 221 | 222 | IncBusyFlag(); 223 | stateReg = ForceRomIn(); 224 | 225 | CloseAllSessions(aspAttenClosed, FALSE); 226 | 227 | RestoreStateReg(stateReg); 228 | DecBusyFlag(); 229 | } 230 | #pragma databank 0 231 | 232 | /* 233 | * Request procedure called by Marinetti with its notifications. 234 | * If the network has gone down, we immediately close all sessions. 235 | * We check the busy flag to avoid doing this within our code 236 | * (which runs with the busy flag set), although I don't think that 237 | * can really happen with current versions of Marinetti. 238 | */ 239 | #pragma databank 1 240 | #pragma toolparms 1 241 | static pascal Word requestProc(Word reqCode, Long dataIn, Long dataOut) { 242 | if (reqCode == TCPIPSaysNetworkDown) { 243 | if (*busyFlagPtr) { 244 | SchAddTask(&handleDisconnect); 245 | } else { 246 | handleDisconnect(); 247 | } 248 | } 249 | 250 | return 0; 251 | } 252 | #pragma toolparms 0 253 | #pragma databank 0 254 | -------------------------------------------------------------------------------- /afpbridge.rez: -------------------------------------------------------------------------------- 1 | #include "types.rez" 2 | 3 | resource rVersion (1) { 4 | { 1,0,0,beta,1 }, 5 | verUS, 6 | "AFPBridge", 7 | "By Stephen Heumann" 8 | }; 9 | 10 | resource rComment (1) { 11 | "AFPBridge allows you to connect to AFP file servers using TCP/IP. " 12 | "To use it, install this file in the *:System:System.Setup folder " 13 | "and then reboot.\n" 14 | "\n" 15 | "Copyright 2017 Stephen Heumann\n" 16 | "\n" 17 | "This program contains material from the ORCA/C Run-Time Libraries, " 18 | "copyright 1987-1996 by Byte Works, Inc. Used with permission." 19 | }; 20 | -------------------------------------------------------------------------------- /afpcdev.c: -------------------------------------------------------------------------------- 1 | #pragma cdev cdevMain 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include "afpoptions.h" 24 | #include "afpurlparser.h" 25 | #include "strncasecmp.h" 26 | #include "cdevutil.h" 27 | 28 | #define MachineCDEV 1 29 | #define BootCDEV 2 30 | #define InitCDEV 4 31 | #define CloseCDEV 5 32 | #define EventsCDEV 6 33 | #define CreateCDEV 7 34 | #define AboutCDEV 8 35 | #define RectCDEV 9 36 | #define HitCDEV 10 37 | #define RunCDEV 11 38 | #define EditCDEV 12 39 | 40 | #define serverAddressTxt 2 41 | #define urlLine 3 42 | #define saveAliasBtn 4 43 | #define connectBtn 1 44 | #define optionsPopUp 6 45 | #define trianglePic 7 46 | 47 | #define saveFilePrompt 100 48 | 49 | #define optionsMenu 300 50 | #define afpOverTCPOptionsItem 301 51 | #define useLargeReadsItem 302 52 | #define forceAFP22Item 303 53 | #define fakeSleepItem 304 54 | #define useLargeWritesItem 305 55 | #define ignoreErrorsSettingFileTypesItem 306 56 | 57 | #define fstMissingError 3000 58 | #define noEasyMountError 3001 59 | #define tempFileError 3002 60 | #define aliasFileError 3003 61 | #define tempFileNameError 3004 62 | #define saveAliasError 3005 63 | #define noAFPBridgeError 3006 64 | #define noAFPBridgeWarning 3007 65 | 66 | #define EM_filetype 0xE2 67 | #define EM_auxtype 0xFFFF 68 | 69 | FSTInfoRecGS fstInfoRec; 70 | 71 | char urlBuf[257]; 72 | 73 | WindowPtr wPtr = NULL; 74 | 75 | /* System 6.0-style EasyMount rec, as documented in its FTN. */ 76 | typedef struct EasyMountRec { 77 | char entity[97]; 78 | char volume[28]; 79 | char user[32]; 80 | char password[8]; 81 | char volpass[8]; 82 | } EasyMountRec; 83 | 84 | EasyMountRec easyMountRec; 85 | 86 | typedef struct GSString1024 { 87 | Word length; 88 | char text[1024]; 89 | } GSString1024; 90 | 91 | typedef struct ResultBuf1024 { 92 | Word bufSize; 93 | GSString1024 bufString; 94 | } ResultBuf1024; 95 | 96 | CreateRecGS createRec; 97 | OpenRecGS openRec; 98 | IORecGS writeRec; 99 | RefNumRecGS closeRec; 100 | NameRecGS destroyRec; 101 | RefInfoRecGS getRefInfoRec; 102 | 103 | ResultBuf1024 filename = { sizeof(ResultBuf1024) }; 104 | 105 | char tempFileName[] = "AFPMounter.Temp"; 106 | 107 | finderSaysBeforeOpenIn fsboRec; 108 | 109 | GSString32 origNameString; 110 | SFReplyRec2 sfReplyRec; 111 | 112 | Word modifiers = 0; 113 | 114 | char zoneBuf[ZONE_MAX + 1]; 115 | char AFPOverTCPZone[] = "AFP over TCP"; 116 | 117 | /* Default flags for AFP over TCP connections */ 118 | unsigned int flags = fLargeReads; 119 | 120 | void fillEasyMountRec(char *server, char *zone, char *volume, char *user, 121 | char *password, char *volpass) 122 | { 123 | unsigned int i; 124 | char *next; 125 | 126 | memset(&easyMountRec, 0, sizeof(easyMountRec)); 127 | 128 | i = 0; 129 | next = server; 130 | easyMountRec.entity[i++] = strlen(next); 131 | while (*next != 0 && i < sizeof(easyMountRec.entity) - 2) { 132 | easyMountRec.entity[i++] = *next++; 133 | } 134 | next = "AFPServer"; 135 | easyMountRec.entity[i++] = strlen(next); 136 | while (*next != 0 && i < sizeof(easyMountRec.entity) - 1) { 137 | easyMountRec.entity[i++] = *next++; 138 | } 139 | next = zone; 140 | easyMountRec.entity[i++] = strlen(next); 141 | while (*next != 0 && i < sizeof(easyMountRec.entity)) { 142 | easyMountRec.entity[i++] = *next++; 143 | } 144 | 145 | easyMountRec.volume[0] = strlen(volume); 146 | strncpy(&easyMountRec.volume[1], volume, sizeof(easyMountRec.volume) - 1); 147 | 148 | easyMountRec.user[0] = strlen(user); 149 | strncpy(&easyMountRec.user[1], user, sizeof(easyMountRec.user) - 1); 150 | 151 | strncpy(&easyMountRec.password[0], password, sizeof(easyMountRec.password)); 152 | 153 | strncpy(&easyMountRec.volpass[0], volpass, sizeof(easyMountRec.volpass)); 154 | } 155 | 156 | int deleteAlias(GSString255Ptr file, Boolean overwrite) 157 | { 158 | if (overwrite) { 159 | /* 160 | * Zero out the data before deleting, so password isn't left 161 | * in the unallocated disk block. 162 | */ 163 | memset(&easyMountRec, 0, sizeof(easyMountRec)); 164 | 165 | openRec.pCount = 3; 166 | openRec.pathname = file; 167 | openRec.requestAccess = writeEnable; 168 | OpenGS(&openRec); 169 | if (toolerror()) 170 | goto destroy; 171 | 172 | writeRec.pCount = 5; 173 | writeRec.refNum = openRec.refNum; 174 | writeRec.dataBuffer = (Pointer)&easyMountRec; 175 | writeRec.requestCount = sizeof(EasyMountRec); 176 | writeRec.cachePriority = cacheOff; 177 | WriteGS(&writeRec); 178 | if (toolerror()) 179 | goto destroy; 180 | 181 | closeRec.pCount = 1; 182 | closeRec.refNum = openRec.refNum; 183 | CloseGS(&closeRec); 184 | if (toolerror()) 185 | goto destroy; 186 | } 187 | 188 | destroy: 189 | destroyRec.pCount = 1; 190 | destroyRec.pathname = file; 191 | DestroyGS(&destroyRec); 192 | return toolerror(); 193 | } 194 | 195 | int writeAlias(GSString255Ptr file) 196 | { 197 | int err; 198 | 199 | createRec.pCount = 6; 200 | createRec.pathname = file; 201 | createRec.access = readEnable|writeEnable|renameEnable|destroyEnable; 202 | createRec.fileType = EM_filetype; 203 | createRec.auxType = EM_auxtype; 204 | createRec.storageType = standardFile; 205 | createRec.eof = sizeof(EasyMountRec); 206 | CreateGS(&createRec); 207 | if ((err = toolerror())) 208 | return err; 209 | 210 | openRec.pCount = 3; 211 | openRec.pathname = file; 212 | openRec.requestAccess = writeEnable; 213 | OpenGS(&openRec); 214 | if ((err = toolerror())) { 215 | deleteAlias(file, FALSE); 216 | return err; 217 | } 218 | 219 | writeRec.pCount = 5; 220 | writeRec.refNum = openRec.refNum; 221 | writeRec.dataBuffer = (Pointer)&easyMountRec; 222 | writeRec.requestCount = sizeof(EasyMountRec); 223 | writeRec.cachePriority = cacheOn; 224 | WriteGS(&writeRec); 225 | if ((err = toolerror())) { 226 | deleteAlias(file, TRUE); 227 | return err; 228 | } 229 | 230 | closeRec.pCount = 1; 231 | closeRec.refNum = openRec.refNum; 232 | CloseGS(&closeRec); 233 | if ((err = toolerror())) { 234 | deleteAlias(file, TRUE); 235 | return err; 236 | } 237 | 238 | return 0; 239 | } 240 | 241 | void finalizeURLParts(AFPURLParts *urlParts) 242 | { 243 | unsigned int i; 244 | 245 | if (urlParts->server == NULL) 246 | urlParts->server = ""; 247 | if (urlParts->zone == NULL) 248 | urlParts->zone = "*"; 249 | if (urlParts->username == NULL) 250 | urlParts->username = ""; 251 | if (urlParts->password == NULL) 252 | urlParts->password = ""; 253 | if (urlParts->auth == NULL) 254 | urlParts->auth = ""; 255 | if (urlParts->volpass == NULL) 256 | urlParts->volpass = ""; 257 | if (urlParts->volume == NULL) 258 | urlParts->volume = ""; 259 | 260 | /* 261 | * Guess the protocol if it's not explicitly specified: 262 | * If server name contains a dot it's probably an IP address 263 | * or domain name, so use TCP. Otherwise use AppleTalk. 264 | */ 265 | if (urlParts->protocol == proto_unknown) { 266 | if (strchr(urlParts->server, '.') != NULL) { 267 | urlParts->protocol = proto_TCP; 268 | } else { 269 | urlParts->protocol = proto_AT; 270 | } 271 | } 272 | 273 | /* 274 | * Assemble zone name for TCP, including any options. 275 | * Note that the generated zone name can't be over 32 characters. 276 | */ 277 | if (urlParts->protocol == proto_TCP) { 278 | strcpy(zoneBuf, AFPOverTCPZone); 279 | if (flags != 0) { 280 | strcat(zoneBuf, " ("); 281 | 282 | for (i = 0; afpOptions[i].optString != NULL; i++) { 283 | if (afpOptions[i].flag & flags) { 284 | strcat(zoneBuf, afpOptions[i].optString); 285 | strcat(zoneBuf, ","); 286 | } 287 | } 288 | zoneBuf[strlen(zoneBuf) - 1] = ')'; 289 | } 290 | urlParts->zone = zoneBuf; 291 | } 292 | } 293 | 294 | AFPURLParts prepareURL(char *url) { 295 | int result; 296 | AFPURLParts urlParts; 297 | 298 | urlParts = parseAFPURL(url); 299 | result = validateAFPURL(&urlParts); 300 | if (result != 0) { 301 | AlertWindow(awResource+awButtonLayout, NULL, result); 302 | urlParts.protocol = proto_invalid; 303 | return urlParts; 304 | } 305 | finalizeURLParts(&urlParts); 306 | return urlParts; 307 | } 308 | 309 | void ConnectOrSave(AFPURLParts* urlParts, GSString255Ptr file, Boolean connect) 310 | { 311 | Word recvCount; 312 | 313 | fillEasyMountRec(urlParts->server, urlParts->zone, urlParts->volume, 314 | urlParts->username, urlParts->password, urlParts->volpass); 315 | 316 | if (writeAlias(file) != 0) { 317 | AlertWindow(awResource+awButtonLayout, NULL, 318 | connect ? tempFileError : aliasFileError); 319 | return; 320 | } 321 | 322 | if (connect) { 323 | recvCount = 0; 324 | fsboRec.pCount = 7; 325 | fsboRec.pathname = (pointer)file; 326 | fsboRec.zoomRect = 0; 327 | fsboRec.filetype = EM_filetype; 328 | fsboRec.auxtype = EM_auxtype; 329 | fsboRec.modifiers = modifiers; 330 | fsboRec.theIconObj = 0; 331 | fsboRec.printFlag = 0; 332 | SendRequest(finderSaysBeforeOpen, sendToAll+stopAfterOne, 0, 333 | (Long)&fsboRec, (Ptr)&recvCount); 334 | 335 | deleteAlias(file, TRUE); 336 | 337 | if (recvCount == 0) { 338 | AlertWindow(awResource+awButtonLayout, NULL, noEasyMountError); 339 | return; 340 | } 341 | } 342 | } 343 | 344 | Boolean checkVersions(void) 345 | { 346 | static struct { 347 | Word blockLen; 348 | char nameString[23]; 349 | } messageRec = { sizeof(messageRec), "\pSTH~AFPBridge~Version~" }; 350 | 351 | /* Check if AFPBridge is active (any version is OK) */ 352 | MessageByName(FALSE, (Pointer)&messageRec); 353 | if (toolerror()) 354 | return FALSE; 355 | 356 | /* Check for Marinetti (AFPBridge will keep it active if installed) */ 357 | if (!TCPIPStatus() || toolerror()) 358 | return FALSE; 359 | 360 | return TRUE; 361 | } 362 | 363 | void DoConnect(void) 364 | { 365 | Word i; 366 | char *lastColon; 367 | AFPURLParts urlParts; 368 | Boolean completedOK = FALSE; 369 | CtlRecHndl ctl; 370 | 371 | GetLETextByID(wPtr, urlLine, (StringPtr)&urlBuf); 372 | urlParts = prepareURL(urlBuf+1); 373 | if (urlParts.protocol == proto_invalid) 374 | goto fixcaret; 375 | 376 | if (urlParts.protocol == proto_TCP && !checkVersions()) { 377 | AlertWindow(awResource+awButtonLayout, NULL, noAFPBridgeError); 378 | goto fixcaret; 379 | } 380 | 381 | /* Generate the path name for the temp file in same dir as the CDev */ 382 | getRefInfoRec.pCount = 3; 383 | getRefInfoRec.refNum = GetOpenFileRefNum(0); /* current resource file */ 384 | getRefInfoRec.pathname = (ResultBuf255Ptr)&filename; 385 | GetRefInfoGS(&getRefInfoRec); 386 | if (toolerror()) 387 | goto err; 388 | if (filename.bufString.length > filename.bufSize - 5 - strlen(tempFileName)) 389 | goto err; 390 | filename.bufString.text[filename.bufString.length] = 0; 391 | lastColon = strrchr(filename.bufString.text, ':'); 392 | if (lastColon == NULL) 393 | goto err; 394 | strcpy(lastColon + 1, tempFileName); 395 | filename.bufString.length = strlen(filename.bufString.text); 396 | 397 | ConnectOrSave(&urlParts, (GSString255Ptr)&filename.bufString, TRUE); 398 | completedOK = TRUE; 399 | 400 | err: 401 | /* Most error cases here should be impossible or very unlikely. */ 402 | if (!completedOK) 403 | AlertWindow(awResource+awButtonLayout, NULL, tempFileNameError); 404 | 405 | fixcaret: 406 | /* Work around issue where parts of the LE caret may flash out of sync */ 407 | ctl = GetCtlHandleFromID(wPtr, urlLine); 408 | LEDeactivate((LERecHndl) GetCtlTitle(ctl)); 409 | if (FindTargetCtl() == ctl) { 410 | LEActivate((LERecHndl) GetCtlTitle(ctl)); 411 | } 412 | } 413 | 414 | void DoSave(void) 415 | { 416 | Boolean loadedSF = FALSE, startedSF = FALSE, completedOK = FALSE; 417 | Handle dpSpace; 418 | AFPURLParts urlParts; 419 | CtlRecHndl ctl; 420 | 421 | GetLETextByID(wPtr, urlLine, (StringPtr)&urlBuf); 422 | urlParts = prepareURL(urlBuf+1); 423 | if (urlParts.protocol == proto_invalid) 424 | return; 425 | 426 | if (urlParts.protocol == proto_TCP && !checkVersions()) { 427 | if (AlertWindow(awResource+awButtonLayout, NULL, noAFPBridgeWarning)==0) 428 | return; 429 | } 430 | 431 | /* Load Standard File toolset if necessary */ 432 | if (!SFStatus() || toolerror()) { 433 | if (toolerror()) 434 | loadedSF = TRUE; 435 | LoadOneTool(0x17, 0x0303); /* Standard File */ 436 | if (toolerror()) 437 | goto err; 438 | dpSpace = NewHandle(0x0100, GetCurResourceApp(), 439 | attrLocked|attrFixed|attrNoCross|attrBank|attrPage, 0x000000); 440 | if (toolerror()) 441 | goto err; 442 | SFStartUp(GetCurResourceApp(), (Word) *dpSpace); 443 | startedSF = TRUE; 444 | } 445 | 446 | /* Initially proposed file name = volume name */ 447 | origNameString.length = strlen(urlParts.volume); 448 | strcpy(origNameString.text, urlParts.volume); /* OK since VOLUME_MAX < 32 */ 449 | 450 | /* Get the file name */ 451 | memset(&sfReplyRec, 0, sizeof(sfReplyRec)); 452 | sfReplyRec.nameRefDesc = refIsNewHandle; 453 | sfReplyRec.pathRefDesc = refIsPointer; 454 | sfReplyRec.pathRef = (Ref) &filename; 455 | SFPutFile2(GetMasterSCB() & scbColorMode ? 160 : 25, 40, 456 | refIsResource, saveFilePrompt, 457 | refIsPointer, (Ref)&origNameString, &sfReplyRec); 458 | if (toolerror()) { 459 | if (sfReplyRec.good) 460 | DisposeHandle((Handle)sfReplyRec.nameRef); 461 | goto err; 462 | } 463 | 464 | /* Save the file, unless user canceled */ 465 | if (sfReplyRec.good) { 466 | DisposeHandle((Handle)sfReplyRec.nameRef); 467 | deleteAlias((GSString255Ptr)&filename.bufString, FALSE); 468 | ConnectOrSave(&urlParts, (GSString255Ptr)&filename.bufString, FALSE); 469 | } 470 | completedOK = TRUE; 471 | 472 | err: 473 | if (!completedOK) 474 | AlertWindow(awResource+awButtonLayout, NULL, saveAliasError); 475 | if (startedSF) { 476 | SFShutDown(); 477 | DisposeHandle(dpSpace); 478 | } 479 | if (loadedSF) 480 | UnloadOneTool(0x17); 481 | 482 | /* Work around issue where parts of the LE caret may flash out of sync */ 483 | ctl = GetCtlHandleFromID(wPtr, urlLine); 484 | LEDeactivate((LERecHndl) GetCtlTitle(ctl)); 485 | if (FindTargetCtl() == ctl) { 486 | LEActivate((LERecHndl) GetCtlTitle(ctl)); 487 | } 488 | } 489 | 490 | void DoHit(long ctlID, CtlRecHndl ctlHandle) 491 | { 492 | CtlRecHndl oldMenuBar; 493 | Word menuItem; 494 | 495 | if (!wPtr) /* shouldn't happen */ 496 | return; 497 | 498 | if (ctlID == connectBtn) { 499 | DoConnect(); 500 | } else if (ctlID == saveAliasBtn) { 501 | DoSave(); 502 | } else if (ctlID == optionsPopUp) { 503 | oldMenuBar = GetMenuBar(); 504 | SetMenuBar(ctlHandle); 505 | menuItem = GetCtlValue(ctlHandle); 506 | 507 | if (menuItem == useLargeReadsItem) { 508 | flags ^= fLargeReads; 509 | CheckMItem((flags & fLargeReads) ? TRUE : FALSE, useLargeReadsItem); 510 | } else if (menuItem == forceAFP22Item) { 511 | flags ^= fForceAFP22; 512 | CheckMItem((flags & fForceAFP22) ? TRUE : FALSE, forceAFP22Item); 513 | } else if (menuItem == fakeSleepItem) { 514 | flags ^= fFakeSleep; 515 | CheckMItem((flags & fFakeSleep) ? TRUE : FALSE, fakeSleepItem); 516 | } else if (menuItem == useLargeWritesItem) { 517 | flags ^= fLargeWrites; 518 | CheckMItem((flags & fLargeWrites) ? TRUE : FALSE, 519 | useLargeWritesItem); 520 | } else if (menuItem == ignoreErrorsSettingFileTypesItem) { 521 | flags ^= fIgnoreFileTypeErrors; 522 | CheckMItem((flags & fIgnoreFileTypeErrors) ? TRUE : FALSE, 523 | ignoreErrorsSettingFileTypesItem); 524 | } 525 | 526 | SetCtlValue(afpOverTCPOptionsItem, ctlHandle); 527 | SetMenuBar(oldMenuBar); 528 | } 529 | 530 | return; 531 | } 532 | 533 | long DoMachine(void) 534 | { 535 | unsigned int i; 536 | 537 | /* Check for presence of AppleShare FST. */ 538 | fstInfoRec.pCount = 2; 539 | fstInfoRec.fileSysID = 0; 540 | for (i = 1; fstInfoRec.fileSysID != appleShareFSID; i++) { 541 | fstInfoRec.fstNum = i; 542 | GetFSTInfoGS(&fstInfoRec); 543 | if (toolerror() == paramRangeErr) { 544 | InitCursor(); 545 | AlertWindow(awResource+awButtonLayout, NULL, fstMissingError); 546 | return 0; 547 | } 548 | } 549 | 550 | return 1; 551 | } 552 | 553 | void DoEdit(Word op) 554 | { 555 | CtlRecHndl ctl; 556 | GrafPortPtr port; 557 | 558 | if (!wPtr) 559 | return; 560 | port = GetPort(); 561 | SetPort(wPtr); 562 | 563 | ctl = FindTargetCtl(); 564 | if (toolerror() || GetCtlID(ctl) != urlLine) 565 | goto ret; 566 | 567 | switch (op) { 568 | case cutAction: LECut((LERecHndl) GetCtlTitle(ctl)); 569 | if (LEGetScrapLen() > 0) 570 | LEToScrap(); 571 | break; 572 | case copyAction: LECopy((LERecHndl) GetCtlTitle(ctl)); 573 | if (LEGetScrapLen() > 0) 574 | LEToScrap(); 575 | break; 576 | case pasteAction: LEFromScrap(); 577 | LEPaste((LERecHndl) GetCtlTitle(ctl)); 578 | break; 579 | case clearAction: LEDelete((LERecHndl) GetCtlTitle(ctl)); 580 | break; 581 | } 582 | 583 | ret: 584 | SetPort(port); 585 | } 586 | 587 | void DoCreate(WindowPtr windPtr) 588 | { 589 | int mode; 590 | 591 | wPtr = windPtr; 592 | mode = (GetMasterSCB() & scbColorMode) ? 640 : 320; 593 | NewControl2(wPtr, resourceToResource, mode); 594 | } 595 | 596 | LongWord cdevMain (LongWord data2, LongWord data1, Word message) 597 | { 598 | long result = 0; 599 | 600 | switch(message) { 601 | case MachineCDEV: result = DoMachine(); break; 602 | case HitCDEV: DoHit(data2, (CtlRecHndl)data1); break; 603 | case EditCDEV: DoEdit(data1 & 0xFFFF); break; 604 | case CreateCDEV: DoCreate((WindowPtr)data1); break; 605 | case CloseCDEV: wPtr = NULL; break; 606 | case EventsCDEV: /* Now done in assembly for speed. Equivalent to: */ 607 | /* modifiers = ((EventRecordPtr)data1)->modifiers; */ 608 | break; 609 | } 610 | 611 | ret: 612 | FreeAllCDevMem(); 613 | return result; 614 | } 615 | -------------------------------------------------------------------------------- /afpcdev.rez: -------------------------------------------------------------------------------- 1 | #include "types.rez" 2 | 3 | resource rVersion (1) { 4 | { 1,0,0,beta,1 }, 5 | verUS, 6 | "AFP Mounter", 7 | "By Stephen Heumann" 8 | }; 9 | 10 | resource rComment (1) { 11 | "The AFP Mounter control panel allows you to connect to " 12 | "file servers using the Apple Filing Protocol (AFP) " 13 | "over either AppleTalk or TCP/IP.\n" 14 | "\n" 15 | "Copyright 2017 Stephen Heumann\n" 16 | "\n" 17 | "This program contains material from the ORCA/C Run-Time Libraries, " 18 | "copyright 1987-1996 by Byte Works, Inc. Used with permission." 19 | }; 20 | 21 | resource rCDEVFlags (1) { 22 | wantMachine+wantHit+wantClose+wantEvents+wantEdit+wantCreate, 23 | 1, /* enabled */ 24 | 1, /* version */ 25 | 1, /* min ROM version */ 26 | 0, /* reserved */ 27 | {0, 0, 55, 320}, /* rectangle */ 28 | "AFP Mounter", /* name */ 29 | "Stephen Heumann", /* author */ 30 | "v1.0b1" /* version string */ 31 | }; 32 | 33 | read rCDevCode (0x1,convert,locked) "AFPMounter.obj"; 34 | 35 | resource rIcon (1) { 36 | 0x8000, /* color icon */ 37 | 20, /* dimensions */ 38 | 28, 39 | $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF" 40 | $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF" 41 | $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF" 42 | $"F00000000FFFFFFFFFFFFFFFFFFF" 43 | $"F0DDDDDD0FFFFFFFFFFFFFFFFFFF" 44 | $"F0DDDDDD04FFFFFFFF4FFFFFFFFF" 45 | $"F0DDDDDD04FFFFFFFF4F0FF000FF" 46 | $"F0DDDDDD444FFFFF0444A0F0DD0F" 47 | $"F0000000444FFFF0E4440A00DD0F" 48 | $"FF0FFFF4F4F4FFF04E404AA0DD0F" 49 | $"F000000404F4FFF04E404A00DD0F" 50 | $"F0F4FF4F04FF4F04004004000000" 51 | $"F000040004FFF44303433343330F" 52 | $"4444444444444444444444444444" 53 | $"3333333334333333334333333333" 54 | $"4444444444444444444444444444" 55 | $"FFFFFFFFF4FFFFFFFF4FFFFFFFFF" 56 | $"FFFFFFFFF4FFFFFFFF4FFFFFFFFF" 57 | $"FFFFFFFFF4FFFFFFFF4FFFFFFFFF" 58 | $"FFFFFFFFF4FFFFFFFF4FFFFFFFFF", 59 | 60 | $"0000000000000000000000000000" 61 | $"0000000000000000000000000000" 62 | $"0000000000000000000000000000" 63 | $"0FFFFFFFF0000000000000000000" 64 | $"0FFFFFFFF0000000000000000000" 65 | $"0FFFFFFFFF00000000F000000000" 66 | $"0FFFFFFFFF00000000F0F00FFF00" 67 | $"0FFFFFFFFFF00000FFFFFF0FFFF0" 68 | $"0FFFFFFFFFF0000FFFFFFFFFFFF0" 69 | $"00FFFFFF0F0F000FFFFFFFFFFFF0" 70 | $"0FFFFFFFFF0F000FFFFFFFFFFFF0" 71 | $"0FFFFFFFFF00F0FFFFFFFFFFFFFF" 72 | $"0FFFFFFFFF000FFFFFFFFFFFFFF0" 73 | $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF" 74 | $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF" 75 | $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF" 76 | $"000000000F00000000F000000000" 77 | $"000000000F00000000F000000000" 78 | $"000000000F00000000F000000000" 79 | $"000000000F00000000F000000000" 80 | }; 81 | 82 | #define cdevWindow 1000 83 | #define helpWindow 2000 84 | 85 | #define serverAddressTxt 2 86 | #define urlLine 3 87 | #define saveAliasBtn 4 88 | #define connectBtn 1 89 | #define optionsPopUp 6 90 | #define trianglePic 7 91 | 92 | #define helpTxt 5 93 | 94 | #define saveFilePrompt 100 95 | 96 | #define optionsMenu 300 97 | #define afpOverTCPOptionsItem 301 98 | #define useLargeReadsItem 302 99 | #define forceAFP22Item 303 100 | #define fakeSleepItem 304 101 | #define useLargeWritesItem 305 102 | #define ignoreErrorsSettingFileTypesItem 306 103 | 104 | /* 105 | * Controls in the control panel window (for 640 mode or 320 mode) 106 | */ 107 | resource rControlList (640) { 108 | { 109 | cdevWindow+serverAddressTxt, 110 | cdevWindow+urlLine, 111 | cdevWindow+saveAliasBtn, 112 | cdevWindow+connectBtn, 113 | cdevWindow+trianglePic, 114 | cdevWindow+optionsPopUp 115 | }; 116 | }; 117 | 118 | resource rControlList (320) { 119 | { 120 | cdevWindow+serverAddressTxt, 121 | cdevWindow+urlLine, 122 | cdevWindow+saveAliasBtn, 123 | cdevWindow+connectBtn, 124 | cdevWindow+trianglePic+320, 125 | cdevWindow+optionsPopUp+320 126 | }; 127 | }; 128 | 129 | resource rControlTemplate (cdevWindow+serverAddressTxt) { 130 | serverAddressTxt, /* control ID */ 131 | {4, 10, 15, 270}, /* control rect */ 132 | statTextControl {{ 133 | fBlastText, /* flags */ 134 | $1000+RefIsResource, /* moreFlags */ 135 | 0, /* refCon */ 136 | cdevWindow+serverAddressTxt /* title ref */ 137 | }}; 138 | }; 139 | 140 | resource rTextForLETextBox2 (cdevWindow+serverAddressTxt) { 141 | "AFP Server Address:" 142 | }; 143 | 144 | resource rControlTemplate (cdevWindow+urlLine) { 145 | urlLine, 146 | {15, 10, 28, 310}, 147 | editLineControl {{ 148 | 0, 149 | $7000+RefIsResource, 150 | 0, 151 | 255, /* max size */ 152 | cdevWindow+urlLine /* text ref */ 153 | }}; 154 | }; 155 | 156 | resource rPString (cdevWindow+urlLine) { "afp://" }; 157 | 158 | resource rControlTemplate (cdevWindow+saveAliasBtn) { 159 | saveAliasBtn, 160 | {35, 10, 0, 0}, 161 | SimpleButtonControl {{ 162 | NormalButton, 163 | $1000+RefIsResource, 164 | 0, 165 | cdevWindow+saveAliasBtn 166 | }}; 167 | }; 168 | 169 | resource rPString(cdevWindow+saveAliasBtn) { "Save Alias..." }; 170 | 171 | resource rControlTemplate (cdevWindow+connectBtn) { 172 | connectBtn, 173 | {35, 220, 0, 0}, 174 | SimpleButtonControl {{ 175 | DefaultButton, 176 | $3000+RefIsResource, 177 | 0, 178 | cdevWindow+connectBtn, 179 | 0, /* color table ref */ 180 | {"\$0D","\$0D",0,0} /* key equivalent = Return */ 181 | }}; 182 | }; 183 | 184 | resource rPString(cdevWindow+connectBtn) { "Connect" }; 185 | 186 | /* Options menu pop-up -- separate versions for 640 mode and 320 mode */ 187 | resource rControlTemplate (cdevWindow+optionsPopUp) { 188 | optionsPopUp, 189 | {5, 282, 13, 310 }, 190 | PopUpControl {{ 191 | fDontDrawTitle+fDontDrawResult, 192 | fCtlProcNotPtr+RefIsResource/*+fDrawPopDownIcon*/, 193 | 0, 194 | 0, 195 | optionsMenu, /* menu ref */ 196 | afpOverTCPOptionsItem, /* initial value */ 197 | 0 198 | }}; 199 | }; 200 | 201 | resource rControlTemplate (cdevWindow+optionsPopUp+ 320) { 202 | optionsPopUp, 203 | {5, 296, 13, 310 }, 204 | PopUpControl {{ 205 | fDontDrawTitle+fDontDrawResult, 206 | fCtlProcNotPtr+RefIsResource/*+fDrawPopDownIcon*/, 207 | 0, 208 | 0, 209 | optionsMenu, /* menu ref */ 210 | afpOverTCPOptionsItem, /* initial value */ 211 | 0 212 | }}; 213 | }; 214 | 215 | /* Triangle to draw on options pop-up */ 216 | resource rControlTemplate (cdevWindow+trianglePic) { 217 | trianglePic, 218 | {7, 286, 11, 302}, 219 | PictureControl {{ 220 | CtlInactive, 221 | fCtlProcNotPtr+RefIsResource, 222 | 0, 223 | trianglePic /* picture reference */ 224 | }}; 225 | }; 226 | 227 | resource rControlTemplate (cdevWindow+trianglePic+320) { 228 | trianglePic, 229 | {7, 298, 0, 0}, 230 | PictureControl {{ 231 | CtlInactive, 232 | fCtlProcNotPtr+RefIsResource, 233 | 0, 234 | trianglePic /* picture reference */ 235 | }}; 236 | }; 237 | 238 | data rPicture (trianglePic) { 239 | $"8000 0000 0000 0400 1000 1182 0100 0A00" 240 | $"01C0 01C0 FF3F FF3F 9000 8000 0000 0400" 241 | $"0000 0000 0400 1000 0000 0000 0400 1000" 242 | $"0000 0000 0400 1000 0000 0000 0000 F000" 243 | $"000F FF00 00FF FFF0 0FFF" 244 | }; 245 | 246 | resource rMenu (optionsMenu) { 247 | optionsMenu, /* menu ID */ 248 | refIsResource*menuTitleRefShift + refIsResource*itemRefShift, 249 | optionsMenu, /* menu title ref (not drawn) */ 250 | { 251 | afpOverTCPOptionsItem, 252 | useLargeReadsItem, 253 | useLargeWritesItem, 254 | forceAFP22Item, 255 | fakeSleepItem, 256 | ignoreErrorsSettingFileTypesItem 257 | }; 258 | }; 259 | resource rPString(optionsMenu,noCrossBank) { "" }; 260 | 261 | resource rMenuItem (afpOverTCPOptionsItem) { 262 | afpOverTCPOptionsItem, /* menu item ID */ 263 | "","", 264 | 0, 265 | fDisabled+refIsResource*itemTitleRefShift, 266 | afpOverTCPOptionsItem /* menu item title ref */ 267 | }; 268 | resource rPString(afpOverTCPOptionsItem,noCrossBank) { "AFP Over TCP Options:" }; 269 | 270 | resource rMenuItem (useLargeReadsItem) { 271 | useLargeReadsItem, /* menu item ID */ 272 | "","", 273 | $12, 274 | refIsResource*itemTitleRefShift, 275 | useLargeReadsItem /* menu item title ref */ 276 | }; 277 | resource rPString(useLargeReadsItem,noCrossBank) { "Use Large Reads" }; 278 | 279 | resource rMenuItem (useLargeWritesItem) { 280 | useLargeWritesItem, /* menu item ID */ 281 | "","", 282 | 0, 283 | refIsResource*itemTitleRefShift, 284 | useLargeWritesItem /* menu item title ref */ 285 | }; 286 | resource rPString(useLargeWritesItem,noCrossBank) { "Use Large Writes" }; 287 | 288 | resource rMenuItem (forceAFP22Item) { 289 | forceAFP22Item, /* menu item ID */ 290 | "","", 291 | 0, 292 | refIsResource*itemTitleRefShift, 293 | forceAFP22Item /* menu item title ref */ 294 | }; 295 | resource rPString(forceAFP22Item,noCrossBank) { "Force AFP Version 2.2" }; 296 | 297 | resource rMenuItem (fakeSleepItem) { 298 | fakeSleepItem, /* menu item ID */ 299 | "","", 300 | 0, 301 | refIsResource*itemTitleRefShift, 302 | fakeSleepItem /* menu item title ref */ 303 | }; 304 | resource rPString(fakeSleepItem,noCrossBank) { "Fake Sleep to Keep Alive" }; 305 | 306 | resource rMenuItem (ignoreErrorsSettingFileTypesItem) { 307 | ignoreErrorsSettingFileTypesItem, /* menu item ID */ 308 | "","", 309 | 0, 310 | refIsResource*itemTitleRefShift, 311 | ignoreErrorsSettingFileTypesItem /* menu item title ref */ 312 | }; 313 | resource rPString(ignoreErrorsSettingFileTypesItem,noCrossBank) { 314 | "Ignore Errors Setting File Types" 315 | }; 316 | 317 | /* 318 | * Controls in the help window 319 | */ 320 | resource rControlList (2) { 321 | { 322 | helpWindow+helpTxt 323 | }; 324 | }; 325 | 326 | resource rControlTemplate (helpWindow+helpTxt) { 327 | helpTxt, 328 | {38, 5, 138, 280}, 329 | statTextControl {{ 330 | 0, /* flags */ 331 | $1000+RefIsResource, /* moreFlags */ 332 | 0, /* refCon */ 333 | helpWindow+helpTxt /* title ref */ 334 | }}; 335 | }; 336 | 337 | resource rTextForLETextBox2 (helpWindow+helpTxt) { 338 | "The AFP Mounter control panel allows you to connect to " 339 | "file servers using the Apple Filing Protocol (AFP) " 340 | "over either AppleTalk or TCP/IP networks. " 341 | "The server address can be specified by URLs " 342 | "of the following forms:\n\$01X\$03\$00" 343 | "afp://[user:password@]server[:port]/\n\$01X\$00\$00" 344 | "volume (to connect using TCP/IP)\n\$01X\$03\$00" 345 | "afp:/at/[user:password@]server:zone/\n\$01X\$00\$00" 346 | "volume (to connect using AppleTalk)\n" 347 | }; 348 | 349 | resource rPString(saveFilePrompt) { "Save server alias as:" }; 350 | 351 | /* 352 | * Error messages 353 | */ 354 | 355 | #define fstMissingError 3000 356 | #define noEasyMountError 3001 357 | #define tempFileError 3002 358 | #define aliasFileError 3003 359 | #define tempFileNameError 3004 360 | #define saveAliasError 3005 361 | #define noAFPBridgeError 3006 362 | #define noAFPBridgeWarning 3007 363 | 364 | #define protoInvalidError 4000 365 | #define noServerOrVolumeNameError 4001 366 | #define serverNameTooLongError 4002 367 | #define volumeNameTooLongError 4003 368 | #define zoneTooLongError 4004 369 | #define usernameTooLongError 4005 370 | #define passwordTooLongError 4006 371 | #define volpassTooLongError 4007 372 | #define passwordWithoutUserError 4008 373 | #define badUAMError 4009 374 | 375 | resource rAlertString (fstMissingError) { 376 | "72:" 377 | "To use the AFP Mounter control panel, the AppleShare FST " 378 | "and the AppleTalk-related system components it requires " 379 | "must be installed and enabled." 380 | ":^#0\$00" 381 | }; 382 | 383 | resource rAlertString (noEasyMountError) { 384 | "72:" 385 | "Communication with EasyMount failed.\n" 386 | "\n" 387 | "To use the AFP Mounter control panel, EasyMount must be " 388 | "installed and enabled." 389 | ":^#0\$00" 390 | }; 391 | 392 | resource rAlertString (protoInvalidError) { 393 | "32:" 394 | "The specified address is not a valid AFP URL." 395 | ":^#0\$00" 396 | }; 397 | 398 | resource rAlertString (noServerOrVolumeNameError) { 399 | "32:" 400 | "Please specify at least a server and volume name in the AFP URL." 401 | ":^#0\$00" 402 | }; 403 | 404 | resource rAlertString (serverNameTooLongError) { 405 | "32:" 406 | "The server name is too long (maximum 32 characters)." 407 | ":^#0\$00" 408 | }; 409 | 410 | resource rAlertString (volumeNameTooLongError) { 411 | "32:" 412 | "The volume name is too long (maximum 27 characters)." 413 | ":^#0\$00" 414 | }; 415 | 416 | resource rAlertString (zoneTooLongError) { 417 | "32:" 418 | "The zone name is too long (maximum 32 characters)." 419 | ":^#0\$00" 420 | }; 421 | 422 | resource rAlertString (usernameTooLongError) { 423 | "32:" 424 | "The username is too long (maximum 31 characters)." 425 | ":^#0\$00" 426 | }; 427 | 428 | resource rAlertString (passwordTooLongError) { 429 | "32:" 430 | "The password is too long (maximum 8 characters)." 431 | ":^#0\$00" 432 | }; 433 | 434 | resource rAlertString (volpassTooLongError) { 435 | "32:" 436 | "The volume password is too long (maximum 8 characters)." 437 | ":^#0\$00" 438 | }; 439 | 440 | resource rAlertString (passwordWithoutUserError) { 441 | "42:" 442 | "When a password is specified, a user name must also be given." 443 | ":^#0\$00" 444 | }; 445 | 446 | resource rAlertString (badUAMError) { 447 | "62:" 448 | "The requested user authentication method is not supported " 449 | "or cannot be used with the specified URL." 450 | ":^#0\$00" 451 | }; 452 | 453 | resource rAlertString (tempFileError) { 454 | "82:" 455 | "There was an error writing the temporary file used by the " 456 | "AFPMounter control panel. Please make sure the directory " 457 | "containing the AFPMounter control panel is writable and " 458 | "remove the AFPMounter.Temp file if it is present." 459 | ":^#0\$00" 460 | }; 461 | 462 | resource rAlertString (aliasFileError) { 463 | "32:" 464 | "There was an error writing the alias file." 465 | ":^#0\$00" 466 | }; 467 | 468 | resource rAlertString (tempFileNameError) { 469 | "72:" 470 | "There was an error while constructing the name for the " 471 | "temporary file used by the AFPMounter control panel." 472 | ":^#0\$00" 473 | }; 474 | 475 | resource rAlertString (saveAliasError) { 476 | "42:" 477 | "There was an error while attempting to save the alias file." 478 | ":^#0\$00" 479 | }; 480 | 481 | resource rAlertString (noAFPBridgeError) { 482 | "62:" 483 | "To connect to AFP servers over TCP, AFPBridge and Marinetti " 484 | "must be installed and enabled. Please install them and then " 485 | "restart your system." 486 | ":^#0\$00" 487 | }; 488 | 489 | resource rAlertString (noAFPBridgeWarning) { 490 | "74:" 491 | "To connect to AFP servers over TCP, AFPBridge and Marinetti " 492 | "must be installed and enabled. You can continue to save an " 493 | "alias now, but to connect to the server you will need to " 494 | "install them and then restart your system." 495 | ":#1:^#6\$00" 496 | }; 497 | -------------------------------------------------------------------------------- /afpinit.asm: -------------------------------------------------------------------------------- 1 | case on 2 | 3 | dummy private 4 | bra InitStart 5 | end 6 | 7 | unloadFlagPtr data 8 | ds 4 9 | end 10 | 11 | * This gets called when control-reset is pressed in P8 mode. 12 | * It's intended to basically reinitialize the whole AppleTalk stack. 13 | resetRoutine start 14 | jsl oldSoftReset 15 | jml ResetAllSessions 16 | end 17 | 18 | oldSoftReset start 19 | ds 4 ; to be modified 20 | end 21 | 22 | InitStart private 23 | tay 24 | tsc 25 | clc 26 | adc #4 27 | sta >unloadFlagPtr 28 | lda #0 29 | sta >unloadFlagPtr+2 30 | tya 31 | end 32 | -------------------------------------------------------------------------------- /afpmounter.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "endian.h" 6 | 7 | #define ENTITY_FIELD_MAX 32 8 | 9 | typedef struct NBPLookupResultBuf { 10 | Word networkNum; 11 | Byte nodeNum; 12 | Byte socketNum; 13 | // First enumerator/name only 14 | Byte enumerator; 15 | EntName entName; 16 | } NBPLookupResultBuf; 17 | 18 | char *object, *zone, *type; 19 | 20 | NBPLookupResultBuf lookupResultBuf; 21 | NBPLookupNameRec lookupNameRec; 22 | EntName entName; 23 | 24 | #define kFPLogin 18 25 | char *afpVersionStr, *uamStr; 26 | unsigned char fpLoginCmdRec[128]; 27 | unsigned char replyBuffer[1024]; 28 | 29 | PFILogin2Rec login2Rec; 30 | 31 | #define VOL_NAME_MAX 27 32 | #define VOL_PASSWORD_MAX 8 33 | PFIMountvolRec mountVolRec; 34 | unsigned char volName[VOL_NAME_MAX + 1]; 35 | unsigned char volPassword[VOL_PASSWORD_MAX] = {0}; 36 | 37 | PFILogoutRec logoutRec; 38 | 39 | int main(int argc, char **argv) { 40 | int i, j; 41 | int count; 42 | int zoneNameOffset; 43 | Word atRetCode; 44 | 45 | if (argc < 4 || argc > 5) { 46 | fprintf(stderr, "Usage: afpmounter name zone volume [volPassword]\n"); 47 | return EXIT_FAILURE; 48 | } 49 | 50 | object = argv[1]; 51 | type = "AFPServer"; 52 | zone = argv[2]; 53 | if (strlen(object) > ENTITY_FIELD_MAX || strlen(zone) > ENTITY_FIELD_MAX) { 54 | fprintf(stderr, "Entity name too long (max 32 chars)\n"); 55 | return EXIT_FAILURE; 56 | } 57 | 58 | count = strlen(argv[3]); 59 | if (count > VOL_NAME_MAX) { 60 | fprintf(stderr, "Volume name too long\n"); 61 | return EXIT_FAILURE; 62 | } 63 | volName[0] = count; 64 | strncpy(volName+1, argv[3], count); 65 | 66 | if (argc >= 5) { 67 | count = strlen(argv[4]); 68 | if (count > VOL_PASSWORD_MAX) { 69 | fprintf(stderr, "Volume password too long\n"); 70 | return EXIT_FAILURE; 71 | } 72 | strncpy(volPassword, argv[4], count); 73 | } 74 | 75 | lookupNameRec.async = 0; 76 | lookupNameRec.command = nbpLookupNameCommand; 77 | lookupNameRec.completionPtr = 0; 78 | lookupNameRec.entityPtr = (LongWord)&entName; 79 | lookupNameRec.rInterval = 1; 80 | lookupNameRec.rCount = 20; 81 | lookupNameRec.reserved = 0; 82 | lookupNameRec.bufferLength = sizeof(lookupResultBuf); 83 | lookupNameRec.bufferPtr = (LongWord)&lookupResultBuf; 84 | lookupNameRec.maxMatch = 1; 85 | 86 | i = 0; 87 | count = strlen(object); 88 | entName.buffer[i++] = count; 89 | for (j = 0; j < count; j++) { 90 | entName.buffer[i++] = object[j]; 91 | } 92 | count = strlen(type); 93 | entName.buffer[i++] = count; 94 | for (j = 0; j < count; j++) { 95 | entName.buffer[i++] = type[j]; 96 | } 97 | zoneNameOffset = i; 98 | count = strlen(zone); 99 | entName.buffer[i++] = count; 100 | for (j = 0; j < count; j++) { 101 | entName.buffer[i++] = zone[j]; 102 | } 103 | 104 | atRetCode = _CALLAT(&lookupNameRec); 105 | if (atRetCode != 0) { 106 | fprintf(stderr, "NBP lookup error: %04x\n", lookupNameRec.result); 107 | return EXIT_FAILURE; 108 | } 109 | 110 | if (lookupNameRec.actualMatch == 0) { 111 | fprintf(stderr, "The specified server could not be found\n"); 112 | return EXIT_FAILURE; 113 | } 114 | 115 | #if 0 116 | printf("%i matches\n", lookupNameRec.actualMatch); 117 | printf("network = %u, node = %u, socket = %u\n", 118 | ntohs(lookupResultBuf.networkNum), 119 | lookupResultBuf.nodeNum, 120 | lookupResultBuf.socketNum); 121 | #endif 122 | 123 | i = 0; 124 | fpLoginCmdRec[i++] = kFPLogin; 125 | afpVersionStr = "AFPVersion 2.0"; 126 | count = strlen(afpVersionStr); 127 | fpLoginCmdRec[i++] = count; 128 | for(j = 0; j < count; j++) { 129 | fpLoginCmdRec[i++] = afpVersionStr[j]; 130 | } 131 | uamStr = "No User Authent"; 132 | count = strlen(uamStr); 133 | fpLoginCmdRec[i++] = count; 134 | for(j = 0; j < count; j++) { 135 | fpLoginCmdRec[i++] = uamStr[j]; 136 | } 137 | 138 | login2Rec.async = 0; 139 | login2Rec.command = pfiLogin2Command; 140 | login2Rec.networkID = lookupResultBuf.networkNum; 141 | login2Rec.nodeID = lookupResultBuf.nodeNum; 142 | login2Rec.socketID = lookupResultBuf.socketNum; 143 | login2Rec.cmdBufferLength = i; 144 | login2Rec.cmdBufferPtr = (LongWord)&fpLoginCmdRec; 145 | login2Rec.replyBufferLen = sizeof(replyBuffer); 146 | login2Rec.replyBufferPtr = (LongWord)&replyBuffer; 147 | login2Rec.attnRtnAddr = 0; 148 | login2Rec.serverName = &entName.buffer[0]; 149 | login2Rec.zoneName = &entName.buffer[zoneNameOffset]; 150 | login2Rec.afpVersionNum = pfiAFPVersion20; 151 | 152 | atRetCode = _CALLAT(&login2Rec); 153 | if (atRetCode != 0) { 154 | fprintf(stderr, "Login failure: %04x\n", login2Rec.result); 155 | if (login2Rec.result == pfiLoginContErr) 156 | goto logout_and_exit; 157 | return EXIT_FAILURE; 158 | } 159 | 160 | mountVolRec.async = 0; 161 | mountVolRec.command = pfiMountVolCommand; 162 | mountVolRec.sessRefID = login2Rec.sessRefID; 163 | mountVolRec.mountflag = pfiMountMask | pfiPasswordMask; 164 | mountVolRec.volNamePtr = (LongWord)&volName; 165 | mountVolRec.passwordPtr = (LongWord)&volPassword; 166 | 167 | atRetCode = _CALLAT(&mountVolRec); 168 | if (atRetCode != 0) { 169 | fprintf(stderr, "Failed to mount volume: %04x\n", mountVolRec.result); 170 | goto logout_and_exit; 171 | } 172 | 173 | /* success - leave the volume mounted */ 174 | return EXIT_SUCCESS; 175 | 176 | logout_and_exit: 177 | logoutRec.async = 0; 178 | logoutRec.command = pfiLogOutCommand; 179 | logoutRec.sessRefID = login2Rec.sessRefID; 180 | 181 | _CALLAT(&logoutRec); 182 | return EXIT_FAILURE; 183 | } 184 | -------------------------------------------------------------------------------- /afpoptions.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include "afpoptions.h" 4 | 5 | /* 6 | * These are option codes that can be used in the zone string, e.g. 7 | * "AFP over TCP (LR,22)". Note that the zone string is limited to 32 8 | * characters. With 2-character options, we can use up to six at once. 9 | */ 10 | AFPOptions afpOptions[] = 11 | { 12 | {"LR", fLargeReads}, 13 | {"LW", fLargeWrites}, 14 | {"22", fForceAFP22}, 15 | {"FS", fFakeSleep}, 16 | {"IE", fIgnoreFileTypeErrors}, 17 | {0, 0} 18 | }; 19 | -------------------------------------------------------------------------------- /afpoptions.h: -------------------------------------------------------------------------------- 1 | #ifndef AFPOPTIONS_H 2 | #define AFPOPTIONS_H 3 | 4 | #define fLargeReads 0x0001 5 | #define fForceAFP22 0x0002 6 | #define fFakeSleep 0x0004 7 | #define fIgnoreFileTypeErrors 0x0008 8 | #define fLargeWrites 0x0010 9 | 10 | typedef struct AFPOptions { 11 | char *optString; 12 | unsigned int flag; 13 | } AFPOptions; 14 | 15 | extern AFPOptions afpOptions[]; 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /afpurlparser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "afpurlparser.h" 4 | #include "strncasecmp.h" 5 | 6 | #ifdef AFPURLPARSER_TEST 7 | # include 8 | #else 9 | # ifdef __ORCAC__ 10 | # pragma noroot 11 | # endif 12 | #endif 13 | 14 | static int hextonum(char c) { 15 | if (c >= '0' && c <= '9') 16 | return c - '0'; 17 | if (c >= 'a' && c <= 'f') 18 | return c - 'a' + 10; 19 | if (c >= 'A' && c <= 'F') 20 | return c - 'A' + 10; 21 | return 0; 22 | } 23 | 24 | static void parseEscapes(char *url) 25 | { 26 | unsigned int i, j; 27 | unsigned int len; 28 | 29 | len = strlen(url); 30 | 31 | for (i = 0, j = 0; url[i] != 0; j++) { 32 | if (url[i] == '%' && isxdigit(url[i+1]) && isxdigit(url[i+2])) 33 | { 34 | url[j] = hextonum(url[i+1]) * 16 + hextonum(url[i+2]); 35 | i += 3; 36 | } else { 37 | url[j] = url[i]; 38 | i++; 39 | } 40 | } 41 | url[j] = 0; 42 | } 43 | 44 | AFPURLParts parseAFPURL(char *url) 45 | { 46 | char *sep; 47 | AFPURLParts urlParts = {0}; 48 | 49 | parseEscapes(url); 50 | 51 | urlParts.protocol = proto_unknown; 52 | 53 | if (strncasecmp(url, "afp:", 4) == 0) 54 | url = url + 4; 55 | 56 | if (strncmp(url, "//", 2) == 0) { 57 | urlParts.protocol = proto_TCP; 58 | url += 2; 59 | } else if (strncasecmp(url, "/at/", 4) == 0) { 60 | urlParts.protocol = proto_AT; 61 | url += 4; 62 | } 63 | 64 | /* Discard extra slashes */ 65 | while (*url == '/') 66 | url++; 67 | 68 | sep = strchr(url, '@'); 69 | if (sep) { 70 | *sep = 0; 71 | urlParts.username = url; 72 | urlParts.server = sep + 1; 73 | } else { 74 | urlParts.username = NULL; 75 | urlParts.server = url; 76 | } 77 | 78 | if (urlParts.username != NULL) { 79 | sep = strchr(urlParts.username, ':'); 80 | 81 | if (sep) { 82 | *sep = 0; 83 | urlParts.password = sep + 1; 84 | } 85 | 86 | sep = strrchr(urlParts.username, ';'); 87 | if (sep && strncmp(sep, ";VOLPASS=", 9) == 0) { 88 | *sep = 0; 89 | urlParts.volpass = sep + 9; 90 | } 91 | 92 | sep = strrchr(urlParts.username, ';'); 93 | if (sep && strncmp(sep, ";AUTH=", 6) == 0) { 94 | *sep = 0; 95 | urlParts.auth = sep + 6; 96 | } 97 | } 98 | 99 | sep = strchr(urlParts.server, '/'); 100 | if (sep) { 101 | *sep = 0; 102 | urlParts.volume = sep + 1; 103 | 104 | /* strip trailing slashes */ 105 | while ((sep = strrchr(urlParts.volume, '/')) != NULL && sep[1] == 0) 106 | *sep = 0; 107 | } 108 | 109 | sep = strchr(urlParts.server, ':'); 110 | if (sep && (urlParts.protocol == proto_AT || 111 | ((urlParts.protocol == proto_unknown || urlParts.protocol == proto_TCP) 112 | && strspn(sep+1, "0123456789") != strlen(sep+1)))) 113 | { 114 | *sep = 0; 115 | urlParts.zone = sep + 1; 116 | urlParts.protocol = proto_AT; 117 | } 118 | 119 | if (urlParts.server == NULL) 120 | urlParts.protocol = proto_invalid; 121 | 122 | return urlParts; 123 | } 124 | 125 | /* Check if an AFP URL is suitable for our AFP mounter */ 126 | int validateAFPURL(AFPURLParts *urlParts) 127 | { 128 | if (urlParts->server == NULL || urlParts->volume == NULL 129 | || *urlParts->server == 0 || *urlParts->volume == 0) 130 | return noServerOrVolumeNameError; 131 | if (urlParts->protocol == proto_invalid) 132 | return protoInvalidError; 133 | if (strlen(urlParts->server) > SERVER_MAX) 134 | return serverNameTooLongError; 135 | if (strlen(urlParts->volume) > VOLUME_MAX) 136 | return volumeNameTooLongError; 137 | if (urlParts->zone != NULL && strlen(urlParts->zone) > ZONE_MAX) 138 | return zoneTooLongError; 139 | if (urlParts->username != NULL && strlen(urlParts->username) > USERNAME_MAX) 140 | return usernameTooLongError; 141 | if (urlParts->password != NULL && strlen(urlParts->password) > PASSWORD_MAX) 142 | return passwordTooLongError; 143 | if (urlParts->volpass != NULL && strlen(urlParts->volpass) > VOLPASS_MAX) 144 | return volpassTooLongError; 145 | 146 | if (urlParts->password != NULL && *urlParts->password != 0 147 | && (urlParts->username == NULL || *urlParts->username == 0)) 148 | return passwordWithoutUserError; 149 | 150 | if (urlParts->auth != NULL) { 151 | if (strncasecmp(urlParts->auth, "No User Authent", 16) == 0) { 152 | if ((urlParts->username != NULL && *urlParts->username != 0) 153 | || (urlParts->password != NULL && *urlParts->password != 0)) 154 | return badUAMError; 155 | } else if (strncasecmp(urlParts->auth, "Randnum Exchange", 17) == 0) { 156 | if (urlParts->username == NULL || *urlParts->username == 0) 157 | return badUAMError; 158 | } else { 159 | /* unknown/unsupported UAM */ 160 | return badUAMError; 161 | } 162 | } 163 | 164 | return 0; 165 | } 166 | 167 | #ifdef AFPURLPARSER_TEST 168 | int main(int argc, char **argv) 169 | { 170 | AFPURLParts urlParts; 171 | 172 | if (argc < 2) 173 | return 1; 174 | 175 | urlParts = parseAFPURL(strdup(argv[1])); 176 | 177 | printf("protocol = "); 178 | switch(urlParts.protocol) { 179 | case proto_unknown: printf("unknown\n"); break; 180 | case proto_AT: printf("AppleTalk\n"); break; 181 | case proto_TCP: printf("TCP\n"); break; 182 | case proto_invalid: printf("invalid URL\n"); break; 183 | default: printf("Bad URL structure\n"); break; 184 | } 185 | 186 | printf("server: %s\n", urlParts.server); 187 | printf("zone: %s\n", urlParts.zone); 188 | printf("username: %s\n", urlParts.username); 189 | printf("password: %s\n", urlParts.password); 190 | printf("auth: %s\n", urlParts.auth); 191 | printf("volpass: %s\n", urlParts.volpass); 192 | printf("volume: %s\n", urlParts.volume); 193 | 194 | printf("Validation result: %s\n", validateAFPURL(&urlParts) ? "bad" : "OK"); 195 | } 196 | #endif 197 | 198 | -------------------------------------------------------------------------------- /afpurlparser.h: -------------------------------------------------------------------------------- 1 | #ifndef AFPURLPARSER_H 2 | #define AFPURLPARSER_H 3 | 4 | enum protocol { proto_unknown, proto_AT, proto_TCP, proto_invalid }; 5 | 6 | typedef struct AFPURLParts { 7 | enum protocol protocol; 8 | char *server; 9 | char *zone; /* for AppleTalk only */ 10 | char *username; 11 | char *password; 12 | char *auth; 13 | char *volpass; 14 | char *volume; 15 | } AFPURLParts; 16 | 17 | /* Maximum lengths of components (for use in our mounter, using EasyMount) */ 18 | #define SERVER_MAX 32 19 | #define ZONE_MAX 32 20 | #define USERNAME_MAX 31 21 | #define PASSWORD_MAX 8 22 | #define VOLPASS_MAX 8 23 | #define VOLUME_MAX 27 24 | 25 | #define protoInvalidError 4000 26 | #define noServerOrVolumeNameError 4001 27 | #define serverNameTooLongError 4002 28 | #define volumeNameTooLongError 4003 29 | #define zoneTooLongError 4004 30 | #define usernameTooLongError 4005 31 | #define passwordTooLongError 4006 32 | #define volpassTooLongError 4007 33 | #define passwordWithoutUserError 4008 34 | #define badUAMError 4009 35 | 36 | AFPURLParts parseAFPURL(char *url); 37 | int validateAFPURL(AFPURLParts *urlParts); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /asmglue.asm: -------------------------------------------------------------------------------- 1 | case on 2 | mcopy asmglue.macros 3 | 4 | ROMIN gequ $C081 5 | LCBANK2 gequ $C083 6 | STATEREG gequ $C068 7 | 8 | ForceLCBank2 start 9 | short i,m 10 | lda >STATEREG ;get original state reg. 11 | tax 12 | lda >LCBANK2 ;force LC bank 2 13 | lda >LCBANK2 14 | long i,m 15 | txa 16 | rtl 17 | end 18 | 19 | ForceRomIn start 20 | short i,m 21 | lda >STATEREG ;get original state reg. 22 | tax 23 | lda >ROMIN ;force ROM in to Language Card space 24 | lda >ROMIN 25 | long i,m 26 | txa 27 | rtl 28 | end 29 | 30 | RestoreStateReg start 31 | short m 32 | plx 33 | pla 34 | ply 35 | pha 36 | phx 37 | tya 38 | sta >STATEREG 39 | long m 40 | rtl 41 | end 42 | -------------------------------------------------------------------------------- /asmglue.h: -------------------------------------------------------------------------------- 1 | #ifndef ASMGLUE_H 2 | #define ASMGLUE_H 3 | 4 | #include 5 | 6 | extern Word ForceLCBank2(void); 7 | extern Word ForceRomIn(void); 8 | extern void RestoreStateReg(Word); 9 | 10 | void IncBusyFlag(void) inline(0, 0xE10064); 11 | void DecBusyFlag(void) inline(0, 0xE10068); 12 | 13 | #define OS_KIND (*(Byte*)0xE100BC) 14 | #define KIND_P8 0x00 15 | #define KIND_GSOS 0x01 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /aspinterface.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "session.h" 8 | #include "aspinterface.h" 9 | #include "dsi.h" 10 | #include "tcpconnection.h" 11 | #include "endian.h" 12 | #include "readtcp.h" 13 | #include "asmglue.h" 14 | #include "cmdproc.h" 15 | #include "installcmds.h" 16 | #include "atipmapping.h" 17 | #include "afpoptions.h" 18 | #include "strncasecmp.h" 19 | 20 | typedef struct FPReadRec { 21 | Word CommandCode; /* includes pad byte */ 22 | Word OForkRefNum; 23 | LongWord Offset; 24 | LongWord ReqCount; 25 | Byte NewLineMask; 26 | Byte NewLineChar; 27 | } FPReadRec; 28 | 29 | typedef struct FPWriteRec { 30 | Word CommandCode; /* includes pad byte */ 31 | Word OForkRecNum; 32 | LongWord Offset; 33 | LongWord ReqCount; 34 | } FPWriteRec; 35 | 36 | typedef struct FPSetFileDirParmsRec { 37 | Word CommandCode; /* includes pad byte */ 38 | Word VolumeID; 39 | LongWord DirectoryID; 40 | Word Bitmap; 41 | Byte PathType; 42 | /* Pathname and parameters follow */ 43 | } FPSetFileDirParmsRec; 44 | 45 | #define kFPRead 27 46 | #define kFPLogin 18 47 | #define kFPZzzzz 122 48 | #define kFPSetFileDirParms 35 49 | 50 | #define kFPBitmapErr (-5004L) 51 | 52 | #define kFPProDOSInfoBit 0x2000 /* In file/directory bitmaps */ 53 | 54 | #define ASPQuantumSize 4624 55 | 56 | /* For forced AFP 2.2 login */ 57 | static Byte loginBuf[100]; 58 | static const Byte afp20VersionStr[] = "\pAFPVersion 2.0"; 59 | static const Byte afp22VersionStr[] = "\pAFP2.2"; 60 | 61 | static void DoSPGetStatus(Session *sess, ASPGetStatusRec *commandRec); 62 | static void DoSPOpenSession(Session *sess); 63 | static void DoSPCloseSession(Session *sess); 64 | static void DoSPCommand(Session *sess, ASPCommandRec *commandRec); 65 | static void DoSPWrite(Session *sess, ASPWriteRec *commandRec); 66 | 67 | static void CompleteASPCommand(SPCommandRec *commandRec, Word result); 68 | static void InitSleepRec(Session *sess, Byte sessRefNum); 69 | 70 | Session sessionTbl[MAX_SESSIONS]; 71 | 72 | #pragma databank 1 73 | LongWord DispatchASPCommand(SPCommandRec *commandRec) { 74 | Session *sess; 75 | unsigned int i; 76 | Word stateReg; 77 | LongWord lastActivityTime; 78 | LongWord lastReadCount; 79 | 80 | stateReg = ForceRomIn(); 81 | 82 | top: 83 | if (commandRec->command == aspGetStatusCommand 84 | || commandRec->command==aspOpenSessionCommand) 85 | { 86 | if (((ASPGetStatusRec*)commandRec)->slsNet != atipMapping.networkNumber 87 | || ((ASPGetStatusRec*)commandRec)->slsNode != atipMapping.node 88 | || ((ASPGetStatusRec*)commandRec)->slsSocket != atipMapping.socket) 89 | { 90 | goto callOrig; 91 | } 92 | 93 | if (OS_KIND != KIND_GSOS) { 94 | CompleteASPCommand(commandRec, aspNetworkErr); 95 | goto ret; 96 | } 97 | 98 | for (i = 0; i < MAX_SESSIONS; i++) { 99 | if (sessionTbl[i].dsiStatus == needsReset) 100 | EndASPSession(&sessionTbl[i], 0, TRUE); 101 | } 102 | for (i = 0; i < MAX_SESSIONS; i++) { 103 | if (sessionTbl[i].dsiStatus == unused) 104 | break; 105 | } 106 | if (i == MAX_SESSIONS) { 107 | CompleteASPCommand(commandRec, aspTooManySessions); 108 | goto ret; 109 | } 110 | sess = &sessionTbl[i]; 111 | sess->spCommandRec = commandRec; 112 | sess->commandPending = TRUE; 113 | InitSleepRec(sess, i + SESSION_NUM_START); 114 | 115 | if ((i = StartTCPConnection(sess)) != 0) { 116 | FlagFatalError(sess, i); 117 | goto ret; 118 | } 119 | sess->dsiStatus = awaitingHeader; 120 | InitReadTCP(sess, DSI_HEADER_SIZE, &sess->reply); 121 | 122 | if (commandRec->command==aspOpenSessionCommand) { 123 | sess->attention = (ASPAttentionHeaderRec *) 124 | ((ASPOpenSessionRec*)commandRec)->attnRtnAddr; 125 | } 126 | } else { 127 | if (commandRec->refNum < SESSION_NUM_START) { 128 | goto callOrig; 129 | } 130 | 131 | if (OS_KIND != KIND_GSOS) { 132 | CompleteASPCommand(commandRec, aspNetworkErr); 133 | goto ret; 134 | } 135 | 136 | /* 137 | * Hack to avoid hangs/crashes related to the AppleShare FST's 138 | * asynchronous polling to check if volumes have been modified. 139 | * This effectively disables that mechanism (which is the only 140 | * real user of asynchronous ASP calls). 141 | * 142 | * TODO Identify and fix the underlying issue. 143 | */ 144 | if ((commandRec->async & AT_ASYNC) 145 | && commandRec->command == aspCommandCommand) 146 | { 147 | CompleteASPCommand(commandRec, atSyncErr); 148 | goto ret; 149 | } 150 | 151 | sess = &sessionTbl[commandRec->refNum - SESSION_NUM_START]; 152 | 153 | if (sess->dsiStatus == unused) { 154 | CompleteASPCommand(commandRec, aspRefErr); 155 | goto ret; 156 | } 157 | 158 | if (sess->commandPending) { 159 | if (commandRec->command != aspCloseSessionCommand) { 160 | CompleteASPCommand(commandRec, aspSessNumErr); 161 | goto ret; 162 | } else { 163 | CompleteCurrentASPCommand(sess, aspSessionClosed); 164 | } 165 | } 166 | 167 | sess->spCommandRec = commandRec; 168 | if (commandRec->command != aspCloseSessionCommand) 169 | sess->commandPending = TRUE; 170 | } 171 | 172 | switch (commandRec->command) { 173 | case aspGetStatusCommand: 174 | DoSPGetStatus(sess, (ASPGetStatusRec *)commandRec); 175 | break; 176 | case aspOpenSessionCommand: 177 | DoSPOpenSession(sess); 178 | break; 179 | case aspCloseSessionCommand: 180 | DoSPCloseSession(sess); 181 | break; 182 | case aspCommandCommand: 183 | DoSPCommand(sess, (ASPCommandRec *)commandRec); 184 | break; 185 | case aspWriteCommand: 186 | DoSPWrite(sess, (ASPWriteRec *)commandRec); 187 | break; 188 | } 189 | 190 | if ((commandRec->async & AT_ASYNC) && sess->commandPending) { 191 | commandRec->result = aspBusyErr; // indicate call in process 192 | goto ret; 193 | } 194 | 195 | // if we're here, the call is synchronous -- we must complete it 196 | 197 | if (commandRec->command == aspCloseSessionCommand) { 198 | /* We don't wait for a reply to close */ 199 | memset(&sess->reply, 0, sizeof(sess->reply)); 200 | FinishASPCommand(sess); 201 | } else { 202 | lastActivityTime = GetTick(); 203 | lastReadCount = sess->readCount; 204 | i = 0; 205 | while (sess->commandPending) { 206 | PollForData(sess); 207 | if (sess->readCount != lastReadCount) { 208 | lastReadCount = sess->readCount; 209 | lastActivityTime = GetTick(); 210 | i = 0; 211 | } else if (GetTick() - lastActivityTime > 30*60) { 212 | if (i < 5) { 213 | i++; 214 | } else { 215 | FlagFatalError(sess, aspNoRespErr); 216 | } 217 | } 218 | } 219 | } 220 | 221 | ret: 222 | /* 223 | * If requested by user, we tell the server we are "going to sleep" 224 | * after every command we send. This avoids having the server 225 | * disconnect us because we can't send tickles for a while. 226 | * 227 | * This implementation differs from Apple's specifications in a couple 228 | * respects, but matches the actual behavior of the servers I've tried: 229 | * 230 | * -Apple's docs say FPZzzzz was introduced with AFP 2.3, but Netatalk and 231 | * OS X 10.5 support it with AFP 2.2 and doesn't support AFP 2.3 at all. 232 | * -Apple's docs show no reply block for FPZzzzz, but Netatalk and 233 | * OS X 10.5 send a reply with four bytes of data. 234 | */ 235 | if ((sess->atipMapping.flags & fFakeSleep) 236 | && sess->loggedIn && commandRec->result == 0 237 | && (commandRec->command == aspCommandCommand 238 | || commandRec->command == aspWriteCommand) 239 | && commandRec != (SPCommandRec*)&sess->sleepCommandRec) 240 | { 241 | commandRec = (SPCommandRec*)&sess->sleepCommandRec; 242 | goto top; 243 | } 244 | 245 | RestoreStateReg(stateReg); 246 | return 0; 247 | 248 | callOrig: 249 | RestoreStateReg(stateReg); 250 | return oldCmds[commandRec->command]; 251 | } 252 | #pragma databank 0 253 | 254 | static void DoSPGetStatus(Session *sess, ASPGetStatusRec *commandRec) { 255 | static const Word kFPGetSrvrInfo = 15; 256 | sess->request.flags = DSI_REQUEST; 257 | sess->request.command = DSIGetStatus; 258 | sess->request.requestID = htons(sess->nextRequestID++); 259 | sess->request.writeOffset = 0; 260 | sess->request.totalDataLength = htonl(2); 261 | sess->replyBuf = (void*)commandRec->bufferAddr; 262 | sess->replyBufLen = commandRec->bufferLength; 263 | 264 | SendDSIMessage(sess, &sess->request, &kFPGetSrvrInfo, NULL); 265 | } 266 | 267 | static void DoSPOpenSession(Session *sess) { 268 | sess->request.flags = DSI_REQUEST; 269 | sess->request.command = DSIOpenSession; 270 | sess->request.requestID = htons(sess->nextRequestID++); 271 | sess->request.writeOffset = 0; 272 | sess->request.totalDataLength = 0; 273 | sess->replyBuf = NULL; 274 | sess->replyBufLen = 0; 275 | 276 | SendDSIMessage(sess, &sess->request, NULL, NULL); 277 | } 278 | 279 | static void DoSPCloseSession(Session *sess) { 280 | sess->request.flags = DSI_REQUEST; 281 | sess->request.command = DSICloseSession; 282 | sess->request.requestID = htons(sess->nextRequestID++); 283 | sess->request.writeOffset = 0; 284 | sess->request.totalDataLength = 0; 285 | sess->replyBuf = NULL; 286 | sess->replyBufLen = 0; 287 | 288 | SendDSIMessage(sess, &sess->request, NULL, NULL); 289 | } 290 | 291 | static void DoSPCommand(Session *sess, ASPCommandRec *commandRec) { 292 | LongWord readSize; 293 | 294 | sess->request.flags = DSI_REQUEST; 295 | sess->request.command = DSICommand; 296 | sess->request.requestID = htons(sess->nextRequestID++); 297 | sess->request.writeOffset = 0; 298 | sess->request.totalDataLength = htonl(commandRec->cmdBlkLength); 299 | sess->replyBuf = (void*)(commandRec->replyBufferAddr & 0x00FFFFFF); 300 | sess->replyBufLen = commandRec->replyBufferLen; 301 | 302 | /* 303 | * If the client is requesting to read more data than will fit in its 304 | * buffer, reduce the amount of data requested and/or assume the buffer 305 | * is really larger than specified. 306 | * 307 | * This is necessary because ASP request and reply bodies are limited 308 | * to a "quantum size" of 4624 bytes. Even if more data than that is 309 | * requested, only that amount will be returned in a single reply. 310 | * If the full read count requested is larger, it simply serves as a 311 | * hint about the total amount that will be read across a series of 312 | * FPRead requests. The AppleShare FST relies on this behavior and 313 | * only specifies a buffer size of 4624 bytes for such requests. 314 | * However, it appears that it actually always has a buffer big enough 315 | * for the full read amount, and it can deal with receiving up to 316 | * 65535 bytes at once. 317 | * 318 | * DSI does not have this "quantum size" limitation, so the full 319 | * requested amount would be returned but then not fit in the specified 320 | * buffer size. To deal with this, we can reduce the amount of data 321 | * requested from the server and/or just assume we actually have a 322 | * bigger reply buffer than specified (up to 65535 bytes). The former 323 | * approach is safer, but allowing larger reads gives significantly 324 | * better performance and seems to work OK with the AppleShare FST, 325 | * so we offer options for both approaches. 326 | * 327 | * A similar issue could arise with FPEnumerate requests, but it 328 | * actually doesn't in the case of the requests generated by the 329 | * AppleShare FST, so we don't try to address them for now. 330 | */ 331 | if (commandRec->cmdBlkLength == sizeof(FPReadRec) 332 | && ((FPReadRec*)commandRec->cmdBlkAddr)->CommandCode == kFPRead) 333 | { 334 | readSize = ntohl(((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount); 335 | if (readSize > sess->replyBufLen) { 336 | if (sess->atipMapping.flags & fLargeReads) { 337 | if (readSize <= 0xFFFF) { 338 | sess->replyBufLen = readSize; 339 | } else { 340 | sess->replyBufLen = 0xFFFF; 341 | ((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount = 342 | htonl(0xFFFF); 343 | } 344 | } else { 345 | ((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount = 346 | htonl(sess->replyBufLen); 347 | } 348 | } 349 | } 350 | /* 351 | * If requested, replace AFP 2.0 login requests with otherwise-identical 352 | * AFP 2.2 login requests. This provides compatibility with some servers 353 | * that don't support AFP 2.0 over TCP. The protocols are similar enough 354 | * that it seems to work, although there could be issues. 355 | */ 356 | else if ((sess->atipMapping.flags & fForceAFP22) 357 | && commandRec->cmdBlkLength >= sizeof(afp20VersionStr) 358 | && *((Byte*)commandRec->cmdBlkAddr) == kFPLogin 359 | && commandRec->cmdBlkLength <= sizeof(loginBuf) 360 | && strncasecmp(afp20VersionStr, (Byte*)commandRec->cmdBlkAddr + 1, 361 | sizeof(afp20VersionStr) - 1) == 0) 362 | { 363 | memcpy(loginBuf, (Byte*)commandRec->cmdBlkAddr, 364 | commandRec->cmdBlkLength); 365 | loginBuf[sizeof(afp20VersionStr)-sizeof(afp22VersionStr)] = kFPLogin; 366 | memcpy(&loginBuf[sizeof(afp20VersionStr)-sizeof(afp22VersionStr)+1], 367 | afp22VersionStr, 368 | sizeof(afp22VersionStr) - 1); 369 | 370 | sess->request.totalDataLength = 371 | htonl(commandRec->cmdBlkLength 372 | -sizeof(afp20VersionStr)+sizeof(afp22VersionStr)); 373 | SendDSIMessage(sess, &sess->request, 374 | loginBuf+sizeof(afp20VersionStr)-sizeof(afp22VersionStr), 375 | NULL); 376 | return; 377 | } 378 | 379 | /* Mask off high byte of addresses because PFI (at least) may 380 | * put junk in them, and this can cause Marinetti errors. */ 381 | SendDSIMessage(sess, &sess->request, 382 | (void*)(commandRec->cmdBlkAddr & 0x00FFFFFF), NULL); 383 | } 384 | 385 | static void DoSPWrite(Session *sess, ASPWriteRec *commandRec) { 386 | LongWord dataLength; 387 | 388 | sess->request.flags = DSI_REQUEST; 389 | sess->request.command = DSIWrite; 390 | sess->request.requestID = htons(sess->nextRequestID++); 391 | sess->request.writeOffset = htonl(commandRec->cmdBlkLength); 392 | dataLength = commandRec->writeDataLength; 393 | 394 | /* 395 | * If requested, limit the data size of a single DSIWrite command to 4623. 396 | * This is necessary to make writes work on OS X (and others?). 397 | */ 398 | if (!(sess->atipMapping.flags & fLargeWrites) 399 | && dataLength > ASPQuantumSize - 1) 400 | { 401 | dataLength = ASPQuantumSize - 1; 402 | ((FPReadRec*)commandRec->cmdBlkAddr)->ReqCount = htonl(dataLength); 403 | } 404 | 405 | sess->request.totalDataLength = 406 | htonl(dataLength + commandRec->cmdBlkLength); 407 | sess->replyBuf = (void*)(commandRec->replyBufferAddr & 0x00FFFFFF); 408 | sess->replyBufLen = commandRec->replyBufferLen; 409 | 410 | SendDSIMessage(sess, &sess->request, 411 | (void*)(commandRec->cmdBlkAddr & 0x00FFFFFF), 412 | (void*)(commandRec->writeDataAddr & 0x00FFFFFF)); 413 | } 414 | 415 | 416 | void FlagFatalError(Session *sess, Word errorCode) { 417 | sess->dsiStatus = error; 418 | if (errorCode == 0) { 419 | // TODO deduce better error code from Marinetti errors? 420 | errorCode = aspNetworkErr; 421 | } 422 | 423 | if (sess->commandPending) { 424 | CompleteCurrentASPCommand(sess, errorCode); 425 | } 426 | 427 | EndASPSession(sess, aspAttenTimeout, TRUE); 428 | } 429 | 430 | 431 | // Fill in any necessary data in the ASP command rec for a successful return 432 | void FinishASPCommand(Session *sess) { 433 | LongWord dataLength; 434 | 435 | dataLength = ntohl(sess->reply.totalDataLength); 436 | if (dataLength > 0xFFFF) { 437 | // The IIgs ASP interfaces can't represent lengths over 64k. 438 | // This should be detected as an error earlier, but let's make sure. 439 | CompleteCurrentASPCommand(sess, aspSizeErr); 440 | return; 441 | } 442 | 443 | switch(sess->spCommandRec->command) { 444 | case aspGetStatusCommand: 445 | ((ASPGetStatusRec*)(sess->spCommandRec))->dataLength = dataLength; 446 | break; 447 | case aspOpenSessionCommand: 448 | ((ASPOpenSessionRec*)(sess->spCommandRec))->refNum = 449 | (sess - &sessionTbl[0]) + SESSION_NUM_START; 450 | break; 451 | case aspCloseSessionCommand: 452 | break; 453 | case aspCommandCommand: 454 | ((ASPCommandRec*)(sess->spCommandRec))->cmdResult = 455 | sess->reply.errorCode; 456 | ((ASPCommandRec*)(sess->spCommandRec))->replyLength = dataLength; 457 | 458 | /* 459 | * If requested by user, ignore errors when setting ProDOS-style 460 | * filetype info. This allows files to be written to servers running 461 | * old versions of OS X, although filetypes are not set correctly. 462 | */ 463 | if ((sess->atipMapping.flags & fIgnoreFileTypeErrors) 464 | && ((ASPCommandRec*)(sess->spCommandRec))->cmdBlkLength 465 | > sizeof(FPSetFileDirParmsRec) 466 | && *(Byte*)(((ASPCommandRec*)(sess->spCommandRec))->cmdBlkAddr) 467 | == kFPSetFileDirParms 468 | && (((FPSetFileDirParmsRec*)(((ASPCommandRec*)(sess->spCommandRec)) 469 | ->cmdBlkAddr))->Bitmap & htons(kFPProDOSInfoBit)) 470 | && sess->reply.errorCode == htonl((LongWord)kFPBitmapErr)) 471 | { 472 | ((ASPCommandRec*)(sess->spCommandRec))->cmdResult = 0; 473 | } 474 | 475 | break; 476 | case aspWriteCommand: 477 | ((ASPWriteRec*)(sess->spCommandRec))->cmdResult = 478 | sess->reply.errorCode; 479 | ((ASPWriteRec*)(sess->spCommandRec))->replyLength = dataLength; 480 | ((ASPWriteRec*)(sess->spCommandRec))->writtenLength = 481 | ntohl(sess->request.totalDataLength) 482 | - ((ASPWriteRec*)(sess->spCommandRec))->cmdBlkLength; 483 | break; 484 | } 485 | 486 | complete: 487 | CompleteCurrentASPCommand(sess, 0); 488 | } 489 | 490 | /* Actions to complete a command, whether successful or not */ 491 | void CompleteCurrentASPCommand(Session *sess, Word result) { 492 | SPCommandRec *commandRec; 493 | 494 | commandRec = sess->spCommandRec; 495 | 496 | if (sess->spCommandRec->command == aspGetStatusCommand 497 | || sess->spCommandRec->command == aspCloseSessionCommand) 498 | { 499 | EndASPSession(sess, 0, TRUE); 500 | } else { 501 | sess->commandPending = FALSE; 502 | if (sess->dsiStatus != error) { 503 | sess->dsiStatus = awaitingHeader; 504 | InitReadTCP(sess, DSI_HEADER_SIZE, &sess->reply); 505 | } 506 | } 507 | 508 | commandRec->result = result; 509 | 510 | if ((commandRec->async & AT_ASYNC) && commandRec->completionPtr != NULL) { 511 | CallCompletionRoutine((void *)commandRec->completionPtr); 512 | } 513 | } 514 | 515 | /* For use in error cases not involving the session's current command */ 516 | static void CompleteASPCommand(SPCommandRec *commandRec, Word result) { 517 | commandRec->result = result; 518 | 519 | if ((commandRec->async & AT_ASYNC) && commandRec->completionPtr != NULL) { 520 | CallCompletionRoutine((void *)commandRec->completionPtr); 521 | } 522 | } 523 | 524 | 525 | void EndASPSession(Session *sess, Byte attentionCode, Boolean doLogout) { 526 | if (attentionCode != 0) { 527 | CallAttentionRoutine(sess, attentionCode, 0); 528 | } 529 | 530 | if (doLogout) 531 | EndTCPConnection(sess); 532 | memset(sess, 0, sizeof(*sess)); 533 | } 534 | 535 | void CallAttentionRoutine(Session *sess, Byte attenType, Word atten) { 536 | if (sess->attention == NULL) 537 | return; 538 | 539 | sess->attention->sessionRefNum = (sess - sessionTbl) + SESSION_NUM_START; 540 | sess->attention->attenType = attenType; 541 | sess->attention->atten = atten; 542 | 543 | /* Call attention routine like completion routine */ 544 | CallCompletionRoutine((void *)(sess->attention + 1)); 545 | } 546 | 547 | static void InitSleepRec(Session *sess, Byte sessRefNum) { 548 | sess->sleepCommandRec.async = 0; 549 | sess->sleepCommandRec.command = aspCommandCommand; 550 | sess->sleepCommandRec.completionPtr = 0; 551 | sess->sleepCommandRec.refNum = sessRefNum; 552 | sess->sleepCommandRec.cmdBlkLength = sizeof(sess->fpZzzzzRec); 553 | sess->sleepCommandRec.cmdBlkAddr = (LongWord)&sess->fpZzzzzRec; 554 | sess->sleepCommandRec.replyBufferLen = sizeof(sess->fpZzzzzResponseRec); 555 | sess->sleepCommandRec.replyBufferAddr = (LongWord)&sess->fpZzzzzResponseRec; 556 | sess->fpZzzzzRec.CommandCode = kFPZzzzz; 557 | sess->fpZzzzzRec.Flag = 0; 558 | } 559 | 560 | void PollAllSessions(void) { 561 | unsigned int i; 562 | 563 | for (i = 0; i < MAX_SESSIONS; i++) { 564 | switch (sessionTbl[i].dsiStatus) { 565 | case awaitingHeader: 566 | case awaitingPayload: 567 | PollForData(&sessionTbl[i]); 568 | break; 569 | 570 | case needsReset: 571 | EndASPSession(&sessionTbl[i], 0, TRUE); 572 | break; 573 | } 574 | } 575 | } 576 | 577 | /* Close all sessions -- used when we're shutting down */ 578 | void CloseAllSessions(Byte attentionCode, Boolean doLogout) { 579 | unsigned int i; 580 | Session *sess; 581 | 582 | for (i = 0; i < MAX_SESSIONS; i++) { 583 | sess = &sessionTbl[i]; 584 | if (sess->dsiStatus != unused) { 585 | if (doLogout) 586 | DoSPCloseSession(sess); 587 | EndASPSession(sess, attentionCode, doLogout); 588 | } 589 | } 590 | } 591 | 592 | /* 593 | * Reset the state of all sessions. 594 | * Used when control-reset is pressed in P8 mode. We can't call Marinetti 595 | * in P8 mode, but we also don't want to just leak any ipids in use, so 596 | * we flag sessions that should be reset when we're back in GS/OS. 597 | */ 598 | #pragma databank 1 599 | void ResetAllSessions(void) { 600 | unsigned int i; 601 | 602 | for (i = 0; i < MAX_SESSIONS; i++) { 603 | if (sessionTbl[i].dsiStatus != unused) { 604 | sessionTbl[i].dsiStatus = needsReset; 605 | } 606 | } 607 | } 608 | #pragma databank 0 609 | -------------------------------------------------------------------------------- /aspinterface.h: -------------------------------------------------------------------------------- 1 | #ifndef ASPINTERFACE_H 2 | #define ASPINTERFACE_H 3 | 4 | #include "session.h" 5 | 6 | /* async flag values */ 7 | #define AT_SYNC 0 8 | #define AT_ASYNC 0x80 9 | 10 | #define aspBusyErr 0x07FF /* temp result code for async call in process */ 11 | 12 | #define SESSION_NUM_START 0xF8 13 | #define MAX_SESSIONS (256 - SESSION_NUM_START) 14 | 15 | extern Session sessionTbl[MAX_SESSIONS]; 16 | 17 | LongWord DispatchASPCommand(SPCommandRec *commandRec); 18 | void CompleteCurrentASPCommand(Session *sess, Word result); 19 | void FinishASPCommand(Session *sess); 20 | void FlagFatalError(Session *sess, Word errorCode); 21 | void EndASPSession(Session *sess, Byte attentionCode, Boolean doLogout); 22 | void CallAttentionRoutine(Session *sess, Byte attenType, Word atten); 23 | void PollAllSessions(void); 24 | void CloseAllSessions(Byte attentionCode, Boolean doLogout); 25 | void ResetAllSessions(void); 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /atipmapping.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "atipmapping.h" 11 | #include "asmglue.h" 12 | #include "aspinterface.h" 13 | #include "installcmds.h" 14 | #include "cmdproc.h" 15 | #include "afpoptions.h" 16 | #include "strncasecmp.h" 17 | 18 | struct ATIPMapping atipMapping; 19 | 20 | #define DEFAULT_DSI_PORT 548 21 | 22 | static char ATIPTypeName[] = "\pAFPServer"; 23 | static char DefaultZoneName[] = "\p*"; 24 | static char AFPOverTCPZone[] = "AFP over TCP"; 25 | 26 | // Next numbers to use for new mappings 27 | static int nextNode = 1; 28 | static int nextSocket = 1; 29 | 30 | #define return_error(x) do { \ 31 | commandRec->result = (x); \ 32 | commandRec->actualMatch = 0; \ 33 | RestoreStateReg(stateReg); \ 34 | return 0; \ 35 | } while (0) 36 | 37 | #pragma databank 1 38 | LongWord DoLookupName(NBPLookupNameRec *commandRec) { 39 | cvtRec hostInfo; 40 | dnrBuffer dnrInfo; 41 | Byte *curr, *dest, *sep; 42 | unsigned int count, nameLen, i; 43 | NBPLUNameBufferRec *resultBuf; 44 | LongWord initialTime; 45 | Word stateReg; 46 | unsigned int flags; 47 | 48 | stateReg = ForceRomIn(); 49 | 50 | if (OS_KIND != KIND_GSOS) 51 | goto passThrough; 52 | 53 | // Length needed for result, assuming the request is for our type/zone 54 | count = offsetof(NBPLUNameBufferRec, entityName) 55 | + ((EntName*)commandRec->entityPtr)->buffer[0] + 1 56 | + ATIPTypeName[0] + 1 + DefaultZoneName[0] + 1; 57 | if (count > commandRec->bufferLength) 58 | goto passThrough; 59 | 60 | resultBuf = (NBPLUNameBufferRec *)commandRec->bufferPtr; 61 | 62 | curr = &((EntName*)commandRec->entityPtr)->buffer[0]; 63 | dest = &resultBuf->entityName.buffer[0]; 64 | 65 | // Copy object name into result buffer 66 | nameLen = *curr; 67 | for (count = 0; count <= nameLen; count++) { 68 | *dest++ = *curr++; 69 | } 70 | 71 | // Check that entity type and zone are what we want, 72 | // and copy names to result buffer. 73 | nameLen = *curr; 74 | for (count = 0; count <= nameLen; count++) { 75 | if (toupper(*curr++) != toupper(ATIPTypeName[count])) 76 | goto passThrough; 77 | *dest++ = ATIPTypeName[count]; 78 | } 79 | nameLen = *curr++; 80 | 81 | /* Check if zone starts with "AFP over TCP" */ 82 | if (nameLen < sizeof(AFPOverTCPZone) - 1) 83 | goto passThrough; 84 | if (strncasecmp(AFPOverTCPZone, curr, sizeof(AFPOverTCPZone) - 1) != 0) 85 | goto passThrough; 86 | 87 | /* Check for options (in parentheses after "AFP over TCP ") */ 88 | flags = 0; 89 | if (nameLen > sizeof(AFPOverTCPZone) - 1) { 90 | nameLen -= sizeof(AFPOverTCPZone) - 1; 91 | curr += sizeof(AFPOverTCPZone) - 1; 92 | if (nameLen < 3) 93 | goto passThrough; 94 | if (curr[0] != ' ' || curr[1] != '(' || curr[nameLen-1] != ')') 95 | goto passThrough; 96 | nameLen -= 2; 97 | curr += 2; 98 | 99 | while (nameLen > 1) { 100 | /* Parse options */ 101 | if (memchr(curr, '\0', nameLen - 1) != NULL) 102 | goto passThrough; 103 | sep = memchr(curr, ',', nameLen - 1); 104 | count = (sep == NULL) ? nameLen - 1 : sep - curr; 105 | 106 | for (i = 0; afpOptions[i].optString != NULL; i++) { 107 | if (strncasecmp(curr, afpOptions[i].optString, count) == 0 108 | && afpOptions[i].optString[count] == '\0') 109 | { 110 | flags |= afpOptions[i].flag; 111 | break; 112 | } 113 | } 114 | /* Don't accept unknown options */ 115 | if (afpOptions[i].optString == NULL) 116 | goto passThrough; 117 | 118 | nameLen -= count + 1; 119 | curr += count + 1; 120 | } 121 | } 122 | 123 | nameLen = *DefaultZoneName; 124 | for (count = 0; count <= nameLen; count++) { 125 | *dest++ = DefaultZoneName[count]; 126 | } 127 | 128 | if (TCPIPValidateIPString(&resultBuf->entityName.buffer[0])) { 129 | TCPIPConvertIPToHex(&hostInfo, &resultBuf->entityName.buffer[0]); 130 | resultBuf->enumerator = 0; 131 | 132 | // TCPIPConvertIPToHex seems not to give port, so get it this way 133 | hostInfo.cvtPort = 134 | TCPIPMangleDomainName(0, &resultBuf->entityName.buffer[0]); 135 | } else { 136 | // Make sure we're connected before doing DNS lookup. 137 | if (TCPIPGetConnectStatus() == FALSE) 138 | TCPIPConnect(NULL); 139 | 140 | hostInfo.cvtPort = 141 | TCPIPMangleDomainName(0xE000, &resultBuf->entityName.buffer[0]); 142 | TCPIPDNRNameToIP(&resultBuf->entityName.buffer[0], &dnrInfo); 143 | if (toolerror()) 144 | return_error(nbpNameErr); 145 | 146 | initialTime = GetTick(); 147 | while (dnrInfo.DNRstatus == DNR_Pending) { 148 | if (GetTick() - initialTime >= 15*60) 149 | break; 150 | TCPIPPoll(); 151 | } 152 | 153 | // Re-copy object name into result buffer to undo mangling 154 | curr = &((EntName*)commandRec->entityPtr)->buffer[0]; 155 | dest = &resultBuf->entityName.buffer[0]; 156 | nameLen = *curr; 157 | for (count = 0; count <= nameLen; count++) { 158 | *dest++ = *curr++; 159 | } 160 | 161 | if (dnrInfo.DNRstatus == DNR_OK) { 162 | hostInfo.cvtIPAddress = dnrInfo.DNRIPaddress; 163 | resultBuf->enumerator = 1; 164 | } else if (dnrInfo.DNRstatus == DNR_NoDNSEntry) { 165 | return_error(0); // not really an error, but 0 results 166 | } else { 167 | TCPIPCancelDNR(&dnrInfo); 168 | return_error(nbpNameErr); 169 | } 170 | } 171 | 172 | if (hostInfo.cvtPort == 0) 173 | hostInfo.cvtPort = DEFAULT_DSI_PORT; 174 | 175 | for (count = 0; count < MAX_SESSIONS; count++) { 176 | if (sessionTbl[count].atipMapping.ipAddr == hostInfo.cvtIPAddress 177 | && sessionTbl[count].atipMapping.port == hostInfo.cvtPort) 178 | { 179 | atipMapping = sessionTbl[count].atipMapping; 180 | goto haveMapping; 181 | } 182 | } 183 | 184 | atipMapping.ipAddr = hostInfo.cvtIPAddress; 185 | atipMapping.port = hostInfo.cvtPort; 186 | atipMapping.networkNumber = 0xFFFF; /* invalid/reserved */ 187 | atipMapping.node = nextNode++; 188 | atipMapping.socket = nextSocket++; 189 | if (nextNode == 254) 190 | nextNode = 1; 191 | if (nextSocket == 255) 192 | nextSocket = 1; 193 | 194 | haveMapping: 195 | atipMapping.flags = flags; 196 | resultBuf->netNum = atipMapping.networkNumber; 197 | resultBuf->nodeNum = atipMapping.node; 198 | resultBuf->socketNum = atipMapping.socket; 199 | commandRec->actualMatch = 1; 200 | commandRec->result = 0; 201 | if ((commandRec->async & AT_ASYNC) && commandRec->completionPtr != 0) { 202 | CallCompletionRoutine((void *)commandRec->completionPtr); 203 | } 204 | RestoreStateReg(stateReg); 205 | return 0; 206 | 207 | passThrough: 208 | RestoreStateReg(stateReg); 209 | return oldCmds[commandRec->command]; 210 | } 211 | #pragma databank 0 212 | 213 | #undef return_error 214 | -------------------------------------------------------------------------------- /atipmapping.h: -------------------------------------------------------------------------------- 1 | #ifndef ATIPMAPPING_H 2 | #define ATIPMAPPING_H 3 | 4 | #include 5 | #include 6 | 7 | typedef struct ATIPMapping { 8 | /* AppleTalk address/socket */ 9 | Word networkNumber; /* in network byte order */ 10 | Byte node; 11 | Byte socket; 12 | 13 | /* IP address/port */ 14 | LongWord ipAddr; 15 | Word port; 16 | 17 | /* Flags for AFP over TCP (defined in afpoptions.h) */ 18 | unsigned int flags; 19 | } ATIPMapping; 20 | 21 | extern struct ATIPMapping atipMapping; 22 | 23 | LongWord DoLookupName(NBPLookupNameRec *commandRec); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /callat.asm: -------------------------------------------------------------------------------- 1 | case on 2 | 3 | * Bogus segment to go into the .root file and force generation of .a/.o file 4 | bogus private 5 | nop 6 | end 7 | 8 | RamDispatch gequ $E11014 9 | 10 | _CALLAT start 11 | lda 4,s 12 | tax 13 | lda 6,s 14 | tay 15 | phb 16 | pla 17 | sta 3,s 18 | pla 19 | sta 3,s 20 | plb 21 | jml RamDispatch 22 | end 23 | -------------------------------------------------------------------------------- /cdevstart.asm: -------------------------------------------------------------------------------- 1 | case on 2 | 3 | * This is the actual entry point for the CDEV. 4 | * We continue to the C code in most cases, but 5 | * handle EventsCDEV messages here for performance. 6 | cdeventry start 7 | lda 12,s ; get message 8 | cmp #6 ; is it EventsCDEV? 9 | bne continue 10 | 11 | doEvent anop ; handle an EventsCDEV message 12 | pla ; move return address 13 | sta 9,s 14 | pla 15 | sta 9,s 16 | 17 | tsc 18 | phd 19 | tcd 20 | ldy #14 ; modifiers field in event structure 21 | lda [4],y ; get data1->modifiers & save it away 22 | sta >modifiers 23 | stz 10 ; result = 0 (necessary?) 24 | stz 12 25 | pld 26 | 27 | tsc 28 | clc 29 | adc #6 30 | tcs 31 | rtl 32 | end 33 | 34 | FreeAllCDevMem start 35 | pea 0 36 | jsl ~DAID 37 | rtl 38 | end 39 | 40 | continue private 41 | end 42 | -------------------------------------------------------------------------------- /cdevutil.h: -------------------------------------------------------------------------------- 1 | #ifndef CDEVUTIL_H 2 | #define CDEVUTIL_H 3 | 4 | void FreeAllCDevMem(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /cmdproc.asm: -------------------------------------------------------------------------------- 1 | case on 2 | mcopy cmdproc.macros 3 | 4 | SESSION_NUM_START gequ $F8 5 | 6 | RamGoComp gequ $E1100C 7 | RamForbid gequ $E11018 8 | RamPermit gequ $E1101C 9 | 10 | * Location of command rec ptr on entry to an AppleTalk command procedure 11 | * (in the system zero page, which is the current direct page) 12 | cmdRecPtr gequ $80 13 | 14 | * Location to put completion routine ptr before calling RamGoComp 15 | compPtr gequ $84 16 | 17 | 18 | * AppleTalk command procedure (which acts as a dispatcher for all commands) 19 | cmdProc start 20 | lda cmdRecPtr+2 21 | pha 22 | lda cmdRecPtr 23 | pha 24 | jsl DispatchASPCommand 25 | cmp #0 26 | bne doOrig 27 | cpx #0 28 | bne doOrig 29 | rtl 30 | doOrig short i ;push original procedure ptr 31 | phx 32 | long i 33 | dec a 34 | pha 35 | rtl ;jump to it 36 | end 37 | 38 | nbpCmdProc start 39 | lda cmdRecPtr+2 40 | pha 41 | lda cmdRecPtr 42 | pha 43 | jsl DoLookupName 44 | cmp #0 45 | bne doOrig 46 | cpx #0 47 | bne doOrig 48 | rtl 49 | doOrig short i ;push original procedure ptr 50 | phx 51 | long i 52 | dec a 53 | pha 54 | rtl ;jump to it 55 | end 56 | 57 | pfiLoginCmdProc start 58 | lda cmdRecPtr+2 59 | pha 60 | lda cmdRecPtr 61 | pha 62 | jslOldPFILogin entry 63 | jsl jslOldPFILogin ; to be changed 64 | rep #$30 ; ensure full native mode 65 | jsl SaveNames 66 | rtl 67 | end 68 | 69 | pfiLogin2CmdProc start 70 | lda cmdRecPtr+2 71 | pha 72 | lda cmdRecPtr 73 | pha 74 | jslOldPFILogin2 entry 75 | jsl jslOldPFILogin2 ; to be changed 76 | rep #$30 ; ensure full native mode 77 | jsl SaveNames 78 | rtl 79 | end 80 | 81 | pfiListSessions2CmdProc start 82 | lda cmdRecPtr+2 83 | pha 84 | lda cmdRecPtr 85 | pha 86 | jslOldPFIListSessions2 entry 87 | jsl jslOldPFIListSessions2 ; to be changed 88 | rep #$30 ; ensure full native mode 89 | jsl InsertCorrectNames 90 | rtl 91 | end 92 | 93 | pfiLoginContCmdProc start 94 | lda cmdRecPtr+2 95 | pha 96 | lda cmdRecPtr 97 | pha 98 | jslOldPFILoginCont entry 99 | jsl jslOldPFILoginCont ; to be changed 100 | rep #$30 ; ensure full native mode 101 | jsl PostLoginCont 102 | rtl 103 | end 104 | 105 | 106 | CallCompletionRoutine start 107 | phb 108 | jsl ForceLCBank2 ;use LC bank 2 109 | pha 110 | phd 111 | lda #0 ;set direct page = system zero page 112 | tcd 113 | 114 | jsl RamForbid 115 | lda 9,s 116 | sta compPtr 117 | lda 9+2,s 118 | sta compPtr+2 119 | ora 9,s 120 | beq skip ;skip call if compPtr = 0 121 | jsl >RamGoComp 122 | skip jsl RamPermit 123 | pld 124 | jsl RestoreStateReg 125 | 126 | pla 127 | sta 3,s 128 | pla 129 | sta 3,s 130 | plb 131 | rtl 132 | end 133 | 134 | * Replacement attention vector to be called from PFI. 135 | * This calls the previous attention vector (from the ATalk driver) for ASP 136 | * sessions (numbered 1-8), but not for the higher-numbered DSI sessions. 137 | * This is needed because ATalk's attention vector is hard-coded for session 138 | * numbers 1-8 only and trashes memory when called with higher numbers. 139 | attentionVec start 140 | phd 141 | phy 142 | phx 143 | tsc 144 | tcd 145 | lda [1] 146 | plx 147 | ply 148 | pld 149 | 150 | and #$00FF 151 | cmp #SESSION_NUM_START 152 | bge skip 153 | jmlOldAttentionVec entry 154 | jml attentionVec ;to be changed to old attention vec 155 | skip clc 156 | rtl 157 | end 158 | 159 | -------------------------------------------------------------------------------- /cmdproc.h: -------------------------------------------------------------------------------- 1 | #ifndef CMDPROC_H 2 | #define CMDPROC_H 3 | 4 | void cmdProc(void); 5 | void nbpCmdProc(void); 6 | void pfiLoginCmdProc(void); 7 | extern LongWord jslOldPFILogin; 8 | void pfiLogin2CmdProc(void); 9 | extern LongWord jslOldPFILogin2; 10 | void pfiLoginContCmdProc(void); 11 | extern LongWord jslOldPFILoginCont; 12 | void pfiListSessions2CmdProc(void); 13 | extern LongWord jslOldPFIListSessions2; 14 | void CallCompletionRoutine(void *); 15 | 16 | extern void attentionVec(void); 17 | extern LongWord jmlOldAttentionVec; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /dsi.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "dsiproto.h" 8 | #include "session.h" 9 | #include "readtcp.h" 10 | #include "dsi.h" 11 | #include "endian.h" 12 | #include "aspinterface.h" 13 | 14 | static DSIRequestHeader tickleRequestRec = { 15 | DSI_REQUEST, /* flags */ 16 | DSITickle, /* command */ 17 | 0, /* requestID - set later */ 18 | 0, /* writeOffset */ 19 | 0, /* totalDataLength */ 20 | 0 /* reserved */ 21 | }; 22 | 23 | /* Actually a reply, but use DSIRequestHeader type so we can send it. */ 24 | static DSIRequestHeader attentionReplyRec = { 25 | DSI_REPLY, /* flags */ 26 | DSIAttention, /* command */ 27 | 0, /* requestID - set later */ 28 | 0, /* errorCode */ 29 | 0, /* totalDataLength */ 30 | 0 /* reserved */ 31 | }; 32 | 33 | 34 | void SendDSIMessage(Session *sess, DSIRequestHeader *header, void *payload, 35 | void *extraPayload) { 36 | LongWord cmdLen; 37 | LongWord extraLen; 38 | 39 | sess->tcperr = TCPIPWriteTCP(sess->ipid, (void*)header, 40 | DSI_HEADER_SIZE, 41 | !header->totalDataLength, FALSE); 42 | sess->toolerr = toolerror(); 43 | if (sess->tcperr || sess->toolerr) { 44 | FlagFatalError(sess, 0); 45 | return; 46 | } 47 | 48 | if (header->writeOffset) { 49 | cmdLen = ntohl(header->writeOffset); 50 | extraLen = ntohl(header->totalDataLength) - cmdLen; 51 | } else { 52 | cmdLen = ntohl(header->totalDataLength); 53 | extraLen = 0; 54 | } 55 | while (cmdLen) { 56 | sess->tcperr = TCPIPWriteTCP(sess->ipid, payload, cmdLen, 57 | !extraLen, FALSE); 58 | sess->toolerr = toolerror(); 59 | if (sess->tcperr || sess->toolerr) { 60 | FlagFatalError(sess, 0); 61 | return; 62 | } 63 | 64 | cmdLen = extraLen; 65 | payload = extraPayload; 66 | extraLen = 0; 67 | } 68 | } 69 | 70 | 71 | void PollForData(Session *sess) { 72 | ReadStatus rs; 73 | LongWord dataLength; 74 | 75 | if (sess->dsiStatus != awaitingHeader && sess->dsiStatus != awaitingPayload) 76 | return; 77 | 78 | top: 79 | rs = TryReadTCP(sess); 80 | if (rs != rsDone) { 81 | if (rs == rsError) FlagFatalError(sess, 0); 82 | return; 83 | } 84 | 85 | // If we're here, we successfully read something. 86 | 87 | if (sess->dsiStatus == awaitingHeader) { 88 | if (sess->reply.totalDataLength != 0) { 89 | dataLength = ntohl(sess->reply.totalDataLength); 90 | 91 | if (sess->commandPending 92 | && sess->reply.flags == DSI_REPLY 93 | && sess->reply.requestID == sess->request.requestID 94 | && sess->reply.command == sess->request.command) 95 | { 96 | if (dataLength <= sess->replyBufLen) { 97 | InitReadTCP(sess, dataLength, sess->replyBuf); 98 | sess->dsiStatus = awaitingPayload; 99 | goto top; 100 | } else { 101 | // handle data too long 102 | sess->junkBuf = *NewHandle(dataLength, 103 | userid(), attrFixed, 0); 104 | if (toolerror() || sess->junkBuf == NULL) { 105 | FlagFatalError(sess, atMemoryErr); 106 | return; 107 | } else { 108 | InitReadTCP(sess, dataLength, sess->junkBuf); 109 | sess->dsiStatus = awaitingPayload; 110 | goto top; 111 | } 112 | } 113 | } 114 | else if (sess->reply.flags == DSI_REQUEST 115 | && sess->reply.command == DSIAttention 116 | && dataLength <= DSI_ATTENTION_QUANTUM) 117 | { 118 | InitReadTCP(sess, DSI_ATTENTION_QUANTUM, &sess->attentionCode); 119 | sess->dsiStatus = awaitingPayload; 120 | goto top; 121 | } 122 | else 123 | { 124 | FlagFatalError(sess, aspSizeErr); 125 | return; 126 | } 127 | } 128 | } 129 | 130 | // If we're here, we got a full message. 131 | 132 | // Handle a command that is now done, if any. 133 | if (sess->commandPending 134 | && sess->reply.flags == DSI_REPLY 135 | && sess->reply.requestID == sess->request.requestID 136 | && sess->reply.command == sess->request.command) 137 | { 138 | if (sess->junkBuf != NULL) { 139 | DisposeHandle(FindHandle(sess->junkBuf)); 140 | sess->junkBuf = NULL; 141 | if (sess->reply.command == DSIOpenSession) { 142 | // We ignore the DSIOpenSession options for now. 143 | // Maybe we should do something with them? 144 | FinishASPCommand(sess); 145 | } else { 146 | CompleteCurrentASPCommand(sess, aspBufErr); 147 | } 148 | } else { 149 | FinishASPCommand(sess); 150 | } 151 | return; 152 | } 153 | //Handle a request from the server 154 | else if (sess->reply.flags == DSI_REQUEST) 155 | { 156 | if (sess->reply.command == DSIAttention) { 157 | attentionReplyRec.requestID = sess->reply.requestID; 158 | SendDSIMessage(sess, &attentionReplyRec, NULL, NULL); 159 | CallAttentionRoutine(sess, aspAttenNormal, sess->attentionCode); 160 | } else if (sess->reply.command == DSICloseSession) { 161 | EndASPSession(sess, aspAttenClosed, TRUE); 162 | return; 163 | } else if (sess->reply.command == DSITickle) { 164 | tickleRequestRec.requestID = htons(sess->nextRequestID++); 165 | SendDSIMessage(sess, &tickleRequestRec, NULL, NULL); 166 | } else { 167 | FlagFatalError(sess, aspNetworkErr); 168 | return; 169 | } 170 | 171 | sess->dsiStatus = awaitingHeader; 172 | InitReadTCP(sess, DSI_HEADER_SIZE, &sess->reply); 173 | } 174 | else 175 | { 176 | FlagFatalError(sess, aspNetworkErr); 177 | return; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /dsi.h: -------------------------------------------------------------------------------- 1 | #ifndef DSI_H 2 | #define DSI_H 3 | 4 | #include "dsiproto.h" 5 | #include "session.h" 6 | 7 | void SendDSIMessage(Session *sess, DSIRequestHeader *header, void *payload, 8 | void *extraPayload); 9 | void PollForData(Session *sess); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /dsiproto.h: -------------------------------------------------------------------------------- 1 | #ifndef DSIPROTO_H 2 | #define DSIPROTO_H 3 | 4 | #include 5 | 6 | typedef struct DSIRequestHeader { 7 | Byte flags; 8 | Byte command; 9 | Word requestID; 10 | LongWord writeOffset; 11 | LongWord totalDataLength; 12 | LongWord reserved; 13 | } DSIRequestHeader; 14 | 15 | typedef struct DSIReplyHeader { 16 | Byte flags; 17 | Byte command; 18 | Word requestID; 19 | LongWord errorCode; 20 | LongWord totalDataLength; 21 | LongWord reserved; 22 | } DSIReplyHeader; 23 | 24 | #define DSI_HEADER_SIZE 16 25 | 26 | /* flags values */ 27 | #define DSI_REQUEST 0 28 | #define DSI_REPLY 1 29 | 30 | /* DSI command codes */ 31 | #define DSICloseSession 1 32 | #define DSICommand 2 33 | #define DSIGetStatus 3 34 | #define DSIOpenSession 4 35 | #define DSITickle 5 36 | #define DSIWrite 6 37 | #define DSIAttention 8 38 | 39 | /* The attention quantum supported by this implementation */ 40 | #define DSI_ATTENTION_QUANTUM 2 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /dsitest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "aspinterface.h" 7 | #include "atipmapping.h" 8 | #include "endian.h" 9 | 10 | ASPGetStatusRec getStatusRec; 11 | ASPOpenSessionRec openSessionRec; 12 | ASPCommandRec commandRec; 13 | ASPWriteRec writeRec; 14 | ASPCloseSessionRec closeSessionRec; 15 | Byte replyBuffer[1024]; 16 | 17 | struct FPFlushRec { 18 | Byte CommandCode; 19 | Byte Pad; 20 | Word VolumeID; 21 | } fpFlushRec; 22 | 23 | struct FPWriteRec { 24 | Byte CommandCode; 25 | Byte Flag; 26 | Word OForkRefNum; 27 | LongWord Offset; 28 | LongWord ReqCount; 29 | } fpWriteRec; 30 | 31 | #define kFPFlush 10 32 | #define kFPWrite 33 33 | 34 | int main(int argc, char **argv) 35 | { 36 | Boolean loadedTCP = FALSE; 37 | Boolean startedTCP = FALSE; 38 | cvtRec myCvtRec; 39 | int i; 40 | 41 | TLStartUp(); 42 | if (!TCPIPStatus()) { 43 | LoadOneTool(54, 0x0300); /* load Marinetti 3.0+ */ 44 | if (toolerror()) 45 | goto error; 46 | loadedTCP = TRUE; 47 | TCPIPStartUp(); 48 | if (toolerror()) 49 | goto error; 50 | startedTCP = TRUE; 51 | } 52 | 53 | atipMapping.networkNumber = 0xFFFF; 54 | atipMapping.node = 0xFF; 55 | atipMapping.socket = 0xFF; 56 | 57 | TCPIPConvertIPCToHex(&myCvtRec, argv[1]); 58 | atipMapping.ipAddr = myCvtRec.cvtIPAddress; 59 | atipMapping.port = 548; 60 | 61 | // Do the call 62 | getStatusRec.async = AT_SYNC; 63 | getStatusRec.command = aspGetStatusCommand; 64 | getStatusRec.completionPtr = 0; 65 | getStatusRec.slsNet = atipMapping.networkNumber; 66 | getStatusRec.slsNode = atipMapping.node; 67 | getStatusRec.slsSocket = atipMapping.socket; 68 | getStatusRec.bufferLength = sizeof(replyBuffer); 69 | getStatusRec.bufferAddr = (LongWord)&replyBuffer; 70 | 71 | DispatchASPCommand((SPCommandRec *)&getStatusRec); 72 | 73 | #if 0 74 | for (i=0; i= ' ' && replyBuffer[i] <= 126) 82 | printf("%c", replyBuffer[i]); 83 | else 84 | printf(" "); 85 | } 86 | printf("\n"); 87 | 88 | openSessionRec.async = AT_SYNC; 89 | openSessionRec.command = aspOpenSessionCommand; 90 | openSessionRec.completionPtr = 0; 91 | openSessionRec.slsNet = atipMapping.networkNumber; 92 | openSessionRec.slsNode = atipMapping.node; 93 | openSessionRec.slsSocket = atipMapping.socket; 94 | openSessionRec.attnRtnAddr = NULL; // not used for now 95 | 96 | printf("Opening...\n"); 97 | DispatchASPCommand((SPCommandRec *)&openSessionRec); 98 | 99 | printf("refnum = %i\n", openSessionRec.refNum); 100 | printf("result code = %04x\n", openSessionRec.result); 101 | if (openSessionRec.result) 102 | goto error; 103 | 104 | writeRec.async = AT_SYNC; 105 | writeRec.command = aspWriteCommand; 106 | writeRec.completionPtr = 0; 107 | writeRec.refNum = openSessionRec.refNum; 108 | writeRec.cmdBlkLength = sizeof(fpWriteRec); 109 | writeRec.cmdBlkAddr = (LongWord)&fpWriteRec; 110 | fpWriteRec.CommandCode = kFPWrite; 111 | fpWriteRec.ReqCount = htonl(16); 112 | writeRec.writeDataLength = 16; 113 | writeRec.writeDataAddr = (LongWord)&openSessionRec; 114 | writeRec.replyBufferLen = sizeof(replyBuffer); 115 | writeRec.replyBufferAddr = (LongWord)&replyBuffer; 116 | 117 | printf("Sending write...\n"); 118 | DispatchASPCommand((SPCommandRec *)&writeRec); 119 | printf("result code = %04x, write result = %08lx\n", 120 | writeRec.result, writeRec.cmdResult); 121 | 122 | commandRec.async = AT_SYNC; 123 | commandRec.command = aspCommandCommand; 124 | commandRec.completionPtr = 0; 125 | commandRec.refNum = openSessionRec.refNum; 126 | commandRec.cmdBlkLength = sizeof(fpFlushRec); 127 | commandRec.cmdBlkAddr = (LongWord)&fpFlushRec; 128 | fpFlushRec.CommandCode = kFPFlush; 129 | commandRec.replyBufferLen = sizeof(replyBuffer); 130 | commandRec.replyBufferAddr = (LongWord)&replyBuffer; 131 | 132 | printf("Sending command...\n"); 133 | DispatchASPCommand((SPCommandRec *)&commandRec); 134 | printf("result code = %04x, command result = %08lx\n", 135 | commandRec.result, commandRec.cmdResult); 136 | 137 | closeSessionRec.async = AT_SYNC; 138 | closeSessionRec.command = aspCloseSessionCommand; 139 | closeSessionRec.completionPtr = 0; 140 | closeSessionRec.refNum = openSessionRec.refNum; 141 | 142 | printf("Closing...\n"); 143 | DispatchASPCommand((SPCommandRec *)&closeSessionRec); 144 | printf("result code = %04x\n", closeSessionRec.result); 145 | 146 | error: 147 | if (startedTCP) 148 | TCPIPShutDown(); 149 | if (loadedTCP) 150 | UnloadOneTool(54); 151 | TLShutDown(); 152 | 153 | } 154 | -------------------------------------------------------------------------------- /dumpcmdtbl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "asmglue.h" 4 | 5 | LongWord *cmdTable = (LongWord *)0xE1D600; 6 | 7 | LongWord cmdTableCopy[256]; 8 | 9 | int main(void) { 10 | int i; 11 | Word savedStateReg; 12 | 13 | IncBusyFlag(); 14 | savedStateReg = ForceLCBank2(); 15 | for (i = 0; i <= 0xFF; i++) { 16 | cmdTableCopy[i] = cmdTable[i]; 17 | } 18 | RestoreStateReg(savedStateReg); 19 | DecBusyFlag(); 20 | 21 | for (i = 0; i <= 0xFF; i++) { 22 | printf("%02x: %08lx\n", i, cmdTableCopy[i]); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /endian.asm: -------------------------------------------------------------------------------- 1 | case on 2 | 3 | htons start 4 | ntohs entry 5 | ply 6 | phb 7 | plx 8 | pla 9 | xba 10 | phx 11 | plb 12 | phy 13 | rtl 14 | end 15 | 16 | htonl start 17 | ntohl entry 18 | lda 4,s 19 | xba 20 | tax 21 | lda 6,s 22 | xba 23 | tay 24 | phb 25 | pla 26 | sta 3,s 27 | pla 28 | sta 3,s 29 | plb 30 | tya 31 | rtl 32 | end 33 | -------------------------------------------------------------------------------- /endian.h: -------------------------------------------------------------------------------- 1 | #ifndef ENDIAN_H 2 | #define ENDIAN_H 3 | 4 | #include 5 | 6 | /* 7 | * Undefine these in case they're defined as macros. 8 | * (In particular, GNO has broken macro definitions for these.) 9 | */ 10 | #undef htons 11 | #undef ntohs 12 | #undef htonl 13 | #undef ntohl 14 | 15 | Word htons(Word); 16 | Word ntohs(Word); 17 | LongWord htonl(LongWord); 18 | LongWord ntohl(LongWord); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /endiantest.c: -------------------------------------------------------------------------------- 1 | #include "endian.h" 2 | #include 3 | 4 | int main(void) 5 | { 6 | printf("%lx\n", htonl(0x12345678)); 7 | printf("%x\n", htons(0xabcd)); 8 | } 9 | -------------------------------------------------------------------------------- /installcmds.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include 4 | #include 5 | #include "asmglue.h" 6 | #include "cmdproc.h" 7 | #include "installcmds.h" 8 | 9 | typedef struct NewCmd { 10 | Word cmdNum; 11 | void (*cmdAddr)(void); 12 | LongWord *jslOldCmdLocation; 13 | } NewCmd; 14 | 15 | NewCmd newCmds[] = { 16 | {aspGetStatusCommand, cmdProc, NULL}, 17 | {aspOpenSessionCommand, cmdProc, NULL}, 18 | {aspCloseSessionCommand, cmdProc, NULL}, 19 | {aspCommandCommand, cmdProc, NULL}, 20 | {aspWriteCommand, cmdProc, NULL}, 21 | {nbpLookupNameCommand, nbpCmdProc, NULL}, 22 | {pfiLoginCommand, pfiLoginCmdProc, &jslOldPFILogin}, 23 | {pfiLogin2Command, pfiLogin2CmdProc, &jslOldPFILogin2}, 24 | {pfiLoginContCommand, pfiLoginContCmdProc, &jslOldPFILoginCont}, 25 | {pfiListSessions2Command, pfiListSessions2CmdProc, &jslOldPFIListSessions2}, 26 | {0, 0, 0} 27 | }; 28 | 29 | LongWord *cmdTable = (LongWord *)0xE1D600; 30 | 31 | LongWord oldCmds[MAX_CMD + 1]; /* holds old entries for commands we changed */ 32 | 33 | #define JSL 0x22 34 | 35 | void installCmds(void) { 36 | Word savedStateReg; 37 | NewCmd *cmd; 38 | 39 | savedStateReg = ForceLCBank2(); 40 | 41 | for (cmd = newCmds; cmd->cmdNum != 0; cmd++) { 42 | oldCmds[cmd->cmdNum] = cmdTable[cmd->cmdNum]; 43 | cmdTable[cmd->cmdNum] = 44 | (oldCmds[cmd->cmdNum] & 0xFF000000) | (LongWord)cmd->cmdAddr; 45 | if (cmd->jslOldCmdLocation != NULL) 46 | *cmd->jslOldCmdLocation = JSL | (oldCmds[cmd->cmdNum] << 8); 47 | } 48 | 49 | RestoreStateReg(savedStateReg); 50 | } 51 | -------------------------------------------------------------------------------- /installcmds.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTALLCMDS_H 2 | #define INSTALLCMDS_H 3 | 4 | #include 5 | 6 | #define MAX_CMD rpmFlushSessionCommand 7 | 8 | extern LongWord oldCmds[MAX_CMD + 1]; 9 | 10 | void installCmds(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /listsess.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef struct ListSessions2ResultRec { 5 | Byte sessionRefNum; 6 | Byte slotDrive; 7 | char volumeName[28]; 8 | Word volumeID; 9 | char serverName[32]; 10 | char zoneName[33]; 11 | } ListSessions2ResultRec; 12 | 13 | ListSessions2ResultRec ls2Results[20]; 14 | 15 | int main(void) 16 | { 17 | PFIListSessions2Rec listSessions2Rec; 18 | int i; 19 | 20 | listSessions2Rec.async = 0; 21 | listSessions2Rec.command = pfiListSessions2Command; 22 | listSessions2Rec.bufferLength = sizeof(ls2Results); 23 | listSessions2Rec.bufferPtr = (LongWord)&ls2Results; 24 | i = _CALLAT(&listSessions2Rec); 25 | if (i != 0) { 26 | fprintf(stderr, "Error %04x\n", listSessions2Rec.result); 27 | return 1; 28 | } 29 | 30 | for (i = 0; i < listSessions2Rec.entriesRtn; i++) { 31 | printf("Session=%i, Volume=%b, Server=%b, Zone=%b\n", 32 | ls2Results[i].sessionRefNum, ls2Results[i].volumeName, 33 | ls2Results[i].serverName, ls2Results[i].zoneName); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /readtcp.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include "readtcp.h" 4 | #include "session.h" 5 | #include 6 | #include 7 | 8 | #define buffTypePointer 0x0000 /* For TCPIPReadTCP() */ 9 | #define buffTypeHandle 0x0001 10 | #define buffTypeNewHandle 0x0002 11 | 12 | void InitReadTCP(Session *sess, LongWord readCount, void *readPtr) { 13 | sess->readCount = readCount; 14 | sess->readPtr = readPtr; 15 | } 16 | 17 | 18 | ReadStatus TryReadTCP(Session *sess) { 19 | rrBuff rrBuff; 20 | 21 | TCPIPPoll(); 22 | sess->tcperr = TCPIPReadTCP(sess->ipid, buffTypePointer, (Ref)sess->readPtr, 23 | sess->readCount, &rrBuff); 24 | sess->toolerr = toolerror(); 25 | if (sess->tcperr || sess->toolerr) { 26 | sess->dsiStatus = error; 27 | return rsError; 28 | } 29 | 30 | sess->readCount -= rrBuff.rrBuffCount; 31 | sess->readPtr += rrBuff.rrBuffCount; 32 | 33 | if (sess->readCount == 0) { 34 | return rsDone; 35 | } else { 36 | return rsWaiting; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /readtcp.h: -------------------------------------------------------------------------------- 1 | #ifndef READTCP_H 2 | #define READTCP_H 3 | 4 | #include "session.h" 5 | 6 | typedef enum ReadStatus { 7 | rsDone, 8 | rsWaiting, 9 | rsError 10 | } ReadStatus; 11 | 12 | void InitReadTCP(Session *sess, LongWord readCount, void *readPtr); 13 | ReadStatus TryReadTCP(Session *sess); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /savenames.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "aspinterface.h" 4 | #include "asmglue.h" 5 | 6 | /* 7 | * This code is responsible for saving away the server name and zone at 8 | * the time of an FILogin(2) call, and then writing them into the result 9 | * records returned by FIListSessions2. 10 | * 11 | * This code is needed because FIListSessions2 has a bug that will cause 12 | * these fields of its result records to contain garbage for AFP-over-TCP 13 | * connections and in some cases also wrong values for concurrent 14 | * AFP-over-AppleTalk sessions. 15 | */ 16 | 17 | #define SERVER_NAME_SIZE 32 18 | #define ZONE_NAME_SIZE 33 19 | 20 | typedef struct NameRec { 21 | unsigned char serverName[SERVER_NAME_SIZE]; 22 | unsigned char zoneName[ZONE_NAME_SIZE]; 23 | } NameRec; 24 | 25 | #define MAX_ASP_SESSION_NUM 8 26 | #define MAX_PFI_SESSIONS 8 27 | 28 | NameRec aspSessionNames[MAX_ASP_SESSION_NUM + 1]; 29 | NameRec dsiSessionNames[MAX_SESSIONS]; 30 | 31 | Byte aspActiveFlags[MAX_ASP_SESSION_NUM + 1] = {0}; 32 | Byte dsiActiveFlags[MAX_SESSIONS] = {0}; 33 | Byte extraActive = 0; 34 | 35 | static unsigned char emptyStr[1] = {0}; 36 | 37 | typedef struct ListSessions2Buffer { 38 | Byte refNum; 39 | Byte slotDrive; 40 | char volName[28]; 41 | Word volID; 42 | char serverName[32]; 43 | char zoneName[33]; 44 | } ListSessions2Buffer; 45 | 46 | 47 | /* Count number of active sessions (may overestimate) */ 48 | static unsigned int countSess(void) { 49 | unsigned int activeCount; 50 | unsigned int i; 51 | 52 | activeCount = extraActive; 53 | for (i = 0; i <= MAX_ASP_SESSION_NUM; i++) { 54 | activeCount += aspActiveFlags[i]; 55 | } 56 | for (i = 0; i < MAX_SESSIONS; i++) { 57 | activeCount += aspActiveFlags[i]; 58 | } 59 | return activeCount; 60 | } 61 | 62 | /* 63 | * This is called following an FILogin or FILogin2 call, to save the names. 64 | */ 65 | #pragma databank 1 66 | void SaveNames(PFILogin2Rec *commandRec) { 67 | unsigned char *serverName, *zoneName; 68 | unsigned int i; 69 | unsigned int strLen; 70 | NameRec *nameRec; 71 | Word stateReg; 72 | 73 | stateReg = ForceRomIn(); 74 | 75 | /* 76 | * Don't save names for failed connections, but if we get a 77 | * "too many sessions" error when there aren't really too many sessions, 78 | * then change it to something more appropriate (this can happen when 79 | * the ASP/DSI layer returns a network error). 80 | */ 81 | if (commandRec->result != 0 && commandRec->result != pfiLoginContErr) { 82 | if (commandRec->result == pfiTooManySessErr 83 | && countSess() < MAX_PFI_SESSIONS) 84 | { 85 | commandRec->result = pfiUnableOpenSessErr; 86 | } 87 | goto ret; 88 | } 89 | 90 | if (commandRec->result == 0 && commandRec->sessRefID >= SESSION_NUM_START) { 91 | sessionTbl[commandRec->sessRefID - SESSION_NUM_START].loggedIn = TRUE; 92 | } 93 | 94 | if (commandRec->sessRefID <= MAX_ASP_SESSION_NUM) { 95 | nameRec = &aspSessionNames[commandRec->sessRefID]; 96 | aspActiveFlags[commandRec->sessRefID] = 1; 97 | } else if (commandRec->sessRefID >= SESSION_NUM_START) { 98 | nameRec = &dsiSessionNames[commandRec->sessRefID - SESSION_NUM_START]; 99 | dsiActiveFlags[commandRec->sessRefID - SESSION_NUM_START] = 1; 100 | } else { 101 | extraActive = 255; 102 | goto ret; 103 | } 104 | 105 | /* Get the names (or default to empty strings if not provided) */ 106 | if (commandRec->command == pfiLogin2Command) { 107 | serverName = commandRec->serverName; 108 | zoneName = commandRec->zoneName; 109 | } else { 110 | serverName = emptyStr; 111 | zoneName = emptyStr; 112 | } 113 | 114 | memset(nameRec, 0, sizeof(*nameRec)); 115 | strLen = serverName[0]; 116 | if (strLen >= SERVER_NAME_SIZE) 117 | strLen = SERVER_NAME_SIZE - 1; 118 | memcpy(&nameRec->serverName, serverName, strLen + 1); 119 | nameRec->serverName[0] = strLen; 120 | strLen = zoneName[0]; 121 | if (strLen >= ZONE_NAME_SIZE) 122 | strLen = ZONE_NAME_SIZE - 1; 123 | memcpy(&nameRec->zoneName, zoneName, strLen + 1); 124 | nameRec->zoneName[0] = strLen; 125 | 126 | ret: 127 | RestoreStateReg(stateReg); 128 | } 129 | #pragma databank 0 130 | 131 | #pragma databank 1 132 | void InsertCorrectNames(PFIListSessions2Rec *commandRec) { 133 | unsigned int i; 134 | ListSessions2Buffer *resultBuf; 135 | NameRec *nameRec; 136 | Word stateReg; 137 | 138 | stateReg = ForceRomIn(); 139 | 140 | if (commandRec->result != 0 && commandRec->result != pfiBufferToSmallErr) 141 | goto ret; 142 | 143 | resultBuf = (ListSessions2Buffer *)commandRec->bufferPtr; 144 | for (i = 0; i < commandRec->entriesRtn; i++) { 145 | if (resultBuf[i].refNum <= MAX_ASP_SESSION_NUM) { 146 | nameRec = &aspSessionNames[resultBuf[i].refNum]; 147 | } else if (resultBuf[i].refNum >= SESSION_NUM_START) { 148 | nameRec = &dsiSessionNames[resultBuf[i].refNum - SESSION_NUM_START]; 149 | } else { 150 | continue; 151 | } 152 | 153 | memcpy(&resultBuf[i].serverName, nameRec, sizeof(*nameRec)); 154 | } 155 | 156 | ret: 157 | RestoreStateReg(stateReg); 158 | } 159 | #pragma databank 0 160 | 161 | #pragma databank 1 162 | void PostLoginCont(PFILoginContRec *commandRec) { 163 | Word stateReg; 164 | 165 | stateReg = ForceRomIn(); 166 | 167 | if (commandRec->result == 0 && commandRec->sessRefID >= SESSION_NUM_START) { 168 | sessionTbl[commandRec->sessRefID - SESSION_NUM_START].loggedIn = TRUE; 169 | } 170 | 171 | RestoreStateReg(stateReg); 172 | } 173 | #pragma databank 0 174 | -------------------------------------------------------------------------------- /session.h: -------------------------------------------------------------------------------- 1 | #ifndef SESSION_H 2 | #define SESSION_H 3 | 4 | #include 5 | #include 6 | #include "dsiproto.h" 7 | #include "atipmapping.h" 8 | 9 | /* Common portion of records for the various SP... commands */ 10 | typedef struct SPCommandRec { 11 | Byte async; 12 | Byte command; 13 | Word result; 14 | /* Below are in records for several ASP commands, but not all. */ 15 | LongWord completionPtr; 16 | Byte refNum; 17 | } SPCommandRec; 18 | 19 | typedef enum DSISessionStatus { 20 | unused = 0, 21 | awaitingHeader, 22 | awaitingPayload, 23 | error, 24 | needsReset /* set after control-reset in P8 mode */ 25 | } DSISessionStatus; 26 | 27 | typedef struct FPZzzzzRec { 28 | Word CommandCode; /* includes pad byte */ 29 | LongWord Flag; 30 | } FPZzzzzRec; 31 | 32 | typedef struct Session { 33 | /* Marinetti TCP connection status */ 34 | Word ipid; 35 | Boolean tcpLoggedIn; 36 | 37 | DSISessionStatus dsiStatus; 38 | 39 | /* Information on command currently being processed, if any. */ 40 | Boolean commandPending; 41 | SPCommandRec *spCommandRec; 42 | DSIRequestHeader request; 43 | 44 | DSIReplyHeader reply; 45 | 46 | Word nextRequestID; 47 | 48 | /* Buffer to hold reply payload data */ 49 | void *replyBuf; 50 | Word replyBufLen; 51 | 52 | /* Buffer to hold unusable payload data that doesn't fit in replyBuf */ 53 | void *junkBuf; 54 | 55 | /* ReadTCP status */ 56 | LongWord readCount; 57 | Byte *readPtr; 58 | 59 | /* Set by DSIAttention */ 60 | Word attentionCode; 61 | 62 | /* Marinetti error codes, both the tcperr* value and any tool error */ 63 | Word tcperr; 64 | Word toolerr; 65 | 66 | /* AppleTalk<->IP address mapping used for this session */ 67 | ATIPMapping atipMapping; 68 | 69 | /* Attention routine header (followed by the routine) */ 70 | ASPAttentionHeaderRec *attention; 71 | 72 | /* Is this session successfully logged in? */ 73 | Boolean loggedIn; 74 | 75 | /* Records for sending (fake) sleep messages to server */ 76 | ASPCommandRec sleepCommandRec; 77 | FPZzzzzRec fpZzzzzRec; 78 | Byte fpZzzzzResponseRec[4]; 79 | } Session; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /strncasecmp.c: -------------------------------------------------------------------------------- 1 | # ifdef __ORCAC__ 2 | # pragma noroot 3 | # endif 4 | 5 | #include 6 | #include 7 | 8 | int strncasecmp(const char *s1, const char *s2, size_t n) 9 | { 10 | if (n == 0) 11 | return 0; 12 | 13 | while (n > 1 && *s1 != 0 && tolower(*s1) == tolower(*s2)) { 14 | s1++; 15 | s2++; 16 | n--; 17 | } 18 | 19 | return (int)*s1 - (int)*s2; 20 | } 21 | -------------------------------------------------------------------------------- /strncasecmp.h: -------------------------------------------------------------------------------- 1 | #ifndef STRNCASECMP_H 2 | #define STRNCASECMP_H 3 | 4 | int strncasecmp(const char *s1, const char *s2, size_t n); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /tcpconnection.c: -------------------------------------------------------------------------------- 1 | #pragma noroot 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "atipmapping.h" 9 | #include "session.h" 10 | 11 | /* Make a TCP connection to the address mapped to the specified AT address. 12 | * sess->spCommandRec should be an SPGetStatus or SPOpenSession command. 13 | * 14 | * On success, returns 0 and sets sess->ipid. 15 | * On failure, returns an ASP error code. 16 | */ 17 | Word StartTCPConnection(Session *sess) { 18 | Word tcperr; 19 | ASPOpenSessionRec *commandRec; 20 | srBuff mySRBuff; 21 | LongWord initialTime; 22 | 23 | commandRec = (ASPOpenSessionRec *)sess->spCommandRec; 24 | 25 | sess->atipMapping = atipMapping; 26 | 27 | if (TCPIPGetConnectStatus() == FALSE) { 28 | TCPIPConnect(NULL); 29 | if (toolerror()) 30 | return aspNetworkErr; 31 | } 32 | 33 | sess->ipid = 34 | TCPIPLogin(userid(), atipMapping.ipAddr, atipMapping.port, 0, 0x40); 35 | if (toolerror()) 36 | return aspNetworkErr; 37 | 38 | tcperr = TCPIPOpenTCP(sess->ipid); 39 | if (toolerror()) { 40 | TCPIPLogout(sess->ipid); 41 | return aspNetworkErr; 42 | } else if (tcperr != tcperrOK) { 43 | TCPIPLogout(sess->ipid); 44 | return aspNoRespErr; 45 | } 46 | 47 | initialTime = GetTick(); 48 | do { 49 | TCPIPPoll(); 50 | TCPIPStatusTCP(sess->ipid, &mySRBuff); 51 | } while (mySRBuff.srState == TCPSSYNSENT && GetTick()-initialTime < 15*60); 52 | if (mySRBuff.srState != TCPSESTABLISHED) { 53 | TCPIPAbortTCP(sess->ipid); 54 | TCPIPLogout(sess->ipid); 55 | return aspNoRespErr; 56 | } 57 | 58 | sess->tcpLoggedIn = TRUE; 59 | return 0; 60 | } 61 | 62 | void EndTCPConnection(Session *sess) { 63 | if (sess->tcpLoggedIn) { 64 | TCPIPPoll(); 65 | TCPIPAbortTCP(sess->ipid); 66 | TCPIPLogout(sess->ipid); 67 | sess->tcpLoggedIn = FALSE; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tcpconnection.h: -------------------------------------------------------------------------------- 1 | #ifndef TCPCONNECTION_H 2 | #define TCPCONNECTION_H 3 | 4 | #include "session.h" 5 | 6 | Word StartTCPConnection(Session *sess); 7 | void EndTCPConnection(Session *sess); 8 | 9 | #endif 10 | --------------------------------------------------------------------------------