tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
# use "sh" (not "csh") to unpack these archives
mkdir copyright
mkdir appl
mkdir t
mkdir net
mkdir sfmget
#
echo extracting copyright/notice.text...
cat >copyright/notice.text <<'!E!O!F!'
The Lisa Pascal distribution of MacIP is Copyright 1985 by
Carnegie-Mellon University. It is made available for
public use without fee, and without warranties express
or implied. It is derived from the MIT version of the
Internet protocols for the IBM PC, the copyright notice
for which appears directly below. Initial work on the
MacIP package was done by Mark Sherman; his copyright
notice is shown at bottom. The current version was done
by Tim Maroney at C-MU.
Copyright 1983, 1984 Massachusetts Institute of Technology
Permission to use, copy, modify, and distribute this program
for any purpose and without fee is hereby granted, provided
that this copyright and permission notice appear on all copies
and supporting documentation, the name of M.I.T. not be used
in advertising or publicity pertaining to distribution of the
program without specific prior permission, and notice be given
in supporting documentation that copying and distribution is
by permission of M.I.T. M.I.T. makes no representations about
the suitability of this software for any purpose. It is pro-
vided "as is" without express or implied warranty.
Copyright 1984 Mark Sherman
Permission to use, copy, modify, and distribute this program
for any purpose and without fee is hereby granted, provided
that this copyright and permission notice appear on all copies
and supporting documentation, the name of Mark Sherman not be used
in advertising or publicity pertaining to distribution of the
program without specific prior permission, and notice be given
in supporting documentation that copying and distribution is
by permission of Mark Sherman. Mark Sherman makes no representations about
the suitability of this software for any purpose. It is pro-
vided "as is" without express or implied warranty.
People acquiring, modifying or using this version of the
software are requested to let the author know by sending correspondence to:
Mark Sherman
Department of Mathematics and Computer Science
Dartmouth College
Hanover, NH 03755
Mark.Sherman@CMU-CS-A.ARPA
mss@Dartmouth.CSNet
...decvax!dartvax!mss
!E!O!F!
#
#
echo extracting t/applback.text...
cat >t/applback.text <<'!E!O!F!'
$EXEC
R{un}bin-maccom
S{ettings}C{onvert Text}Y{es}
Y{es to filename searches}
F{inder}TEXT{type}
EDIT{Creator}
Y{es bundle bit}
N{o. don't always prompt}
Q{uit settings}
Lappl-cust.text
appl/cust.text
Lappl-tftp.text
appl/tftp.text
Lappl-telnet.text
appl/telnet.text
Lappl-custr.text
appl/custr.text
Lappl-tftpr.text
appl/tftpr.text
Lappl-telnetr.text
appl/telnetr.text
Lappl-cust.link.text
appl/cust.link.text
Lappl-tftp.link.text
appl/tftp.link.text
Lappl-telnet.link.text
appl/telnet.link.text
Lappl-custo.text
appl/custo.text
Lappl-tftpo.text
appl/tftpo.text
Lappl-telneto.text
appl/telneto.text
Lcopyright-notice.text
copyright/notice.text
Lt-assemble.text
t/assemble.text
Lt-compile.text
t/compile.text
Lt-depend.text
t/depend.text
Lt-link.text
t/link.text
Lt-make.text
t/make.text
Lt-netback.text
t/netback.text
Lt-applback.text
t/applback.text
Lsfmget-sfmgetfile.text
sfmget/sfmgetfile.text
Lsfmget-sfmgetr.text
sfmget/sfmgetr.text
Lsfmget-sfmtest.text
sfmget/sfmtest.text
Lsfmget-sfmtesto.text
sfmget/sfmtesto.text
Lsfmget-sfmtestr.text
sfmget/sfmtestr.text
Lsfmget-sfmget_asm.text
sfmget/sfmget_asm.text
Lsfmget-make.text
sfmget/make.text
E{ject}Q{uit}
$ENDEXEC
!E!O!F!
#
#
echo extracting t/assemble.text...
cat >t/assemble.text <<'!E!O!F!'
$EXEC
$if not (exists("-upper-%0.obj")) then
A{ssemble}%0 { input file: PROG.TEXT where PROG is the argument }
{no listing file}
%0 {output file PROG.OBJ}
$elseif newer("-upper-%0.text","-upper-%0.obj") then
A{ssemble}%0 { input file: PROG.TEXT where PROG is the argument }
{no listing file}
%0 {output file PROG.OBJ}
$endif
$DOIT
$ENDEXEC
!E!O!F!
#
#
echo extracting t/compile.text...
cat >t/compile.text <<'!E!O!F!'
$EXEC
P{ascal compiler}%0 { input file: PROG.TEXT where PROG is the argument }
{no listing file}
%0 {output file PROG.I}
$DOIT { forces compilation now so later dating will be right }
$ENDEXEC
!E!O!F!
#
#
echo extracting t/depend.text...
cat >t/depend.text <<'!E!O!F!'
*
*{ Please note the copyright notice in the file "copyright/notice" }
*
EXEC(progfile,dep1,dep2,dep3,dep4,dep5,dep6,dep7,dep8)
{ t-depend provides a make-like facility in which a Pascal ".text" file may
be compiled or not depending on whether it is up to date with respect to
".obj" files it depends on. It is recursive, and is meant to be submitted
from another exec file, as shown:
SUBMIT t-depend(progfile,dep1,dep2,...,dep8) }
IF dep1 = '' THEN { no dependencies given or left }
IF NEWER("-upper-[progfile].text","-upper-[progfile].obj") THEN
clear screen
writeln "[progfile].text is newer than [progfile].obj"
SUBMIT t-compile([progfile])
ENDIF
ELSEIF NEWER("-upper-[dep1].obj","-upper-[progfile].obj") THEN
clear screen
writeln "[dep1].obj is newer than [progfile].obj"
SUBMIT t-compile([progfile])
ELSE
SUBMIT t-depend([progfile],[dep2],[dep3],[dep4],[dep5],[dep6],[dep7],[dep8])
ENDIF
ENDEXEC
!E!O!F!
#
#
echo extracting t/link.text...
cat >t/link.text <<'!E!O!F!'
$EXEC
{ USAGE: R<t-link(prog,creator) }
{ Modified to user RFB, resource file builder. Each application has these files:
%0.obj -- object file from Lisa Pascal compiler
%0r.text -- instructions to resource compiler, makes %0r.rsrc
%0o.text -- brief file to convert linked file to CODE resource file %0o.rsrc
%0i.rsrc -- icons and other resource information built with Resource Editor
}
$IF NEWER("%0r.text","%0r.rsrc") THEN
r{un}bin-RMaker
%0r
$ENDIF
$IF NEWER("%0.obj","%0L.obj") THEN
$SUBMIT %0.link.text
R{un}bin-rmaker { convert linked file to CODE resource file }
%0o
$ENDIF
$
R{un}RFB { Resource file builder }
%0.rsrc { output file }
%0r.rsrc { compiled resource file }
*
$IF EXISTS("%0i.rsrc") THEN
%0i.rsrc { icons, etc. }
*
$ENDIF
%0o.rsrc { CODE resource file }
*
{ no more input files }
{ quit }
R{un}bin-MacCom { write the disk }
RYFYL%0.RSRC { strip prefix; set finder info; send PROG.RSRC via Lisa->Mac}
%0
APPL { set type to APPL }
%1 { set creator to second arg }
Y{es, bundle bit}Q{uit}
$DOIT
$ENDEXEC
!E!O!F!
#
#
echo extracting t/make.text...
cat >t/make.text <<'!E!O!F!'
EXEC
submit t-assemble(net-task_asm)
submit t-assemble(net-call_asm)
submit t-assemble(net-ip_listen)
submit t-depend(net-calls,net-call_asm)
submit t-depend(net-err_lib)
submit t-depend(net-timer_lib)
submit t-depend(net-cust_lib)
submit t-depend(net-task_lib,net-err_lib,net-task_asm)
submit t-depend(net-ip_lib,net-task_lib,net-timer_lib,net-calls,net-err_lib)
submit t-depend(net-ip_lib,net-cust_lib)
submit t-depend(net-name_host,net-task_lib,net-timer_lib,net-ip_lib)
submit t-depend(net-icmp_lib,net-task_lib,net-timer_lib,net-ip_lib)
submit t-depend(net-icmp_lib,net-calls,net-err_lib)
submit t-depend(net-arp_lib,net-task_lib,net-ip_lib,net-timer_lib)
submit t-depend(net-udp_lib,net-task_lib,net-timer_lib)
submit t-depend(net-udp_lib,net-ip_lib,net-icmp_lib,net-calls,net-err_lib)
submit t-depend(net-name_user,net-task_lib,net-timer_lib)
submit t-depend(net-name_user,net-udp_lib,net-ip_lib,net-err_lib)
submit t-depend(net-tftp_defs,net-task_lib,net-timer_lib,net-err_lib)
submit t-depend(net-tftp_defs,net-ip_lib,net-udp_lib,net-calls)
submit t-depend(net-tftp_file,net-task_lib,net-timer_lib,net-err_lib)
submit t-depend(net-tftp_file,net-ip_lib,net-udp_lib,net-calls,net-tftp_defs)
submit t-depend(net-tftp_lib,net-task_lib,net-timer_lib,net-err_lib,net-ip_lib)
submit t-depend(net-tftp_lib,net-udp_lib,net-calls,net-tftp_defs,net-tftp_file)
submit t-depend(net-term_lib)
submit t-depend(net-tcp_lib,net-task_lib,net-timer_lib)
submit t-depend(net-tcp_lib,net-ip_lib,net-calls,net-err_lib,net-term_lib)
submit t-depend(net-tn_lib,net-task_lib,net-timer_lib,net-name_host,net-tftp_defs)
submit t-depend(net-tn_lib,net-ip_lib,net-tcp_lib,net-udp_lib,net-tftp_lib)
submit t-depend(net-tn_lib,net-name_user,net-err_lib,net-term_lib,net-calls)
submit t-depend(appl-cust,net-task_lib,net-ip_lib,net-name_user,net-cust_lib)
submit t-depend(appl-tftp,net-task_lib,net-timer_lib,net-name_host,net-arp_lib)
submit t-depend(appl-tftp,net-ip_listen,net-ip_lib,net-udp_lib,net-tftp_defs)
submit t-depend(appl-tftp,net-name_user,net-tftp_lib,net-err_lib,net-icmp_lib)
submit t-depend(appl-telnet,net-task_lib,net-timer_lib,net-err_lib,net-arp_lib)
submit t-depend(appl-telnet,net-ip_listen,net-ip_lib,net-udp_lib)
submit t-depend(appl-telnet,net-tcp_lib,net-tn_lib,net-term_lib,net-icmp_lib)
submit t-link(appl-cust,CUST)
submit t-link(appl-tftp,TFTP)
submit t-link(appl-telnet,TLNT)
$rbin-maccom
$EQ
ENDEXEC
!E!O!F!
#
#
echo extracting t/netback.text...
cat >t/netback.text <<'!E!O!F!'
$EXEC
R{un}bin-maccom
S{ettings}C{onvert Text}Y{es}
Y{es to filename searches}
F{inder}TEXT{type}
EDIT{Creator}
Y{es bundle bit}
N{o. don't always prompt}
Q{uit settings}
Lnet-calls.text
net/calls.text
Lnet-call_asm.text
net/call_asm.text
Lnet-err_lib.text
net/err_lib.text
Lnet-task_lib.text
net/task_lib.text
Lnet-task_asm.text
net/task_asm.text
Lnet-timer_lib.text
net/timer_lib.text
Lnet-cust_lib.text
net/cust_lib.text
Lnet-name_host.text
net/name_host.text
Lnet-arp_lib.text
net/arp_lib.text
Lnet-ip_listen.text
net/ip_listen.text
Lnet-icmp_lib.text
net/icmp_lib.text
Lnet-ip_lib.text
net/ip_lib.text
Lnet-udp_lib.text
net/udp_lib.text
Lnet-name_user.text
net/name_user.text
Lnet-tftp_defs.text
net/tftp_defs.text
Lnet-tftp_file.text
net/tftp_file.text
Lnet-tftp_lib.text
net/tftp_lib.text
Lnet-tcp_lib.text
net/tcp_lib.text
Lnet-term_lib.text
net/term_lib.text
Lnet-tn_lib.text
net/tn_lib.text
E{ject}Q{uit}
$ENDEXEC
!E!O!F!
#
#
echo extracting user...
cat >user <<'!E!O!F!'
MacIP User Manual
Tim Maroney
November 1985
-----------------
This paper describes the programs that have been written to allow Apple
Macintosh computers on an Appletalk network to communicate with computers on
the Internet.
In order to use any of these programs, your Macintosh must be connected to
an Appletalk network that is connected to a router of some sort. One
example of a router is the Seagate router between Appletalk and Ethernet
that was developed at Stanford University. Another is the "Butcher Board"
router developed at C-MU. This is normally an issue for the system
maintenance staff, not a user; it is mentioned here so you won't think that
plugging an Appletalk connector into the back of your Mac magically allows
you to communicate with the Internet.
Three programs are available, CUSTOMIZE, TFTP, and TELNET. CUSTOMIZE
manages a customization file that contains various information for use by
the other programs; it does not talk to Internet sites directly. TFTP is a
simple file transfer program: it can be used to retrieve files to the
Macintosh from Internet sites, and vice versa. TELNET is the Internet
terminal emulator; with TELNET, you can log on and conduct a session at any
Internet site on which you have an account.
In order to run any of these programs, double-click its icon, as is normal
on the Macintosh.
TFTP
----
The TFTP program uses the trivial file transfer protocol running on top of
the user datagram protocol to allow reliable file transfer between a
Macintosh and an Internet site. The other Internet site may be a Macintosh
or another computer.
There are two possible modes in the TFTP program, server mode and user mode.
The TFTP program can be in one or the other mode, but not both. Initially
it is in neither mode; the first selection you make from the "File" menu
sets the mode.
Server mode is rarely useful: when a Mac is acting as a server, other
Internet sites can initiate file transfers to and from it, but the Mac is
completely tied up while it is acting as a server. To select server mode,
select "Server" from the "File" menu. Once this is selected, the only
"File" menu commands available are "Abort" and "Quit". You can still use
the commands from the "Show" menu to display information about the status of
the network.
User mode is the one usually used on the Mac. To select user mode, pick
either "Get" or "Put" from the "File" menu. This places you in user mode
and begins the file transfer process. In any file transfer there are two
files, a local file (the file on the Macintosh) and a remote file (the file
on the other Internet site). To get the local file name, the TFTP program
uses the Macintosh's standard file package, which will be familiar to you if
you have used other Macintosh applications. To get the remote file name,
the TFTP program uses a special-purpose remote file dialog. This contains
two fields which you can edit, one for the file's name and the other for the
name or address of the other Internet site.
Both the standard file dialogs and the remote file dialog have buttons
marked "OK" and "CANCEL". Clicking in the "OK" button (or hitting the
"Return" key on the keyboard) tells the program to go on to the next step of
the file transfer. Clicking "CANCEL" tells the program not to go ahead with
the file transfer and to return to plain user mode.
To get a file from another site to your Mac, use "Get". To put a file from
your Macintosh to another site, use "Put". If something is wrong with your
transfer, use "Abort" to terminate it. When you are entirely finished with
TFTP, use "Quit" to leave the program and return to the Macintosh Finder.
The trivial file transfer protocol uses "transfer modes" to control file
transfer. The transfer modes provide information about how the file is to
be transferred. The Macintosh TFTP provides four transfer modes, which can
be selected from the remote file dialog, or from the "Settings" menu. The
modes are "ASCII", "IMAGE", "OCTET", and "MACINTOSH". The transfer mode
must be selected before you choose "Get" or "Put". The initial transfer
mode is ASCII.
The first three transfer modes are almost identical, and are the only modes
that will be used with non-Macintosh computers in most cases. To transfer a
document to or receive a document from a non-Mac computer, you should use
"ASCII" or "IMAGE" modes. To transfer an application to or receive an
application from a non-Mac computer, you should use "OCTET" mode. To
exchange a file with a Macintosh, use "MACINTOSH" mode. Other computers
such as Vaxes and Suns will probably not support MACINTOSH mode transfers;
check with the system support staff for the computer in question.
A Macintosh file actually has two parts or "forks", the data fork and the
resource fork, unlike most other computers, on which files have only one
part. This was a silly design decision in the Macintosh, since almost all
files have one of the forks empty. It can also create problems in file
transfer between Macs and other machines. In MACINTOSH transfer mode, both
forks (as well as a little bit of data known as the "finder information")
are sent, so there is no problem. In ASCII and IMAGE modes, only the data
fork is sent: that will work for most documents such as MacWrite text files.
In OCTET mode, only the resource fork is sent: this suffices for most
Macintosh applications such as MacPaint or MacWrite. In those rare cases
when both the resource and data forks are used, and you wish to send the
file to a system that does not support MACINTOSH mode file transfers, you
should first use one of the file compression programs such as BINHEX (not
supplied with the Macintosh Internet programs, but widely available) to
compress both forks into the data fork of another file, and then send the
BINHEXed file's data fork.
After receiving an application in OCTET mode, it will often be necessary
to run the "Set File" program, distributed separately, to set the bundle bit
and the application's creator. Note: Over the long-haul networks such as
ARPANET and USENET, applications are usually transferred in "binhex" or some
other converted format, not in the binary form OCTET mode expects. To get
an application in binhex format to your Mac via TFTP, use an ASCII mode
transfer and then run binhex once the transfer is complete.
The "Settings" menu contains a "Remote Directory" command. Usually
Macintosh files are sent to a computer such as a UNIX (tm) machine, a
TOPS-20 system, or a VMS system, which has a notion of directories. When
you "Put" a file, a remote directory prefix will be attached to the
beginning of the file name if you have specified a remote directory. For
instance, to send all your files into the directory "/usr/you/macfiles" on a
UNIX machine, issue the "Remote Directory" command and specify
"/usr/you/macfiles/" (note the final slash). To send all your files to the
directory "pk:<foo.bar>" on a TOPS-20 machine, issue "Remote Directory" and
specify "pk:<foo.bar>". Remember that the directory is just a string which
is stuck onto the beginning of the remote file name; TFTP does not really
know anything about directories as such.
The file dialog that appears when you "Put" a file is a little different
from the standard one for the Macintosh. It has a check box which allows
you to select more than one file from the list of files you are given. All
files selected will be sent, one after another, until either all are sent or
an error happens. (You can bypass the check box and use shift-clicking to
select multiple files if you are familiar and comfortable with
shift-clicking.) This multiple-file capability is useful if you are using
TFTP to back up a number of files, usually program source code files, to
some other computer, because you do not have to issue a new "Put" command
for each file. All the files will be sent to the same host, with the same
directory prefix and transfer mode. The remote file dialog will appear
after you have specified multiple file names, allowing you to set the host,
the transfer mode, and the remote directory.
During file transfer in either user or server mode, some statistics about
the transfer will be displayed in a window that will appear on your screen.
If you want to get rid of the window, click anywhere in it and it will
vanish, reappearing when there is something else to say. The same goes for
windows that appear as a result of selecting a command in the "Show" menu.
TELNET
------
With TELNET, you can log in to any computer on the Internet for which you
have an account. The Macintosh TELNET is different from (and better than)
most other TELNET implementations in that you control the session with menu
commands instead of hard-to-remember and easy-to-screw-up control codes.
The TELNET terminal window emulates a DEC VT100 terminal, a popular type
that most host computers should know how to control.
To open a connection to another computer, use the "Open" command from the
"Commands" menu. You will be asked for the name or address of the remote
computer. To continue, click "OK"; to abort the attempt to connect, use
"CANCEL". The TELNET program will then try to establish the connection. If
it succeeds, the word "Open" will appear in the terminal window and the
remote computer will (in most cases) prompt you for your name and password.
When you are finished, logging out on the remote computer may automatically
close the connection, in which case "Closed" will appear on the screen. If
this does not happen, then you can yourself close the connection by using
the "Close" command in the "Commands" menu. You can also do this before you
log out, but you do not usually want to.
The keyboard sends what you would expect. For instance, holding down the
clover key and "A" causes a "control-A" to be sent. The chief exception is
the key in the upper left-hand corner, which sends an "ESCAPE" character. A
tilde ("~") can be sent by holding down "shift" and this key. It is a known
bug in this release that there is no way to send a backquote, or any of the
control characters associated with the numerical keys, such as "control-^".
It is possible to use TFTP from inside TELNET. Thus, you can log on to a
remote system, look around in its directories and so forth, and retrieve or
send files without having to leave TELNET. This assumes that the remote
computer has a TFTP command. The procedure is to issue a TFTP command on
the remote system that initiates a file transfer back to your Mac. In order
to do this, you will have to know your Mac's Internet address or name, which
can be discovered using a command in the "Show" menu. A TFTP server runs
alongside TELNET; the command from the remote system will cause that system
to try to connect to the server on your Mac. If this succeeds, you will be
asked by your Mac to confirm or refuse the file transfer -- ordinarily you
will confirm it, since you sent the request in the first place, but it is
possible someone else could try to TFTP to or from your machine, in which
case you would probably want to deny the request and report the incident to
the local security staff. While a file transfer is going on, it is possible
but unwise to type characters to the remote system. You will be notified
when the transfer is complete. You can deny all TFTP requests from the
"Commands" menu; "TFTP Service" is checked if TFTP requests will be serviced
as described above. TFTP service inside TELNET can only be used on a
Macintosh which has 512K or more memory. It may be used inside Switcher;
TELNET is pre-configured for Switcher to allow enough memory for TFTP.
It is possible to listen for incoming TELNET requests by using the "Listen"
command. This is usually not useful; it was added for testing. It could be
used to chat back and forth between two users on different machines: one
user would put his Mac in listening state, the other would TELNET to his
machine. The two could then type back and forth to each other's terminal
windows.
It is possible to specify that what you type should not be sent until you
type a return, by selecting "Send Immediately" from the "Commands" menu.
This command will be checked if TELNET is in the normal state, in which
characters are sent as soon as you type them, and not checked if characters
will be held until return is typed. This can be useful because it reduces
the network overhead of the session. However, it is impossible to see what
you've typed until you hit the return, unless TELNET has been given the
"Local Echo" command. It is possible to cause data to be sent before typing
a return by using the "Expedite Data" command from the "Send" menu.
Most computers operate in remote echo mode: characters you type are not
directly printed on the screen; they are sent to the remote computer, which
then echoes the characters back for printing on your terminal. Computers
which operate in local echo mode can be communicated with by selecting
"Local Echo" from the "Send" menu after the connection is open.
The "Are You There?" command from the "Send" menu checks to see that the
remote computer is still connected. If no response is received within a few
seconds, TELNET will assume that the remote computer has crashed or
otherwise disconnected, and shut down your side of the connection.
"Abort Output" and "Break" from the "Send" menu cause special TELNET
information to be transmitted. The remote computer may or may not handle
these commands. In general, "Abort Output" will keep text that's being sent
from being sent, perhaps while a file is being printed on your screen, but
the program on the remote computer will not be interrupted. "Break" will do
whatever is appropriate on the system you are connected to, usually
interrupting the program on the remote computer.
As with TFTP, there is a "Show" menu from which various information can be
gotten. Any windows created when you select a command from the "Show" menu
can be gotten rid of by clicking anywhere in them.
Some menu commands in the "Show" menu are permanently disabled; they were
removed to allow TELNET to run on a 128K Macintosh or in a small Switcher
memory chunk. You should be able to get along just fine without them.
In the future, TELNET will save text that scrolls up past the top of the
screen, will allow TELNET traffic to be saved into a file, will allow you to
send a text file as if you were typing it in, and will allow characters to
be printed to the screen at a much higher rate. These MacTerminal-like
capabilities are not included in the November 1985 release of TELNET.
!E!O!F!
exit
-=-
Tim Maroney, Professional Heretic, CMU Center for Art and Technology
tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim
CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting appl/cust.link.text... cat >appl/cust.link.text <<'!E!O!F!' $EXEC L{ink}? +X { no more options} appl-cust obj-quickdraw obj-tooltraps obj-ostraps obj-prlink obj-packtraps obj-rtlib obj-pasinit obj-paslibasm obj-paslib obj-abpascalls net-cust_lib net-ip_lib net-name_user {no more object files} {list on console} appl-custL.OBJ $ENDEXEC !E!O!F! # # echo extracting appl/cust.text... cat >appl/cust.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} PROGRAM Customize; { Please note the copyright notice in the file "copyright/notice" } {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-cust_Lib } Cust_Lib, {$U net-ip_lib } IP_Lib, {$U net-name_user } Name_User; {$L+} VAR screen,dragRect: Rect; myEvent: EventRecord; whichWindow: WindowPtr; theItem: INTEGER; MyDialog: DialogPtr; dRecord: DialogRecord; TheItemType: Integer; TheItemBox: Rect; PROCEDURE SetupValue(Val:LongInt;Where:Integer); { Where is the item number into which to write the data } VAR LocalItemHandle: Handle; TempText: STR255; BEGIN GetDItem(MyDialog,Where,TheItemType,LocalItemHandle,TheItemBox); cvt_inaddr(Val,TempText); SetIText(LocalItemHandle,TempText); END; FUNCTION GetValue(Where:Integer): LongInt; { Read a value from a field } VAR LocalItemHandle: Handle; TempText: STR255; BEGIN GetDItem(MyDialog,Where,TheItemType,LocalItemHandle,TheItemBox); GetIText(LocalItemHandle,TempText); GetValue := convert_name(TempText); END; PROCEDURE DoCustom; CONST OKBut = 1; CancelBut = 2; LocalIPField = 3; GWIPField = 4; NSIPField = 5; UserField = 6; DefHField = 7; ABRadio = 8; LocIPHeadr = 9; GWIPHeader = 10; NSIPHeader = 11; UserHeader = 12; DefHHeader = 13; ErrField = 14; CustDialog = 1; TYPE TwoHandle = RECORD CASE Integer OF 0:( h: Handle); 1:( ch: ControlHandle); END; VAR i: Integer; ItemHit: Integer; LocalItemHandle: Handle; ErrHandle, ABButHandle: TwoHandle; FRN: Integer; { File reference number for the custimzation values } CurVal: CustRecord; RLength: LongInt; OSStatus: OSErr; ErrMsg: STR255; TLIP,TGWIP,{ TTSIP,} TNSIP: in_name; TUser,THost: STR255; myNet,myNode:INTEGER; BEGIN { Some values in case something goes wrong } if GetNodeAddress(myNode,myNet) <> noErr then myNode := 0; CurVal.LocalIPAddr:= $80020000 + myNode; CurVal.GateWIPAddr:= $80020040; CurVal.NameServer := $80020040; CurVal.TimeServer := $80020040; CurVal.UserName := 'AppleMAC'; CurVal.DefHost := 'Unknown'; CurVal.UseAB := TRUE; { Try to read in old values } OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN); IF OSStatus = noErr THEN BEGIN { File is there, read in the values } RLength := sizeof(CustRecord); { Get to the start of the file } OSStatus := SetFPos(FRN,fsFromStart,0); IF OSStatus = noErr THEN OSStatus := FSRead(FRN,RLength,@CurVal); ErrMsg := 'Current values from customization file are shown.'; END ELSE IF OSStatus = fnfErr THEN BEGIN { File wasn't found, so create it and fill with default values } { Not really a text file, but this allows other programs to try to diddle with it } OSStatus := Create(CFileName, { Vol } 0, { Creator } '????', { FileType} 'TEXT'); { After creating it, open it and make sure we can get the room } IF OSStatus = noErr THEN OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN); { and get the room } RLength := sizeof(CustRecord); IF OSStatus = noErr THEN OSStatus := Allocate(FRN,RLength); ErrMsg := 'Customization file not found - creating it with default values.'; END; IF OSStatus <> noErr THEN BEGIN { Have problem, may be cannot correct them } NumToString(OSStatus,ErrMsg); ErrMsg := concat('Could not open customization file - status ',ErrMsg); END; MyDialog := GetNewDialog(CustDialog,@dRecord,POINTER(-1)); GetDItem(MyDialog,ABRadio,TheItemType,ABButHandle.h,TheItemBox); GetDItem(MyDialog,ErrField,TheItemType,ErrHandle.h,TheItemBox); REPEAT { Now fill in the values read from the file } SetCtlValue(ABButHandle.ch,ORD(CurVal.UseAB)); SetupValue(CurVal.LocalIPAddr,LocalIPField); SetupValue(CurVal.GateWIPAddr,GWIPField); { SetupValue(CurVal.TimeServer,TSIPField); } SetupValue(CurVal.NameServer,NSIPField); GetDItem(MyDialog,UserField,TheItemType,LocalItemHandle,TheItemBox); SetIText(LocalItemHandle,CurVal.UserName); GetDItem(MyDialog,DefHField,TheItemType,LocalItemHandle,TheItemBox); SetIText(LocalItemHandle,CurVal.DefHost); SetIText(ErrHandle.h,ErrMsg); ModalDialog(NIL,ItemHit); { Read in the fields } ErrMsg := ''; TLIP := GetValue(LocalIPField); IF TLIP = 0 THEN ErrMsg := 'Local IP Address is ill formed.' ELSE CurVal.LocalIPAddr := TLIP; IF CurVal.UseAB AND (ErrMsg = '') THEN BEGIN { Now use AB value } CurVal.LocalIPAddr := BitAnd(CurVal.LocalIPAddr,$FFFFFF00) + myNode; SetupValue(CurVal.LocalIPAddr,LocalIPField); END; TGWIP := GetValue(GWIPField); IF TGWIP = 0 THEN ErrMsg := 'Gateway IP Address is ill formed.' ELSE CurVal.GateWIPAddr := TGWIP; { TTSIP := GetValue(TSIPField); IF TTSIP = 0 THEN ErrMsg := 'Timer Server IP Address is ill formed.' ELSE CurVal.TimeServer := TTSIP; } TNSIP := GetValue(NSIPField); IF TNSIP = 0 THEN ErrMsg := 'Name Server IP Address is ill formed.' ELSE CurVal.NameServer := TNSIP; GetDItem(MyDialog,UserField,TheItemType,LocalItemHandle,TheItemBox); GetIText(LocalItemHandle,TUser); IF length(TUser)>8 THEN ErrMsg := 'User name greater than 8 character' ELSE IF length(TUser) = 0 THEN ErrMsg := 'Null user name is not permitted.' ELSE CurVal.UserName := TUser; GetDItem(MyDialog,DefHField,TheItemType,LocalItemHandle,TheItemBox); GetIText(LocalItemHandle,THost); IF length(THost) = 0 THEN ErrMsg := 'Null host name is not permitted.' ELSE CurVal.DefHost := THost; CASE ItemHit OF OKBut: BEGIN IF ErrMsg = '' THEN BEGIN { All values OK } { Writeout the file } ErrMsg := 'Please confirm by clicking ''OK'''; SetIText(ErrHandle.h,ErrMsg); ModalDialog(NIL,ItemHit); IF ItemHit <> OKBut THEN BEGIN ItemHit := ErrField; ErrMsg := 'Save has been canceled.'; SetIText(ErrHandle.h,ErrMsg); END ELSE BEGIN RLength := sizeof(CustRecord); OSStatus := SetFPos(FRN,fsFromStart,0); IF OSStatus = noErr THEN OSStatus := FSWrite(FRN,RLength,@CurVal); IF OSStatus <> noErr THEN BEGIN ErrMsg := 'Trouble writing the customization file.'; SetIText(ErrHandle.h,ErrMsg); ModalDialog(NIL,ItemHit); END; IF OSStatus = noErr THEN OSStatus := FSClose(FRN); IF OSStatus <> noErr THEN BEGIN ErrMsg := 'Trouble closing the customization file.'; SetIText(ErrHandle.h,ErrMsg); ModalDialog(NIL,ItemHit); END; IF OSStatus <> noErr THEN ItemHit := ErrField; END END ELSE ItemHit := ErrField; END; CancelBut: BEGIN { Person decided to punt } ErrMsg := 'Are you sure you want to cancel? (Select OK)'; SetIText(ErrHandle.h,ErrMsg); ModalDialog(NIL,ItemHit); IF ItemHit <> OKBut THEN BEGIN ItemHit := ErrField; ErrMsg := 'Cancel ''canceled'''; END ELSE OSStatus := FSClose(FRN); END; ABRadio: BEGIN { Person wants some calculation based on the AB address to be used for Local IP Address } CurVal.UseAB := NOT CurVal.UseAB; SetCtlvalue(ABButHandle.ch,ORD(CurVal.UseAB)); IF CurVal.UseAB THEN BEGIN { Now use AB value } ErrMsg := 'Local IP Address now a function of Applebus node number.'; CurVal.LocalIPAddr := BitAnd(CurVal.LocalIPAddr,$FFFFFF00) + myNode; END ELSE BEGIN ErrMsg := 'Local IP Address is no longer a function of the Applebus node number.'; END; END; END; UNTIL ItemHit in [OKBut, CancelBut]; CloseDialog(MyDialog); END; BEGIN { main program } InitGraf(@thePort); InitFonts; FlushEvents(everyEvent,0); InitWindows; TEInit; InitDialogs(NIL); InitCursor; screen := screenBits.bounds; SetRect(dragRect,4,24,screen.right-4,screen.bottom-4); DoCustom; END. !E!O!F! # # echo extracting appl/custo.text... cat >appl/custo.text <<'!E!O!F!' appl-custo.rsrc Type CODE appl-custL,0 !E!O!F! # # echo extracting appl/custr.text... cat >appl/custr.text <<'!E!O!F!' * appl-custr -- Resource input for customizer * *{ Please note the copyright notice in the file "copyright/notice" } appl-custr.Rsrc Type MENU ,256 File Customize User Quit Type DLOG ,1 30 20 300 490 Visible 1 NoGoAway 0 3 Type DITL ,3 14 BtnItem Enabled 20 110 35 190 OK BtnItem Enabled 20 260 35 340 Cancel EditText Disabled 50 195 65 350 DefaultLocalIPAddress EditText Disabled 75 195 90 350 DefaultGWIPAddress EditText Disabled 100 195 115 350 DefaultNSIPAddress EditText Disabled 125 195 140 350 DefaultUserName EditText Disabled 150 195 165 350 DefaultHostName ChkItem Enabled 175 10 190 350 Base Local IP Address on Applebus Node Number StatText Disabled 50 10 65 190 Local IP Address: StatText Disabled 75 10 90 190 Gateway IP Address: StatText Disabled 100 10 115 190 Name Server IP Address: StatText Disabled 125 10 140 190 User Name: StatText Disabled 150 10 165 190 Default Foreign Host: StatText Disabled 200 10 260 400 Customization file not found -- creating it. Type BNDL ,128 CUST 0 2 ICN# 1 0 128 FREF 1 0 128 Type FREF ,128 APPL 0 Type CUST = STR ,0 CUST Version 2, 10 September 1985 !E!O!F! # # echo extracting appl/telnet.link.te... cat >appl/telnet.link.te <<'!E!O!F!' $EXEC L{ink}? +X { no more options} appl-telnet obj-quickdraw obj-tooltraps obj-ostraps obj-prlink obj-packtraps obj-rtlib obj-pasinit obj-paslibasm obj-paslib obj-abpascalls net-err_lib net-task_lib net-task_asm net-timer_lib net-cust_lib net-name_host net-arp_lib net-ip_listen net-icmp_lib net-ip_lib net-udp_lib net-name_user net-tftp_defs net-tftp_file net-tftp_lib net-tcp_lib net-term_lib net-tn_lib net-calls net-call_asm {no more object files} {list on console} appl-telnetL.OBJ $ENDEXEC !E!O!F! # # echo extracting appl/telnet.text... cat >appl/telnet.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL LISTEN} {$SETC LISTEN := true} {$DECL TCPSTATS} {$SETC TCPSTATS := false} {$DECL UDPSTATS} {$SETC UDPSTATS := false} {$DECL IPSTATS} {$SETC IPSTATS := false} PROGRAM TELNET; { Please note the copyright notice in the file "copyright/notice" } {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-err_lib } Err_Lib, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-ip_lib } IP_Lib, {$U net-arp_lib } ARP_Lib, {$U net-icmp_lib } ICMP_Lib, {$U net-UDP_Lib } UDP_Lib, {$U net-TCP_Lib } TCP_Lib, {$U net-TFTP_Lib } TFTP_Lib, {$U net-tn_lib } TN_Lib, {$U net-term_lib } Term_Lib; {$L+} CONST lastMenu = 4; appleMenu = 1; { menu ID for desk accessory menu } CmdMenu = 257; { menu ID for Command menu } SndMenu = 256; { menu ID for Send menu } ShowMenu = 258; { menu ID for Show menu } VAR myMenus: ARRAY [1..lastMenu] OF MenuHandle; myEvent: EventRecord; code,refNum: INTEGER; whichWindow: WindowPtr; theMenu,theItem: INTEGER; dragRect,screen:Rect; mainstack: integer; TempText:STR255; {$S InitSeg} PROCEDURE SetUpMenus; { Once-only initialization for menus } VAR i: INTEGER; appleTitle: STRING[1]; BEGIN InitMenus; { initialize Menu Manager } appleTitle := ' '; appleTitle[1] := CHR(20); myMenus[1] := NewMenu(appleMenu,appleTitle); AddResMenu(myMenus[1],'DRVR'); { desk accessories } myMenus[2] := GetMenu(CmdMenu); myMenus[3] := GetMenu(SndMenu); myMenus[4] := GetMenu(ShowMenu); FOR i := 1 TO lastMenu DO InsertMenu(myMenus[i],0); DrawMenuBar; if NOT tn_tftp then DisableItem(myMenus[1],0); {disable desk accessories} {$IFC LISTEN} EnableItem(myMenus[2],5); {$ENDC} {$IFC TCPSTATS} EnableItem(myMenus[4],1); {$ENDC} {$IFC UDPSTATS} EnableItem(myMenus[4],4); {$ENDC} {$IFC IPSTATS} EnableItem(myMenus[4],5); {$ENDC} END; { of SetUpMenus } {$S } PROCEDURE DoCommand(mResult: LongInt); VAR DummyInt,i: Integer; TempLong:LongInt; myNode,myNet:INTEGER; BEGIN theMenu := HiWord(mResult); theItem := LoWord(mResult); CASE theMenu OF appleMenu: BEGIN GetItem(myMenus[1],theItem,TempText); refNum := OpenDeskAcc(TempText); END; CmdMenu: TelnetMenuCmd(theItem,myMenus[2]); SndMenu: SendMenuCmd(theItem,myMenus[3]); ShowMenu: begin case theItem of {$IFC TCPSTATS} 1: showstats; {$ENDC} 2: { Address of my machine } begin cvt_inaddr(in_mymach(0),Msg); Message(StrCvt('My Internet address:'),@Msg); end; 3: { Show Local Appletalk Address } if GetNodeAddress(myNode,myNet) = noErr then begin Msg := 'Network: '; NumToStr(myNet,TempText); insert(TempText,Msg,length(Msg)+1); insert(', Node: ',Msg,length(Msg)+1); NumToStr(myNode,TempText); insert(TempText,Msg,length(Msg)+1); Message(StrCvt('My Appletalk address:'),@Msg); end; {$IFC UDPSTATS} 4: udp_table; {$ENDC} {$IFC IPSTATS} 5: in_stats; {$ENDC} end; end; END; { of menu case } HiliteMenu(0); END; { of DoCommand } {$S InitSeg } FUNCTION StackBase:LongInt; CONST CurStackBase = $908; { system global } TYPE LongPtr = ^LongInt; BEGIN StackBase := LongPtr(CurStackBase)^; END; PROCEDURE InitNetwork; BEGIN MainStack := 4096; IPStack := 4096; SetApplLimit(POINTER(StackBase - (MainStack + IPStack + ARPTKSZ + TFTKSZ + TCPTKSZ + TNTKSZ + 2048))); MaxApplZone; MoreMasters; MoreMasters; MoreMasters; MoreMasters; MoreMasters; if (ORD4(ApplicZone^.bkLim) - ORD4(ApplicZone)) < $19000 { 100K } then tn_tftp := false else tn_tftp := true; InitGraf(@thePort); InitFonts; FlushEvents(everyEvent,0); InitWindows; SetUpMenus; InitDialogs(NIL); InitCursor; TEInit; screen := screenBits.bounds; SetRect(dragRect,4,24,screen.right-4,screen.bottom-4); ErrInit; if MPPOpen <> noErr then Fatal(StrCvt('Can''t open Mac Protocol Package'),false); Main_Task := tk_init(mainstack); tm_init; in_init; IcmpInit; GgpInit; UdpInit; if tn_tftp then tftpinit; tcp_init(512); tel_init; tn_done := FALSE; END; {$S } PROCEDURE MainEventLoop; CONST NetworkEvt = 10; VAR dp:DialogPtr; itemHit:INTEGER; i:INTEGER; BEGIN REPEAT SystemTask; if GetNextEvent(everyEvent,myEvent) then begin if IsDialogEvent(myEvent) then begin if NOT DialogSelect(myEvent,dp,itemHit) then begin if (myEvent.what = mouseDown) then begin IF NOT ClickResolve(myEvent) THEN ; end else if (myEvent.what = activateEvt) then begin if NOT ActResolve(myEvent) then ; end; cycle; end; end; CASE myEvent.what OF mouseDown: BEGIN code := FindWindow(myEvent.where,whichWindow); CASE code OF inMenuBar: begin TNFixMenu(myMenus[2]); FixSendMenu(myMenus[3]); DoCommand(MenuSelect(myEvent.where)); end; inSysWindow: SystemClick(myEvent,whichWindow); inDrag: DragWindow(whichWindow,myEvent.where,dragRect); inGrow,inContent: BEGIN IF whichWindow = myWindow then begin IoClick(code,myEvent.where,myEvent.modifiers) end ELSE IF whichWindow <> FrontWindow THEN SelectWindow(whichWindow) END; inGoAway: ; END; { of code case } END; { of mouseDown } keyDown,autoKey: IF (myWindow=FrontWindow) THEN begin { when connection is open, keyboard input gets treated as terminal input; at other times it is ignored } em_input(@myEvent,TempText); for i := 1 to length(TempText) do gt_usr(TempText[i]); end; activateEvt: if POINTER(myEvent.message) = myWindow then IoActivate(myEvent.modifiers); updateEvt: if POINTER(myEvent.message) = myWindow then IoUpdate; END; { of event case } END { of if GetNextEvent } ELSE IF myEvent.what = nullEvent THEN begin tk_yield; { See if anyone else wants to execute } IOIdle; end; UNTIL tn_done; END; { end of My Main Event Loop } BEGIN { main program } InitNetwork; UnloadSeg(@InitNetwork); MainEventLoop; in_close; tm_allFree; END. !E!O!F! # # echo extracting appl/telneto.text... cat >appl/telneto.text <<'!E!O!F!' appl-telneto.rsrc Type CODE appl-telnetL,0 !E!O!F! # # echo extracting appl/telnetr.text... cat >appl/telnetr.text <<'!E!O!F!' * Resource compiler file for TELNET * *{ Please note the copyright notice in the file "copyright/notice" } * appl-telnetr.Rsrc Type MENU ,256 Send Local Echo "Are You There?" "Abort Output" "Break" Expedite Data ,257 Commands Open... Close Send Immediately TFTP Service (Listen Quit ,258 Show (TCP Statistics My Internet Address My Appletalk Address (Active UDP Connections (IP Statistics Type DLOG ,11 90 50 200 440 Visible 1 NoGoAway 0 13 Confirm File Transfer Type DITL ,13 4 BtnItem Enabled 80 90 105 150 YES BtnItem Enabled 80 240 105 300 NO StatText Disabled 5 20 25 370 You have received a TFTP (file transfer) request. StatText Disabled 25 20 75 370 The host ^0 wants to ^1 the file "^2". Is that all right? Type DLOG ,49 100 125 245 375 Invisible 3 NoGoAway 0 51 Error Box Type DITL ,51 2 StatText Disabled 10 10 25 240 A Network Error Happened! StatText Disabled 80 10 135 240 Set by SetIText Type DLOG ,31 100 125 245 375 Visible 1 NoGoAway 0 62 Name Foreign Host Type DITL ,62 4 BtnItem Enabled 10 50 40 110 OK BtnItem Enabled 10 140 40 200 CANCEL EditText Disabled 115 10 130 240 DefaultForeignHost StatText Disabled 50 10 100 240 Please type in the name or number of the foreign host you want to reach. Type ALRT ,333 80 100 255 420 444 FFFF Type DITL ,444 3 BtnItem Enabled 140 110 165 210 QUIT StatText Disabled 10 90 55 300 A fatal error happened! The program will die. StatText Disabled 80 20 135 300 ^0 Type ALRT ,666 80 100 255 420 777 FFFF Type DITL ,777 5 BtnItem Enabled 140 110 165 210 HALT StatText Disabled 10 90 55 300 Fatal Error! A task overflowed its stack! StatText Disabled 80 20 95 300 Stack Pointer about ^0 StatText Disabled 100 20 115 300 Task control block at ^1 StatText Disabled 120 20 135 300 Task name is ^2 Type DLOG ,77 50 100 295 375 Visible 4 NoGoAway 0 78 IP Statistics Type DITL ,78 22 StatText Disabled 15 180 30 265 precv StatText Disabled 35 180 50 265 psnt StatText Disabled 55 180 70 265 pdrop StatText Disabled 75 180 90 265 bdchk StatText Disabled 95 180 110 265 unprot StatText Disabled 115 180 130 265 bdvers StatText Disabled 135 180 150 265 bdlen StatText Disabled 155 180 170 265 ttlexp StatText Disabled 175 180 190 265 frags StatText Disabled 195 180 210 265 free StatText Disabled 215 180 230 265 fail StatText Disabled 15 10 30 170 Packets Received: StatText Disabled 35 10 50 170 Packets Sent: StatText Disabled 55 10 70 170 Packets Dropped: StatText Disabled 75 10 90 170 Bad Checksums: StatText Disabled 95 10 110 170 Unhandled Protocols: StatText Disabled 115 10 130 170 Bad Versions: StatText Disabled 135 10 150 170 Bad Lengths: StatText Disabled 155 10 170 170 TTL Expired: StatText Disabled 175 10 190 170 Fragments: StatText Disabled 195 10 210 170 Free Packets: StatText Disabled 215 10 230 170 Listen Failures: Type DLOG ,93 100 75 250 425 Visible 4 NoGoAway 0 94 UDP Statistics Type DITL ,94 15 StatText Disabled 10 10 25 70 Local StatText Disabled 10 78 25 138 Foreign StatText Disabled 10 146 25 206 Host StatText Disabled 10 214 25 276 Handler StatText Disabled 10 284 25 344 Cn StatText Disabled 30 10 45 70 StatText Disabled 30 78 45 138 StatText Disabled 30 146 45 206 StatText Disabled 30 214 45 276 StatText Disabled 30 284 45 344 StatText Disabled 50 10 65 70 StatText Disabled 50 78 65 138 StatText Disabled 50 146 65 206 StatText Disabled 50 214 65 276 StatText Disabled 50 284 65 344 Type BNDL ,128 TLNT 0 2 ICN# 1 0 128 FREF 1 0 128 Type FREF ,128 APPL 0 *Switcher application size resource Type SIZE = HEXA ,-1 00000002000000018000 Type TLNT = STR ,0 TELNET Version 1.2, 10 September 1985 !E!O!F! # # echo extracting appl/tftp.link.text... cat >appl/tftp.link.text <<'!E!O!F!' $EXEC L{ink}? +X { no more options} appl-tftp obj-quickdraw obj-tooltraps obj-ostraps obj-prlink obj-packtraps obj-rtlib obj-pasinit obj-paslibasm obj-paslib obj-abpascalls sfmget-sfmintf sfmget-sfmintf_asm net-err_lib net-task_lib net-task_asm net-timer_lib net-cust_lib net-name_host net-arp_lib net-ip_listen net-icmp_lib net-ip_lib net-udp_lib net-name_user net-tftp_defs net-tftp_file net-tftp_lib net-calls net-call_asm {no more object files} {list on console} appl-tftpL.OBJ $ENDEXEC !E!O!F! # # echo extracting appl/tftp.text... cat >appl/tftp.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} PROGRAM TFTP; { Please note the copyright notice in the file "copyright/notice" } { TFTP - Trivial File transfer over Applebus } { by Mark Sherman (Dartmouth) and Tim Maroney (C-MU) } {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-err_lib } Err_Lib, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-ip_lib } IP_Lib, {$U net-arp_lib } ARP_Lib, {$U net-icmp_lib } ICMP_Lib, {$U net-UDP_Lib } UDP_Lib, {$U net-name_user } Name_User, {$U net-tftp_defs } TFTP_Defs, {$U net-tftp_lib } TFTP_Lib, {$U net-name_host } NameHost, {$U sfmget-SFMIntf } SFMIntf; {$L+} CONST lastMenu = 4; {number of menus } appleMenu = 1; { menu ID for desk accessory menu } CmdMenu = 256; { menu ID for Command menu } ShowMenu = 258; { menu ID for Show menu } SettingMenu = 257; { menu ID for Setting menu } RFileDialog = 11; MCmd = 2; MSetting = 3; MShow = 4; ServerItem = 3; MYTKSZ = 6144; VAR myMenus: ARRAY [1..lastMenu] OF MenuHandle; SGOK,SPOK:Boolean; doneFlag: BOOLEAN; myEvent: EventRecord; code,refNum: INTEGER; whichWindow: WindowPtr; theMenu,theItem: INTEGER; dragRect,screen:Rect; TempText:STR255; ServerEnabled: Boolean; INFOMSGChked, NETERRChked, PROTERRChked : BOOLEAN; LocalFileName, DestFileName, DestHostName, Directory: STR255; LocalVolume:Integer; tftpnum: Integer; fhost: in_name; ModeToUse: Integer; IPuninited: Boolean; UserTask, Server_Task: Ref_Task; UserDirection: Integer; UserRunning: Boolean; mainstack: integer; {$S TFTPSeg } PROCEDURE tfshow(host:in_name;fileName:StringPtr;mode:Integer); BEGIN Msg := 'tftp #'; NumToStr(tftpnum,TempText); insert(TempText,Msg,length(Msg)+1); tftpnum := tftpnum + 1; insert(', with ',Msg,length(Msg)+1); cvt_inaddr(host,TempText); insert(TempText,Msg,length(Msg)+1); insert(': ',Msg,length(Msg)+1); IF mode = GET THEN insert('Receiving ',Msg,length(Msg)+1) ELSE IF mode = PUT THEN insert('Sending ',Msg,length(Msg)+1) ELSE insert(' BAD direction ',Msg,length(Msg)+1); insert(fileName^,Msg,length(Msg)+1); Message(StrCvt('Starting file transfer.'),@Msg); END; FUNCTION tfprint(host:in_name;fileName:StringPtr;mode:Integer): Integer; BEGIN if ((mode = PUT) and NOT SGOK) or ((mode = GET) and NOT SPOK) THEN BEGIN Msg := 'From '; cvt_inaddr(host,TempText); insert(TempText,Msg,length(Msg)+1); insert(': Tried to ',Msg,length(Msg)+1); IF mode = GET THEN insert('put ',Msg,length(Msg)+1) ELSE IF mode = PUT THEN insert('get ',Msg,length(Msg)+1); insert(fileName^,Msg,length(Msg)+1); Message(StrCvt('Rejected transfer request.'),@Msg); tfprint := 0; END else BEGIN tfshow(host,fileName,mode); tfprint := 1; END; END; PROCEDURE tfdone(success:integer); BEGIN IF success <> 0 THEN begin Msg := 'Successful transfer'; Message(StrCvt('File transfer done.'),@Msg); end; { Print nothing in case of error } END; PROCEDURE DoServer(Dummy:PTR); BEGIN tfsinit(@tfprint,@tfdone); tfs_on; WHILE (ServerEnabled) DO tk_yield; { Keep acting as server until disabled } tk_exit; END; PROCEDURE FixSetMenu; VAR i:Integer; BEGIN { Fix up the check marks on the menu } FOR i := 1 TO 4 DO CheckItem(MyMenus[MSetting],i,FALSE); case ModeToUse of ASCII: CheckItem(MyMenus[MSetting],1,TRUE); IMAGE: CheckItem(MyMenus[MSetting],2,TRUE); OCTET: CheckItem(MyMenus[MSetting],3,TRUE); MACINTOSH:CheckItem(MyMenus[MSetting],4,TRUE) end; END; FUNCTION SInitDialog: Boolean; CONST OKBut = 1; CancelBut = 2; GetBox = 3; PutBox = 4; SInitDlog = 50; VAR itemHit:INTEGER; MyDialog:DialogPtr; itemType:INTEGER; itemHndl:Handle; itemBox:Rect; BEGIN SGOK := true; SPOK := false; MyDialog := GetNewDialog(SInitDlog,NIL,POINTER(-1)); GetDItem(MyDialog,GetBox,itemType,itemHndl,itemBox); SetCtlValue(POINTER(ORD4(itemHndl)),1); GetDItem(MyDialog,PutBox,itemType,itemHndl,itemBox); SetCtlValue(POINTER(ORD4(itemHndl)),0); ModalDialog(NIL,itemHit); while not (itemHit in [OKBut, CancelBut]) do begin GetDItem(MyDialog,itemHit,itemType,itemHndl,itemBox); case itemHit of GetBox: BEGIN SGOK := NOT SGOK; SetCtlValue(POINTER(ORD4(itemHndl)),ORD(SGOK)); END; PutBox: BEGIN SPOK := NOT SPOK; SetCtlValue(POINTER(ORD4(itemHndl)),ORD(SPOK)); END; end; { case } ModalDialog(NIL, ItemHit); end; DisposDialog(MyDialog); if itemHit = CancelBut then SInitDialog := false else SInitDialog := true; END; FUNCTION NameRemFile(root:STR255; VAR oname:STR255; VAR host:STR255):Boolean; CONST OKBut = 1; CancelBut = 2; FileItem = 3; HostItem = 4; ASCIIitem = 5; IMAGEitem = 6; OCTETitem = 7; MACitem = 8; VAR itemHit:INTEGER; MyDialog:DialogPtr; itemType:INTEGER; itemHndl:Handle; itemBox:Rect; PROCEDURE ResetButton(val:INTEGER); VAR tmp:INTEGER; begin case ModeToUse of ASCII: tmp := ASCIIitem; IMAGE: tmp := IMAGEitem; OCTET: tmp := OCTETitem; MACINTOSH: tmp := MACitem; end; GetDItem(MyDialog,tmp,itemType,itemHndl,itemBox); SetCtlValue(POINTER(ORD4(itemHndl)),val); end; BEGIN if root = '' then begin ParamText('Name of The Directory','','',''); end else begin oname := concat(Directory,root); ParamText('Name of The Remote File','','',''); end; MyDialog := GetNewDialog(RFileDialog,NIL,POINTER(-1)); GetDItem(MyDialog,HostItem,itemType,itemHndl,itemBox); SetIText(itemHndl,DestHostName); GetDItem(MyDialog,FileItem,itemType,itemHndl,itemBox); SetIText(itemHndl,oname); SelIText(MyDialog,FileItem,0,16000); ResetButton(1); ModalDialog(NIL,itemHit); while not (itemHit in [1,2]) do begin ResetButton(0); case itemHit of ASCIIitem: ModeToUse := ASCII; IMAGEitem: ModeToUse := IMAGE; OCTETitem: ModeToUse := OCTET; MACitem: ModeToUse := MACINTOSH; end; GetDItem(MyDialog,itemHit,itemType,itemHndl,itemBox); SetCtlValue(POINTER(ORD4(itemHndl)),1); ModalDialog(NIL,itemHit); end; if itemHit = 2 then begin DisposDialog(MyDialog); FixSetMenu; NameRemFile := false; exit(NameRemFile); end; GetDItem(MyDialog,HostItem,itemType,itemHndl,itemBox); GetIText(itemHndl,host); GetDItem(MyDialog,FileItem,itemType,itemHndl,itemBox); GetIText(itemHndl,oname); DisposDialog(MyDialog); FixSetMenu; NameRemFile := true; END; VAR UserSuccess:Integer; PROCEDURE UserDone(success:Integer); BEGIN tk_wake(UserTask); UserSuccess := success; END; PROCEDURE DoTransfer; VAR TotalDeltat: LongInt; ADummy: INTEGER; BEGIN fhost := resolve_name(DestHostName); IF fhost = 0 THEN BEGIN Error2(StrCvt('TFTP: Couldn''t resolve host name '),@DestHostName); exit(DoTransfer); END; IF fhost = NAMETMO THEN BEGIN Error(StrCvt('TFTP: Name servers not responding')); exit(DoTransfer); END; tfshow(fhost,@LocalFileName,Userdirection); totalDeltat := tickCount; if tftpuse(fhost,@LocalFileName,LocalVolume,@DestFileName,Userdirection, ModeToUse,@UserDone) = 0 then begin UserSuccess := 0; exit(DoTransfer); end; tk_block; Totaldeltat := (tickCount - totalDeltat) DIV TPS; if userdirection = GET then ADummy := ORD(flushvol(NIL,LocalVolume)); if UserSuccess <> 0 then begin Msg := 'File transferred in '; NumToStr(Totaldeltat,TempText); insert(TempText,Msg,length(Msg)+1); insert(' seconds.',Msg,length(Msg)+1); Message(StrCvt('File transfer successful.'),@Msg); end; END; {dummy procedures to force loading of the segments they contain} {$S UDPNameS} PROCEDURE NameLoad; BEGIN END; {$S UDPSeg } PROCEDURE UDPLoad; BEGIN NameLoad END; {$S TFTPSeg } PROCEDURE DoUser(Dummy:PTR); VAR Pnt:Point; Reply:SFReply; SFDummy:SFTypeList; Files:StrLHandle; i:Integer; BEGIN Pnt.h := 100; Pnt.v := 90; WHILE TRUE DO BEGIN UserRunning := FALSE; tk_block; UserRunning := TRUE; DisableItem(MyMenus[MCmd],1); { turn off get } DisableItem(MyMenus[MCmd],2); { turn off put } EnableItem(MyMenus[MCmd],4); { turn on abort } { these next three statements are intended to prevent the need to swap disks if the disk with TFTP is ejected } CouldDialog(RFileDialog); CouldDialog(errID); UDPLoad; if UserDirection = GET then begin SFPutFile(Pnt,'Local File Name:',LocalFileName,NIL,Reply); if Reply.good then begin LocalFileName := Reply.fName; DestFileName := Reply.fName; LocalVolume := Reply.vRefNum; if NameRemFile(LocalFileName,DestFileName, DestHostName) then DoTransfer; end; end else begin { UserDirection = PUT } Files := SFMGetFile(Pnt,NIL,-1,SFDummy,NIL,BitOR(SFMmulti,SFMenable)); if Files <> NIL then begin if Files^^.howmany = 1 then begin { just one file selected } LocalFileName := Files^^.table[0].str^^; DestFileName := Files^^.table[0].str^^; LocalVolume := Files^^.volume; if NameRemFile(LocalFileName,DestFileName,DestHostName) then DoTransfer; end else begin { more than one file selected } if NameRemFile('',Directory,DestHostName) then begin for i := 0 to Files^^.howmany-1 do begin LocalFileName := Files^^.table[i].str^^; DestFileName := concat(Directory, Files^^.table[i].str^^); LocalVolume := Files^^.volume; DoTransfer; if UserSuccess = 0 then leave; end; { for } end; {if NameRemFile} end; { multiple files } DelFList(Files); { clean up from SFMGetFile } end; { if Files <> NIL } SFMEnd; end; { PUT } FreeDialog(RFileDialog); FreeDialog(errID); EnableItem(MyMenus[MCmd],1); { turn on get } EnableItem(MyMenus[MCmd],2); { turn on put } DisableItem(MyMenus[MCmd],4);{ turn off abort } END; { endless while } END; {$S InitSeg} PROCEDURE SetUpMenus; { Once-only initialization for menus } VAR i: INTEGER; appleTitle: STRING[1]; BEGIN InitMenus; { initialize Menu Manager } appleTitle := ' '; appleTitle[1] := CHR(20); myMenus[1] := NewMenu(appleMenu,appleTitle); AddResMenu(myMenus[1],'DRVR'); { desk accessories } myMenus[MCmd] := GetMenu(CmdMenu); myMenus[MSetting] := GetMenu(SettingMenu); myMenus[MShow] := GetMenu(ShowMenu); FOR i := 1 TO lastMenu DO InsertMenu(myMenus[i],0); DrawMenuBar; DisableItem(MyMenus[MCmd],4); { turn off abort } END; { of SetUpMenus } {$S } PROCEDURE DoCommand(mResult: LongInt); CONST FDirDialog = 60; DirItem = 3; VAR i: Integer; ItemHndl:Handle; myNode,myNet:INTEGER; itemHit:INTEGER; MyDialog:DialogPtr; itemType:INTEGER; itemBox:Rect; BEGIN theMenu := HiWord(mResult); theItem := LoWord(mResult); CASE theMenu OF appleMenu: BEGIN GetItem(myMenus[1],theItem,TempText); refNum := OpenDeskAcc(TempText); END; CmdMenu: BEGIN CASE theItem OF 1,2: BEGIN DisableItem(MyMenus[MCmd],3); { turn off server } IF UserTask = NIL THEN BEGIN UserTask := tk_fork(Main_Task,@DoUser, MYTKSZ, 'DoUser',NIL); tk_yield; { Give initial wakeup } END; IF theItem = 1 THEN UserDirection := GET ELSE UserDirection := PUT; tk_wake(UserTask); END; 3: BEGIN if SInitDialog then begin ServerEnabled := TRUE; Server_Task := tk_fork(Main_Task,@DoServer,MYTKSZ, 'Server',NIL); { Disable all commands but Quit } CheckItem(MyMenus[MCmd],ServerItem,TRUE); DisableItem(MyMenus[MSetting],0); DisableItem(MyMenus[MCmd],1); DisableItem(MyMenus[MCmd],2); DisableItem(MyMenus[MCmd],3); end; END; 4: tfabort; 5: doneFlag := TRUE; END; { of item case } END; { of CmdMenu } SettingMenu: BEGIN CASE theItem OF 1: ModeToUse := ASCII; 2: ModeToUse := IMAGE; 3: ModeToUse := OCTET; 4: ModeToUse := MACINTOSH; 5: { Remote Host } if NameRemoteHost(TempText) then DestHostName := TempText; 6: BEGIN { Remote Directory } MyDialog := GetNewDialog(FDirDialog,NIL,POINTER(-1)); GetDItem(MyDialog,DirItem,itemType,itemHndl,itemBox); SetIText(itemHndl,Directory); SelIText(MyDialog,DirItem,0,16000); ModalDialog(NIL,itemHit); if itemHit = 1 then GetIText(itemHndl,Directory); DisposDialog(MyDialog); END; END; { End of item case } FixSetMenu; END; { of SettingMenu } ShowMenu: begin case theItem of 1: BEGIN { Show UDP statistics } udp_table; END; 2: { Address of my machine } begin cvt_inaddr(in_mymach(0),Msg); Message(StrCvt('My Internet address:'),@Msg); end; 3: { Show Local Appletalk Address } if GetNodeAddress(myNode,myNet) = noErr then begin Msg := 'Network: '; NumToStr(myNet,TempText); insert(TempText,Msg,length(Msg)+1); insert(', Node: ',Msg,length(Msg)+1); NumToStr(myNode,TempText); insert(TempText,Msg,length(Msg)+1); Message(StrCvt('My Appletalk address:'),@Msg); END; 4: begin in_stats; end; END; { case theItem of } END; { ShowMenu } END; { case theMenu of } HiliteMenu(0); END; { of DoCommand } {$S InitSeg } FUNCTION StackBase:LongInt; CONST CurStackBase = $908; { system global } TYPE LongPtr = ^LongInt; BEGIN StackBase := LongPtr(CurStackBase)^; END; PROCEDURE InitNetwork; BEGIN MainStack := 4096; IPStack := 4096; SetApplLimit(POINTER(StackBase - (MainStack + IPStack + ARPTKSZ + TFTKSZ + MYTKSZ + 2048))); MaxApplZone; MoreMasters; MoreMasters; MoreMasters; MoreMasters; MoreMasters; InitGraf(@thePort); InitFonts; FlushEvents(everyEvent,0); InitWindows; SetUpMenus; InitDialogs(NIL); InitCursor; TEInit; screen := screenBits.bounds; SetRect(dragRect,4,24,screen.right-4,screen.bottom-4); ErrInit; if MPPOpen <> noErr then Fatal(StrCvt('Can''t open Mac Protocol Package'),false); Main_Task := tk_init(mainstack); tm_init; in_init; IcmpInit; GgpInit; UdpInit; tftpinit; { end of Init_IP } INFOMSGChked := True; NETERRChked := True; PROTERRChked := True; ServerEnabled := False; LocalFileName := ''; DestFileName := ''; Directory := ''; DestHostName := DefaultHost; ModeToUse := ASCII; CheckItem(MyMenus[MSetting],1,true); tftpnum := 0; IPuninited := TRUE; server_task := NIL; UserTask := NIL; UserRunning := FALSE; doneFlag := FALSE; END; {$S } PROCEDURE MainEventLoop; CONST NetworkEvt = 10; VAR dp:DialogPtr; itemHit:INTEGER; BEGIN REPEAT SystemTask; if GetNextEvent(everyEvent,myEvent) then begin if IsDialogEvent(myEvent) then begin if NOT DialogSelect(myEvent,dp,itemHit) then begin if (myEvent.what = mouseDown) then begin IF NOT ClickResolve(myEvent) THEN ; end else if (myEvent.what = activateEvt) then begin if NOT ActResolve(myEvent) then ; end; cycle; end; end; CASE myEvent.what OF mouseDown: BEGIN code := FindWindow(myEvent.where,whichWindow); CASE code OF inMenuBar: DoCommand(MenuSelect(myEvent.where)); inSysWindow: SystemClick(myEvent,whichWindow); inDrag: DragWindow(whichWindow,myEvent.where,dragRect); inGrow,inContent: BEGIN IF whichWindow <> FrontWindow THEN SelectWindow(whichWindow) END; END; { of code case } END; { of mouseDown } keyDown,autoKey: SysBeep(2); END; { of event case } END { of if GetNextEvent } ELSE IF myEvent.what = nullEvent THEN tk_yield; UNTIL doneFlag; END; BEGIN { main program } InitNetwork; UnLoadSeg(@InitNetwork); MainEventLoop; in_close; tm_allFree; END. !E!O!F! # # echo extracting appl/tftpo.text... cat >appl/tftpo.text <<'!E!O!F!' appl-tftpo.rsrc Type CODE appl-tftpl,0 !E!O!F! # # echo extracting appl/tftpr.text... cat >appl/tftpr.text <<'!E!O!F!' * TftpResDef -- Resource input for TFTP program * *{ Please note the copyright notice in the file "copyright/notice" } appl-tftpr.Rsrc Type MENU ,256 File Get... Put... Server... Abort Quit ,257 Settings ASCII Mode IMAGE Mode OCTET Mode MACINTOSH Mode Remote Host... Remote Directory... ,258 Show UDP Statistics My Internet Address My Appletalk Address IP Statistics Type DLOG ,11 90 75 260 425 Visible 1 NoGoAway 0 17 Type DITL ,17 10 BtnItem Enabled 114 15 132 160 OK BtnItem Enabled 114 190 132 335 Cancel EditText Disabled 34 15 50 335 Put Name Here EditText Disabled 84 15 100 335 Put Host Here RadioItem Enabled 150 15 165 90 ASCII RadioItem Enabled 150 95 165 170 Image RadioItem Enabled 150 175 165 250 Octet RadioItem Enabled 150 255 165 330 Mac StatText Disabled 12 15 28 335 ^0: StatText Disabled 62 15 78 335 Name or Address of the Remote Host: Type DLOG ,50 100 100 215 400 Visible 2 NoGoAway 0 52 Type DITL ,52 5 BtnItem Enabled 65 34 97 114 OK BtnItem Enabled 65 180 97 260 CANCEL ChkItem Enabled 35 35 52 115 Fetch ChkItem Enabled 35 180 51 280 Store StatText Disabled 10 35 31 280 Indicate which transfers to allow: Type DLOG ,49 100 125 245 375 Invisible 3 NoGoAway 0 51 Error Box Type DITL ,51 2 StatText Disabled 10 10 25 240 A Network Error Happened! StatText Disabled 80 10 135 240 Set by SetIText Type DLOG ,60 100 80 190 420 Visible 1 NoGoAway 0 61 Foreign Directory Type DITL ,61 4 BtnItem Enabled 10 95 30 155 OK BtnItem Enabled 10 185 30 245 CANCEL EditText Disabled 65 10 80 330 dir StatText Disabled 40 10 55 330 Please type in the foreign directory name. Type DLOG ,-4001 0 0 152 358 InVisible 1 NoGoAway 0 -4001 Type DITL ,-4001 12 BtnItem Enabled 28 162 46 242 Open BtnItem Disabled 59 1552 77 1232 Invisible BtnItem Enabled 90 162 108 242 Cancel *the volume name UserItem Disabled 22 258 54 354 BtnItem Enabled 59 266 77 346 Eject BtnItem Enabled 90 266 108 346 Drive *the window in the dialog UserItem Enabled 11 12 125 135 *the scroll bar for the window UserItem Enabled 11 134 125 150 *the gray line UserItem Disabled 20 254 116 255 StatText Disabled 20 1044 116 1145 Invisible Text *the horizontal scroll bar UserItem Enabled 124 12 140 135 ChkItem Enabled 125 172 140 346 Multiple File Selection Type DLOG ,31 100 125 245 375 Visible 1 NoGoAway 0 62 Name Foreign Host Type DITL ,62 4 BtnItem Enabled 10 50 40 110 OK BtnItem Enabled 10 140 40 200 CANCEL EditText Disabled 115 10 130 240 DefaultForeignHost StatText Disabled 50 10 100 240 Please type in the name or number of the foreign host you want to reach. Type ALRT ,333 80 100 255 420 444 FFFF Type DITL ,444 3 BtnItem Enabled 140 110 165 210 QUIT StatText Disabled 10 90 55 300 A fatal error happened! The program will die. StatText Disabled 80 20 135 300 ^0 Type ALRT ,666 80 100 255 420 777 FFFF Type DITL ,777 5 BtnItem Enabled 140 110 165 210 HALT StatText Disabled 10 90 55 300 Fatal Error! A task overflowed its stack! StatText Disabled 80 20 95 300 Stack Pointer about ^0 StatText Disabled 100 20 115 300 Task control block at ^1 StatText Disabled 120 20 135 300 Task name is ^2 Type DLOG ,77 50 100 295 375 Visible 4 NoGoAway 0 78 IP Statistics Type DITL ,78 22 StatText Disabled 15 180 30 265 precv StatText Disabled 35 180 50 265 psnt StatText Disabled 55 180 70 265 pdrop StatText Disabled 75 180 90 265 bdchk StatText Disabled 95 180 110 265 unprot StatText Disabled 115 180 130 265 bdvers StatText Disabled 135 180 150 265 bdlen StatText Disabled 155 180 170 265 ttlexp StatText Disabled 175 180 190 265 frags StatText Disabled 195 180 210 265 free StatText Disabled 215 180 230 265 fail StatText Disabled 15 10 30 170 Packets Received: StatText Disabled 35 10 50 170 Packets Sent: StatText Disabled 55 10 70 170 Packets Dropped: StatText Disabled 75 10 90 170 Bad Checksums: StatText Disabled 95 10 110 170 Unhandled Protocols: StatText Disabled 115 10 130 170 Bad Versions: StatText Disabled 135 10 150 170 Bad Lengths: StatText Disabled 155 10 170 170 TTL Expired: StatText Disabled 175 10 190 170 Fragments: StatText Disabled 195 10 210 170 Free Packets: StatText Disabled 215 10 230 170 Listen Failures: Type DLOG ,93 100 75 250 425 Visible 4 NoGoAway 0 94 UDP Statistics Type DITL ,94 15 StatText Disabled 10 10 25 70 Local StatText Disabled 10 78 25 138 Foreign StatText Disabled 10 146 25 206 Host StatText Disabled 10 214 25 276 Handler StatText Disabled 10 284 25 344 Cn StatText Disabled 30 10 45 70 StatText Disabled 30 78 45 138 StatText Disabled 30 146 45 206 StatText Disabled 30 214 45 276 StatText Disabled 30 284 45 344 StatText Disabled 50 10 65 70 StatText Disabled 50 78 65 138 StatText Disabled 50 146 65 206 StatText Disabled 50 214 65 276 StatText Disabled 50 284 65 344 Type BNDL ,128 TFTP 0 2 ICN# 1 0 128 FREF 1 0 128 Type FREF ,128 APPL 0 Type TFTP = STR ,0 TFTP Version 2.2, 10 September 1985 !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting net/arp_lib.text... cat >net/arp_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} Unit ARP_Lib; { Please note the copyright notice in the file "copyright/notice" } { Note: This code was inspired by the Ethernet device handler in the PCIP system distributed by MIT. } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-IP_Lib } IP_Lib; {$L+} CONST MaxIPAddr = 10; ARP_Request = 1; ARP_Reply = 2; ARP_Hrd_AB = 3; { This magic number created by Mark Sherman and is not official } AB_ARP = 23;{ Address resolution protocol, as per Ethernet and RFC 826. However, ethernet uses protocol number 2054 to represent an ARP Protocol - I can not since DDP only has 8 bits of protocol number. } AR_DOD_IP = 2048; { Magic number requesting translation to /from DOD internet protocol } TYPE ARP_Buf = PACKED RECORD AR_Hrd: Integer; { Hardward address space } AR_Pro: Integer; { Protocol address space } AR_Hln: Byte; { Byte len of hardware add = 4 } AR_Pln: Byte; { Byte len of protocol add = 4 } AR_Op: Integer; { Opcode (request or reply) } AR_Snd_AB: AddrBlock; { Hardware address of sender } AR_Snd_IP: in_name; { Protocol address of sender } AR_Tar_AB: AddrBlock; { Hardware address of target } AR_Tar_IP: in_name; { Protocol address of target } END; ARP_Packet = ^ ARP_Buf; IP2AB_Entry = RECORD AB: AddrBlock; IP: in_name; Count: Integer; { try to keep some statistics } END; FUNCTION arp_init: Ref_Task; PROCEDURE Add_AB_Address(IP_Address:in_name;AB_Addr:AddrBlock); PROCEDURE AB_ARP_Snd(WhichWay:Integer; Recipient: AddrBlock; Other_IP_Addr: in_name); PROCEDURE AB_ARP_Rcv(Dummy:PTR); FUNCTION Get_AB_Address(IP_Address:in_name;VAR AB_Addr:AddrBlock): Boolean; PROCEDURE IP2AB(IP: in_name; VAR AB:AddrBlock); CONST ARPTKSZ = 2048; IMPLEMENTATION VAR ARPtask: Ref_Task; { main ARP receiving task } Cur_ARP_Pkt:ARP_Packet; { ARP receive packet } arp_rcv: Ref_Task; { Task that waits for incoming ARPs } my_AB_Addr: AddrBlock; { My Apple bus address } Everyone_on_AB: AddrBlock; { A reusable broadcast address } my_ARP_Pkt: ARP_Packet; { Reusable send packet for ARP } { Table of IP <-> AB Pairs } IP2AB_Table: ARRAY [0..MaxIPAddr] OF IP2AB_Entry; Last_IP2AB_Entry: -1..MaxIPAddr; { Last Entry Filled In } Next_IP2AB_Entry: 0..MaxIPAddr; { Next Entry to Use when inserting entry } { Statistics on ARP Usage } ABARPSnd: Integer; { ARPs sent } ABARPReq: Integer; { ARP requests received } ABARPRep: Integer; { ARP replies received } ABARPNotMe: Integer; { Misaddress ARPs - should be 0 w/ DDP } ABARPBad: Integer; { ARP Packet ill formed } ABARPUnexpected: Integer; { Got ARP when Request not outstanding } ABARPProtocolErr: Integer; { ARP wanted non IP address translation } {$S InitSeg } PROCEDURE ARPEnable; external; FUNCTION GetARPBuf:ARP_Packet; external; FUNCTION arp_init: Ref_Task; VAR myNode,myNet:INTEGER; BEGIN ABARPSnd := 0; ABARPReq := 0; ABARPRep := 0; ABARPNotMe := 0; ABARPBad := 0; ABARPUnexpected := 0; ABARPProtocolErr := 0; if GetNodeAddress(myNode,myNet) <> noErr then exit(arp_init); my_AB_Addr.ANode := myNode; my_AB_Addr.ANet := myNet; my_AB_Addr.ASocket := AB_IP_Socket; Everyone_on_AB.ANet := 0; Everyone_on_AB.ANode := $FF; Everyone_on_AB.ASocket := AB_IP_Socket; Last_IP2AB_Entry := -1; Next_IP2AB_Entry := 0; { ARP initialization } arp_rcv := NIL; my_ARP_Pkt := POINTER(ORD4(NewPtr(sizeof(ARP_Buf)))); cur_ARP_Pkt := getarpbuf; ARPtask := tk_fork(tk_cur,@AB_ARP_Rcv,ARPTKSZ,'ARP Recv',NIL); arp_init := ARPTask; END; {$S } {$IFC DEBUG} PROCEDURE out_inaddr(fhost: in_name); VAR host:_ipname; BEGIN host.in_lname := fhost; Write(BitAND(host.in_lst.in_net,255):1,'.', BitAND(host.in_lst.in_nets,255):1,'.', BitAND(host.in_lst.in_netss,255):1,'.', BitAND(host.in_lst.in_host,255):1); END; { End of out_inaddr } {$ENDC} PROCEDURE Add_AB_Address(IP_Address:in_name;AB_Addr:AddrBlock); { Look in the table for the appropriate ip address. If there, reset AB_Addr to it, otherwise make a new entry } VAR i: Integer; BEGIN {$IFC DEBUG} Write('Add_AB_Address: Associating IP address '); out_inaddr(IP_Address);WriteLn(' with AB address ',AB_Addr.ANode:1); {$ENDC} FOR i := 0 TO Last_IP2AB_Entry DO IF IP2AB_Table[i].IP = IP_Address THEN BEGIN IP2AB_Table[i].AB := AB_Addr; EXIT(Add_AB_Address); END; { Hmm, not in table already, must make a new entry } IP2AB_Table[Next_IP2AB_Entry].IP := IP_Address; IP2AB_Table[Next_IP2AB_Entry].AB := AB_Addr; Next_IP2AB_Entry := Next_IP2AB_Entry + 1; IF Next_IP2AB_Entry > MaxIPAddr THEN Next_IP2AB_Entry := 0; IF Last_IP2AB_Entry <> MaxIPAddr THEN Last_IP2AB_Entry := Last_IP2AB_Entry + 1; END; PROCEDURE AB_ARP_Snd(WhichWay:Integer; Recipient: AddrBlock; Other_IP_Addr: in_name); {Always use the same packet to send } VAR Success: Integer; { Status DDP write - ignored for ARP } myNode,myNet:INTEGER; BEGIN {$IFC DEBUG} WriteLn('AB_ARP_Snd: About to send an ARP packet'); {$ENDC} WITH my_ARP_Pkt^ DO BEGIN AR_Hrd:= ARP_Hrd_AB; AR_Pro:= AR_DOD_IP; AR_Hln:= sizeof(AddrBlock); AR_Pln:= sizeof(in_name); AR_Op:= WhichWay; { Should not be necessary but we are paranoid } if GetNodeAddress(myNode,myNet) <> noErr then exit(AB_ARP_Snd); my_AB_Addr.ANode := myNode; my_AB_Addr.ANet := myNet; my_AB_Addr.ASocket := AB_IP_Socket; AR_Snd_AB:= my_AB_Addr; AR_Snd_IP:= LocalIPaddr; AR_Tar_AB:= Recipient; AR_Tar_IP:= Other_IP_Addr; END; {$IFC DEBUG} WriteLn('The Write call is using socket ',my_AB_Addr.ASocket); WriteLn('With buffer length ',sizeof(ARP_Buf)); WriteLn('Going to node ',Recipient.ANode); {$ENDC} { Wait until previous write finished } WHILE CurW_Hdl^^.abResult = 1 DO tk_yield; { Fill in the disk block } WITH CurW_Hdl^^ DO BEGIN ddpType := AB_ARP; ddpSocket := my_AB_Addr.ASocket; ddpAddress := Recipient; ddpReqCount := sizeof(ARP_BUF); ddpDataPtr := POINTER(ORD4(my_ARP_Pkt)); END; { And do the write } Success := DDPWrite(CurW_Hdl,false,true); {$IFC DEBUG} WriteLn('AB_ARP_Snd: Wrote packet on applebus, status = ',Success); {$ENDC} END; PROCEDURE AB_ARP_Rcv(Dummy:PTR); LABEL 777; BEGIN WHILE TRUE DO BEGIN 777: ARPEnable; tk_block; {$IFC DEBUG} WriteLn('AB_ARP_Rcv: Received an ARP packet'); {$ENDC} WITH Cur_ARP_Pkt^ DO BEGIN {$IFC DEBUG} WriteLn('AR_Hrd = ',AR_Hrd); WriteLn('AR_Pro = ',AR_Pro); WriteLn('AR_Hln = ',AR_Hln); WriteLn('AR_Pln = ',AR_Pln); WriteLn('AR_Op = ',AR_Op); WriteLn('AR_Snd_AB = ',AR_Snd_AB.ANode); Write('AR_Snd_IP = ');out_inaddr(AR_Snd_IP);WriteLn(''); WriteLn('AR_Tar_AB = ',AR_Tar_AB.ANode); Write('AR_Tar_IP = ');out_inaddr(AR_Tar_IP);WriteLn(''); {$ENDC} { Wants hardware translation for Apple Bus? } IF AR_Hrd <> ARP_Hrd_AB THEN BEGIN {$IFC DEBUG} WriteLn('AB_ARP_Rcv: Doesn''t want AB hardware: ',AR_Hrd); {$ENDC} ABARPProtocolErr := ABARPProtocolErr + 1; goto 777; END; { Wants protocol translation of DOD's IP? } IF AR_Pro <> AR_DOD_IP THEN BEGIN {$IFC DEBUG} WriteLn('AB_ARP_Rcv: Doesn''t want IP translation: ',AR_Pro); {$ENDC} ABARPProtocolErr := ABARPProtocolErr + 1; goto 777; END; { Wants *my* IP address to be translated into an AB address? } IF AR_Tar_IP <> LocalIPaddr THEN BEGIN {$IFC DEBUG} Write('AB_ARP_Rcv: Not my IP address ');out_inaddr(AR_Tar_IP);WriteLn; {$ENDC} ABARPNotMe := ABARPNotMe + 1; goto 777; END; { Everything OK, so first update my tables with his information } Add_AB_Address(AR_Snd_IP,AR_Snd_AB); CASE AR_OP OF ARP_Request: BEGIN {$IFC DEBUG} Write('AB_ARP_Request: Got an ARP Request for me from '); out_inaddr(AR_Snd_IP); WriteLn(''); {$ENDC} ABARPReq := ABARPReq + 1; { And answer the request } AB_ARP_Snd(ARP_Reply, AR_Snd_AB, AR_Snd_IP); END; { End of ARP_Request } ARP_Reply: BEGIN {$IFC DEBUG} Write('AB_ARP_Request: Got an ARP Reply for me from '); out_inaddr(AR_Snd_IP); WriteLn(''); {$ENDC} { Someone waiting for the reply ? } IF arp_rcv <> NIL THEN tk_wake(arp_rcv) ELSE ABARPUnexpected := ABARPUnexpected + 1; { Why arrived? } arp_rcv := NIL; { Make sure he is not awakened more than once } END; { End of ARP_Reply } OTHERWISE BEGIN ABARPBad := ABARPBad + 1; {$IFC DEBUG} WriteLn('Bad opcode in ARP packet: ',AR_Op); {$ENDC} END; END; { of CASE } END; { of WITH } END; { infinite loop } END; FUNCTION Get_AB_Address(IP_Address:in_name;VAR AB_Addr:AddrBlock): Boolean; { Look in the table for the appropriate ip address. If there, set AB_Addr to it and return true, otherwise return false } VAR i: Integer; BEGIN FOR i := 0 TO Last_IP2AB_Entry DO IF IP2AB_Table[i].IP = IP_Address THEN BEGIN AB_Addr := IP2AB_Table[i].AB; Get_AB_Address := true; EXIT(Get_AB_Address); END; Get_AB_Address := False; END; PROCEDURE ARP_dotimer(tk:Ref_Task); BEGIN if (tk <> NIL) & (tk <> tk_cur) then tk_wake(tk); END; PROCEDURE IP2AB(IP: in_name; VAR AB:AddrBlock); CONST ARP_TIME_OUT = 900; { 15 seconds = 900 ticks } VAR ARPtimer: Ref_Timer; Dummy:BOOLEAN; BEGIN {$IFC DEBUG} Write('IP2AB: Looking up AB address for ');out_inaddr(IP);WriteLn(''); {$ENDC} { Look it up in the current table } IF Get_AB_Address(IP,AB) THEN EXIT(IP2AB); {$IFC DEBUG} WriteLn('IP2AB: AB address not found so performing ARP'); {$ENDC} { Not there, so let's ARP for it } arp_rcv := tk_cur; AB_ARP_Snd(ARP_Request, Everyone_on_AB, IP); ARPtimer := tm_alloc; if ARPtimer = NIL then begin {$IFC DEBUG} WriteLn('IP2AB: Can''t allocate timer'); {$ENDC} AB.ANet := 0; AB.ASocket := 0; AB.ANode := 0; arp_rcv := NIL; exit(IP2AB); end; tm_tset(ARP_TIME_OUT,@arp_dotimer,POINTER(ORD4(arp_rcv)),ARPtimer); while true do begin {$IFC DEBUG} WriteLn('IP2AB: Blocking'); {$ENDC} tk_block; {$IFC DEBUG} WriteLn('IP2AB: Awakened'); {$ENDC} { Have we been awakened by a reception, or timed out? } IF arp_rcv = NIL { got an ARP packet } THEN BEGIN Dummy := tm_clear(ARPtimer); {$IFC DEBUG} WriteLn('IP2AB: Got an ARP response'); {$ENDC} IF Get_AB_Address(IP,AB) THEN BEGIN Dummy := tm_free(ARPtimer); EXIT(IP2AB) END ELSE BEGIN { Nuts, got a different ARP answer, maybe ours is en route } {$IFC DEBUG} WriteLn('IP2AB: Received wrong ARP response'); {$ENDC} tm_tset(ARP_TIME_OUT,@arp_dotimer,POINTER(ORD4(arp_rcv)), ARPtimer); arp_rcv := tk_cur; END; END { end of = NIL block } ELSE BEGIN { Timed out. Oh well, we tried } {$IFC DEBUG} WriteLn('IP2AB: ARP timeout. Cannot translate address'); {$ENDC} Dummy := tm_free(ARPtimer); AB.ANet := 0; AB.ASocket := 0; AB.ANode := 0; arp_rcv := NIL; exit(IP2AB); END; END; { end of WHILE loop } END; END. !E!O!F! # # echo extracting net/call_asm.text... cat >net/call_asm.text <<'!E!O!F!' ; ;{ Please note the copyright notice in the file "copyright/notice" } ; ; The following little hack allows procedures to be called with any number ; of arguments so long as the procedure ptr is the last argument and so long ; as no static links are required to be maintained (Also works for functions!) ; .PROC CALL,4 ; General interface for calling procedures .DEF Call1 .DEF CallB .DEF Call1B .DEF Call1C .DEF Call2 .DEF Call3 .DEF Call3A .DEF Call3B .DEF Call4 .DEF Call4A CALLB CALL1 CALL1A CALL1B CALL1C CALL2 CALL3 CALL3A CALL3B CALL4 CALL4A MOVE.L 4(SP),A0 ; Copy the procedure pointer MOVE.L (SP)+,(SP) ; Move the return address down JMP (A0) ; Jump to the real routine ; ; The following routine has the following signature: ; FUNCTION StrCvt(S:STR255):StringPtr; ; ; It is intended to take a string a return a pointer to that string. ; Because Lisa Pascal will pass all strings over four characters in ; length by passing a pointer to the string, and because the Lisa ; Pascal conventions require the *caller* to make a copy of value ; structures, one can pass the address of a string by just passing the ; string. This is a long way of saying that this routine just moves its ; arguemnt down the stack ; .FUNC StrCvt,1 ; Slimy way to pass String Constants by VAR ; (Just leave the address on the stack!) MOVE.L (SP)+,A0 ; Get the return address MOVE.L (SP)+,(SP) ; Move the argument into the return value JMP (A0) ; And return ! ; ; The Lisa Pascal version of checksumming was wrong. Here is an assembler ; version from Bill Croft at Stanford. ; .func cksum ; (integer) sum = cksum(buf:Ptr, wdcnt:integer) move.l (sp)+,a1 clr.l d1 move.w (sp)+,d1 move.l (sp)+,a0 subq.l #1,d1 clr.l d0 @1 add.w (a0)+,d0 bcc.s @2 addq.l #1,d0 @2 dbra d1,@1 move.w d0,(sp) jmp (a1) ; ; Reboot routine scarfed from net.micro.mac ; .func reboot ROMStart .equ $2AE move.l ROMStart,A0 add.w #10,A0 jmp (A0) ; .END !E!O!F! # # echo extracting net/calls.text... cat >net/calls.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} UNIT Call_Lib; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf; {$L+} PROCEDURE Call(Proc: ProcPtr); FUNCTION CallB(Proc:ProcPtr):Boolean; PROCEDURE Call1(ArgPtr:PTR; Proc: ProcPtr); PROCEDURE Call1B(Flag: Integer; Proc: ProcPtr); PROCEDURE Call1C(c:char; Proc: ProcPtr); PROCEDURE Call3(PacketPtr:PTR; PacketLength: Integer; SourceAdd: LongInt; Handler: ProcPtr); FUNCTION Call3A(host: LongInt; file_name: StringPtr; TransferDirection: Integer; tfs_alert: ProcPtr): Integer; PROCEDURE Call3B(what:PTR; arg2,arg3:Integer; Proc: ProcPtr); FUNCTION Call4(PacketPtr:PTR; Protocol:Integer; PacketLength:Integer; FirstHop: LongInt; SendProc: ProcPtr):Integer; PROCEDURE Call4A(PacketPtr: PTR; PacketLength: Integer; HostAddr: LongInt; FooData: PTR; Handler: ProcPtr); IMPLEMENTATION FUNCTION CallB(Proc:ProcPtr):Boolean; EXTERNAL; PROCEDURE Call1(ArgPtr:PTR; Proc: ProcPtr); EXTERNAL; PROCEDURE Call1B(Flag: Integer; Proc: ProcPtr); EXTERNAL; PROCEDURE Call1C(c:char; Proc: ProcPtr); EXTERNAL; PROCEDURE Call(Proc: ProcPtr); EXTERNAL; PROCEDURE Call3(PacketPtr:PTR; PacketLength: Integer; SourceAdd: LongInt; Handler: ProcPtr); EXTERNAL; FUNCTION Call3A(host: LongInt; file_name: StringPtr; TransferDirection: Integer; tfs_alert: ProcPtr): Integer; EXTERNAL; PROCEDURE Call3B;EXTERNAL; FUNCTION Call4(PacketPtr:PTR; Protocol:Integer; PacketLength:Integer; FirstHop: LongInt; SendProc: ProcPtr):Integer; EXTERNAL; PROCEDURE Call4A(PacketPtr: PTR; PacketLength: Integer; HostAddr: LongInt; FooData: PTR; Handler: ProcPtr); EXTERNAL; END. !E!O!F! # # echo extracting net/cust_lib.text... cat >net/cust_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} Unit Cust_Lib; { Please note the copyright notice in the file "copyright/notice" } {$L-} INTERFACE USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf; TYPE CustRecord = RECORD LocalIPAddr: LongInt; GateWIPAddr: LongInt; NameServer: LongInt; TimeServer: LongInt; UserName: STR255; UseAB: Boolean; DefHost:STR255 END; Ref_CustRecord = ^ CustRecord; CONST CFileName = 'Customization Values'; PROCEDURE ReadCustom(CP: Ref_CustRecord); IMPLEMENTATION {$S InitSeg} PROCEDURE ReadCustom(CP: Ref_CustRecord); VAR OSStatus: OSErr; FRN: Integer; RLength: LongInt; NodeNumber,NetNumber:INTEGER; BEGIN if GetNodeAddress(NodeNumber,NetNumber) <> noErr then NodeNumber := 0; CP^.LocalIPAddr:= $80020000 + NodeNumber; CP^.GateWIPAddr:= $80020040; CP^.NameServer := $80020040; CP^.TimeServer := $80020040; CP^.UserName := 'AppleMAC'; CP^.DefHost := 'Unknown'; CP^.UseAB := TRUE; { Try to read in old values } OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN); IF OSStatus = noErr THEN BEGIN { File is there, read in the values } RLength := sizeof(CustRecord); { Get to the start of the file } OSStatus := SetFPos(FRN,fsFromStart,0); IF OSStatus = noErr THEN OSStatus := FSRead(FRN,RLength,POINTER(ORD4(CP))); OSStatus := FSClose(FRN); IF CP^.UseAB then begin CP^.LocalIPAddr := BitAnd(CP^.LocalIPAddr,$FFFFFF00) + NodeNumber; end; END; END; END. !E!O!F! # # echo extracting net/err_lib.text... cat >net/err_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} Unit Err_Lib; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf; CONST errId = 49; VAR errWindow:WindowPtr; Msg:STR255; PROCEDURE ErrInit; PROCEDURE CantAlloc(where:StringPtr; what:StringPtr); PROCEDURE CantConnect(where:StringPtr; what:StringPtr); PROCEDURE NotResponding(where:StringPtr); PROCEDURE FOpenErr(fileName:StringPtr; status:OSErr); PROCEDURE FWriteErr(fileName:StringPtr; status:OSErr); PROCEDURE FReadErr(fileName:StringPtr; status:OSErr); PROCEDURE Error(what:StringPtr); PROCEDURE Error2(what:StringPtr; arg:StringPtr); PROCEDURE Error3(what:StringPtr; arg:StringPtr; final:StringPtr); PROCEDURE ErrorOS(what:StringPtr;status:OSErr); PROCEDURE Fatal(what:StringPtr; re_boot:Boolean); PROCEDURE Message(from:StringPtr;what:StringPtr); PROCEDURE MsgRegister(wp:DialogPtr); FUNCTION ClickResolve(ev:EventRecord):Boolean; FUNCTION ActResolve(ev:EventRecord):Boolean; FUNCTION StrCvt(S:Str255): StringPtr; PROCEDURE reboot; IMPLEMENTATION FUNCTION StrCvt(S:Str255): StringPtr; EXTERNAL; PROCEDURE reboot; external; TYPE MsgRecPtr = ^MsgRec; MsgRec = RECORD next:MsgRecPtr; qType:QTypes; win:WindowPtr; END; MsgQ = RECORD qFlags:INTEGER; qHead:MsgRecPtr; qTail:MsgRecPtr end; VAR theMsgQ:MsgQ; {$S InitSeg} PROCEDURE ErrInit; BEGIN errWindow := POINTER(ORD4(GetNewDialog(errId,NIL,NIL))); theMsgQ.qFlags := 0; theMsgQ.qHead := NIL; theMsgQ.qTail := NIL; END; {$S } FUNCTION ZapIt(wp:WindowPtr):Boolean; VAR Dummy:OSErr; qp:MsgRecPtr; BEGIN qp := theMsgQ.qHead; while (qp <> NIL) & (qp^.win <> wp) do begin qp := qp^.next; end; if qp <> NIL { found it } then begin Dummy := dequeue(POINTER(ORD4(qp)),@theMsgQ); DisposDialog(wp); DisposPtr(POINTER(ORD4(qp))); ZapIt := true; end else begin ZapIt := false; end; END; FUNCTION ClickResolve(ev:EventRecord):Boolean; VAR wp:WindowPtr; code:INTEGER; BEGIN code := FindWindow(ev.where,wp); if wp = errWindow then begin HideWindow(errWindow); ClickResolve := true; end else ClickResolve := ZapIt(wp); end; FUNCTION ActResolve(ev:EventRecord):Boolean; VAR wp:WindowPtr; code:INTEGER; fake:INTEGER; iHndl:Handle; iRect:Rect; begin wp := POINTER(ORD4(ev.message)); if wp = errWindow then ActResolve := true else if BitAND(ev.modifiers,1) = 1 then ActResolve := true else ActResolve := ZapIt(wp); end; PROCEDURE MsgRegister(wp:WindowPtr); VAR qp:MsgRecPtr; BEGIN qp := POINTER(ORD4(NewPtr(sizeof(MsgRec)))); qp^.next := NIL; qp^.qType := dummyType; qp^.win := wp; enqueue(POINTER(ORD4(qp)),@theMsgQ); END; PROCEDURE DoErr; BEGIN ShowWindow(errWindow); SelectWindow(errWindow); END; PROCEDURE Message(from:StringPtr;what:StringPtr); VAR iType:INTEGER; iHndl:Handle; iRect:Rect; BEGIN GetDItem(errWindow,1,iType,iHndl,iRect); SetIText(iHndl,from^); GetDItem(errWindow,2,iType,iHndl,iRect); SetIText(iHndl,what^); DoErr; END; PROCEDURE Error(what:StringPtr); VAR iType:INTEGER; iHndl:Handle; iRect:Rect; BEGIN GetDItem(errWindow,1,iType,iHndl,iRect); SetIText(iHndl,'A network error happened.'); GetDItem(errWindow,2,iType,iHndl,iRect); SetIText(iHndl,what^); SysBeep(2); DoErr; END; PROCEDURE Error2(what:StringPtr; arg:StringPtr); VAR iType:INTEGER; iHndl:Handle; iRect:Rect; BEGIN GetDItem(errWindow,1,iType,iHndl,iRect); SetIText(iHndl,'A network error happened.'); GetDItem(errWindow,2,iType,iHndl,iRect); SetIText(iHndl,concat(what^,arg^)); SysBeep(2); DoErr; END; PROCEDURE Error3(what:StringPtr; arg:StringPtr; final:StringPtr); VAR iType:INTEGER; iHndl:Handle; iRect:Rect; BEGIN GetDItem(errWindow,1,iType,iHndl,iRect); SetIText(iHndl,'A network error happened.'); GetDItem(errWindow,2,iType,iHndl,iRect); SetIText(iHndl,concat(what^,arg^,final^)); SysBeep(2); DoErr; END; PROCEDURE ErrorOS(what:StringPtr;status:OSErr); VAR errstr:STR255; BEGIN case status of bdNamErr: errstr := 'The file name is invalid.'; dupFNErr: errstr := 'The file already exists.'; dirFulErr: errstr := 'The directory is full.'; dskFulErr: errstr := 'The disk is full.'; eofErr: errstr := 'End of file.'; extFSErr: errstr := 'External file system error.'; fBsyErr: errstr := 'The file is busy.'; fLckdErr: errstr := 'The file is locked.'; fnfErr: errstr := 'The file does not exist.'; fsRnErr: errstr := 'Difficulty with renaming.'; ioErr: errstr := 'Disk I/O error.'; mFulErr: errstr := 'Memory is full.'; nsvErr: errstr := 'There is no such volume.'; opWrErr: errstr := 'The file is already open for writing.'; posErr: errstr := 'The given file position is invalid.'; tmfoErr: errstr := 'Too many files are open now.'; vLckdErr: errstr := 'Software volume lock.'; wrPermErr: errstr := 'Permission does not allow writing.'; wPrErr: errstr := 'Hardware volume lock.'; {$IFC DEBUG} fnOpnErr: errstr := 'File not open!'; paramErr: errstr := 'Parameter error!'; rfNumErr: errstr := 'Bad reference number!'; {$ENDC} end; { of case } Error2(what,@errstr); END; PROCEDURE CantAlloc(where:StringPtr; what:StringPtr); BEGIN Error3(where,StrCvt(': I can''t get a new '),what); END; PROCEDURE CantConnect(where:StringPtr; what:StringPtr); BEGIN Error3(where,StrCvt(': I can''t open a connection, protocol '),what); END; PROCEDURE NotResponding(where:StringPtr); BEGIN Error3(where, StrCvt(': The foreign host is not responding. '), StrCvt('The operation will be aborted.')); END; PROCEDURE FOpenErr(fileName:StringPtr; status:OSErr); BEGIN Msg := concat('I can''t open "',fileName^,'"; '); ErrorOS(@Msg,status); END; PROCEDURE FWriteErr(fileName:StringPtr; status:OSErr); BEGIN Msg := concat('I can''t write to "',fileName^,'"; '); ErrorOS(@Msg,status); END; PROCEDURE FReadErr(fileName:StringPtr; status:OSErr); BEGIN Msg := concat('I can''t read from "',fileName^,'"; '); ErrorOS(@Msg,status); END; PROCEDURE Fatal(what:StringPtr; re_boot:Boolean); CONST FatalAlert = 333; VAR dummy:INTEGER; BEGIN ParamText(what^,'','',''); dummy := StopAlert(FatalAlert,NIL); if re_boot then reboot else halt; END; END. !E!O!F! # # echo extracting net/icmp_lib.text... cat >net/icmp_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT ICMP_Lib; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-err_lib } Err_Lib, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-calls } Call_Lib, {$U net-ip_lib } IP_Lib; {$L+} CONST { Define some ICMP messages } PGNOSND = 0 { Couldn't send pkt }; PGTMO = 1 { timedout }; PGBADDATA = 2 { rcved bad data back }; PGWAITING = 3 { waiting for rcpt of packet }; PGSUCCESS = 4 { success }; PROCEDURE icmprcv(p: PACKET; len:Integer; host:IN_Name); FUNCTION icmp_destun(host: in_name; ip: Ref_IP; Packet_type: Integer):Integer; PROCEDURE IcmpInit; PROCEDURE inroute(host: in_name; hop1: Ref_in_name); PROCEDURE ggprcv(p:PACKET; len:Integer; host:in_name); PROCEDURE GgpInit; IMPLEMENTATION TYPE Ref_Ping = ^ ping; ping = PACKED RECORD { ICMP Echo request/reply header } ptype: byte; pcode: byte; pchksum: Integer; pid: Integer; pseq: Integer; END; CONST ICMPPROT= 1 { ICMP Protocol number }; ECHOREP = 0 { ICMP Echo reply }; DESTIN = 3 { Destination Unreachable }; SOURCEQ = 4 { Source quench }; REDIR = 5 { Redirect }; ECHOREQ = 8 { ICMP Echo request }; TIMEX = 11 { Time exceeded }; PARAM = 12 { Parameter problem }; TIMEREQ = 13 { Timestamp request }; TIMEREP = 14; INFO = 15 { Information request }; ICMPSIZE = sizeof(ping); ECHOTMO = 6 { Echo reply timeout period. }; REDIRTABLEN = 16; { Size + 1 of redirection table } TYPE Raw_Bytes = PACKED ARRAY[0..32767] OF BYTE; Raw_Ptr_Type = ^Raw_Bytes; { redirect table definitions } redent = RECORD rd_dest: in_name; rd_to: in_name; END; { structure of an icmp destination unreachable packet } destun = PACKED RECORD dtype: byte; { + 0 } dcode: byte; { + 1 } dchksum: Integer; { + 2 } dno1: Integer; { + 4 } dno2: Integer; { + 6 } dip: ip; { The header for the packet } { + 8 } ddata: packed array [0..7] of byte; END; { structure of a timestamp reply } tstamp = PACKED RECORD ttype: byte; tcode: byte; txsum: Integer; tid: Integer; tseq: Integer; tstampvals: packed array [0..2] of LongInt; END; { structure of an icmp redirect } redirect = PACKED RECORD rdtype: byte; rdcode: byte; rdchksum: Integer; rdgw: in_name; rdip: ip; rddata: packed array [0..7] of byte; END; VAR redtab: array [0..REDIRTABLEN-1] of redent; rednext : integer; { See initialization in IcmpInit = 0 } icmp: IPCONN; pingstate: Integer; requested: Ref_Task; sent: PACKET; snt_len: Integer; pingseq: Integer; {$S InitSeg } PROCEDURE IcmpInit; VAR i: Integer; BEGIN { Once only initialization that used to be part of var decl's } FOR i := 0 TO REDIRTABLEN-1 DO BEGIN redtab[i].rd_dest := 0; redtab[i].rd_to := 0; END; rednext := 0; pingstate := PGWAITING; requested := NIL; pingseq := 1; sent := NIL; snt_len := 0; icmp := in_open(ICMPPROT, @icmprcv); if (icmp = NIL) then CantConnect(StrCvt('ICMP'),StrCvt('IP')) {$IFC DEBUG} WriteLn('ICMP: Opened ip conn.'); {$ENDC} END; { End of IcmpInit } {$S } PROCEDURE wake_req; BEGIN if (requested<>NIL) THEN tk_wake(requested); END; { ICMP packet handler } {$SETC DODESTIN := true} {$SETC DOTIMEREQ := true} {$SETC DOECHO := false} {$SETC DOSOURCEQ := false} {$SETC DOPARAM := false} {$SETC DOTIMEX := false} {$SETC DOINFO := false} PROCEDURE icmprcv(p: PACKET; len:Integer; host:IN_Name); VAR pip: Ref_IP; e: Ref_Ping; rd: ^redirect; pdp: ^destun; osum, xsum: Integer; data1, data2: PTR; i: integer; Raw_Ptr: Raw_Ptr_Type; DataMisMatch: Boolean; BEGIN {$IFC DEBUG} WriteLn('ICMP: p[',len,'] from '); out_inaddr(host); WriteLn(''); {$ENDC} pip := in_head(p); e := Ref_Ping(ORD4(in_data(pip))); osum := e^.pchksum; e^.pchksum := 0; if Odd(len) THEN BEGIN Raw_Ptr := POINTER(ORD4(e)); Raw_Ptr^[len] := 0; END; xsum := BitNOT(cksum(POINTER(ORD4(e)),BitSR(len+1,1))); if (xsum <> osum) THEN BEGIN e^.pchksum := osum; {$IFC DEBUG} WriteLn('ICMP: Bad xsum ',osum,' should have been ',xsum); in_dump(p); {$ENDC} in_free(p); exit(icmprcv); END; e^.pchksum := osum; CASE (e^.ptype) OF {$IFC DOECHO} ECHOREQ: BEGIN {$IFC DEBUG} Write('ICMP: Sending Echo Reply to '); out_inaddr(host); WriteLn('.'); {$ENDC} e^.ptype := ECHOREP; e^.pchksum := 0; if Odd(len) THEN BEGIN Raw_Ptr := POINTER(ORD4(e)); Raw_Ptr^[len] := 0; END; e^.pchksum := BitNOT(cksum(POINTER(ORD4(e)), BitSR(len+1,1))); pip^.ip_src := pip^.ip_dest; pip^.ip_dest := host; if (in_write(icmp, p, len, host) <= 0) THEN BEGIN {$IFC DEBUG} WriteLn('ICMP: Echo reply failed.\n'); {$ENDC} END; in_free(p); END; { end of ECHOREQ } ECHOREP: BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd echo reply.'); {$ENDC} if(e^.pseq <> (pingseq-1)) THEN BEGIN {$IFC DEBUG} WriteLn('ICMP_RCV: Bad echo reply seq #'); {$ENDC} in_free(p); exit(icmprcv); END; data1 := POINTER(ORD4(in_data(in_head(p)))+ICMPSIZE); data2 := POINTER(ORD4(in_data(in_head(sent)))+ICMPSIZE); FOR i := 0 to snt_len - 1 DO BEGIN DataMisMatch := (Data1^ <> Data2^); Data1 := POINTER(ORD4(Data1)+1); Data2 := POINTER(ORD4(Data2)+1); { if(*data1++ != *data2++) } IF DataMisMatch THEN BEGIN {$IFC DEBUG} WriteLn('bad icmp data at byte ',i); WriteLn('ICMP: Bad data in echo rep sent:'); in_dump(sent); WriteLn('rcvd:'); in_dump(p); {$ENDC} pingstate := PGBADDATA; wake_req; in_free(p); exit(icmprcv); END; { End of outer IF test } END; { End of FOR loop } pingstate := PGSUCCESS; wake_req; in_free(p); {$IFC DEBUG} WriteLn('ICMP: rcvd ICMP Echo Reply.'); {$ENDC} END; { End of ECHOREP } {$ENDC} {$IFC DODESTIN} DESTIN: BEGIN pdp := {(struct destun *)} POINTER(ORD4(in_data(in_head(p)))); {$IFC DEBUG} Write('ICMP: rcvd destination unreachable of type ', pdp^.dcode,' on host '); out_inaddr(pdp^.dip.ip_dest); WriteLn('.'); {$ENDC} { New from Tim -- cause the next attempt to route to this site to fail. This is done by putting in a null redirect table entry. } FOR i := 0 TO REDIRTABLEN - 1 DO if (redtab[i].rd_dest = pdp^.dip.ip_dest) THEN BEGIN redtab[i].rd_to := 0; in_free(p); exit(icmprcv); END; redtab[rednext].rd_dest := pdp^.dip.ip_dest; redtab[rednext].rd_to := 0; IF rednext = REDIRTABLEN - 1 THEN rednext := 0 ELSE rednext := rednext + 1; in_free(p); END; {End of DESTIN } {$ENDC} REDIR: BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd redirect.'); {$ENDC} rd := { (struct redirect *)} POINTER(ORD4(e)); {$IFC DEBUG} Write('redirect for '); out_inaddr(rd^.rdip.ip_dest); Write(' to '); out_inaddr(rd^.rdgw); WriteLn('.'); {$ENDC} FOR i := 0 TO REDIRTABLEN - 1 DO if (redtab[i].rd_dest = rd^.rdip.ip_dest) THEN BEGIN redtab[i].rd_to := rd^.rdgw; in_free(p); exit(icmprcv); END; redtab[rednext].rd_dest := rd^.rdip.ip_dest; redtab[rednext].rd_to := rd^.rdgw; IF rednext = REDIRTABLEN - 1 THEN rednext := 0 ELSE rednext := rednext + 1; in_free(p); END; { End of REDIR } {$IFC DOSOURCEQ} SOURCEQ: BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd source quench; ignoring.'); {$ENDC} in_free(p); END; {End of SOURCEQ } {$ENDC} {$IFC DOTIMEX} TIMEX: BEGIN {$IFC DEBUG} Write('ICMP: rcvd time exceeded message.'); {$ENDC} in_free(p); END; { End of TIMEX } {$ENDC} {$IFC DOPARAM} PARAM: BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd parameter problem message.'); {$ENDC} in_free(p); END; {End of PARAM } {$ENDC} {$IFC DOTIMEREQ} TIMEREQ: BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd Timestamp Request; ignoring.'); {$ENDC} e^.ptype := TIMEREP; e^.pchksum := 0; e^.pchksum := BitNOT(cksum(POINTER(ORD4(e)), BitSR(sizeof(tstamp),1))); pip^.ip_src := pip^.ip_dest; pip^.ip_dest := host; if (in_write(icmp, p, sizeof(tstamp), host) <= 0) THEN BEGIN {$IFC DEBUG} WriteLn('ICMP: Couldn''t send timestamp reply.'); {$ENDC} END; in_free(p); END; {End of TIMEREQ } {$ENDC} {$IFC DOINFO} INFO: BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd information request.'); {$ENDC} in_free(p); END; {End of INFO } {$ENDC} OTHERWISE BEGIN {$IFC DEBUG} WriteLn('ICMP: rcvd unhandled packet of type ',e^.ptype; in_dump(p); {$ENDC} in_free(p); END; {of otherwise } END; { OF CASE } END; { End of Icmprcv } { ICMP Echo Request - returns 1 if host replies, 0 if timeout or error } PROCEDURE pingtmo(Dummy:Ptr); BEGIN pingstate := PGTMO; if (requested<>NIL) THEN tk_wake(requested); END; FUNCTION IcEchoRequest(host: in_name; length: Integer): Integer; VAR p: PACKET; e: Ref_Ping; data: PTR; tm: Ref_Timer; i: integer; DummyBoolean: Boolean; BEGIN IcEchoRequest := 0; p := in_alloc(40, 0); if (p = NIL) THEN BEGIN CantAlloc(StrCvt('ICMP'),StrCvt('packet')); IcEchoRequest := PGNOSND; exit(IcEchoRequest); END; e := Ref_Ping(ORD4(in_data(in_head(p)) )); e^.ptype := ECHOREQ; e^.pcode := 0; e^.pid := 0; e^.pseq := pingseq; pingseq := pingseq + 1; { Put 256 random numbers in the packet. } data := POINTER(ORD4(in_data(in_head(p))) + ICMPSIZE); FOR i := 0 TO Length - 1 DO BEGIN Data^ := Random; Data := POINTER(ORD4(Data) + 1); END; { Calculate the checksum } e^.pchksum:= 0; if Odd(ICMPSIZE+length) THEN Data^ := 0; e^.pchksum := BitNOT(cksum(POINTER(ORD4(e)), BitSR(ICMPSIZE+length+1,1) )); pingstate := PGWAITING; requested := tk_cur; sent := p; snt_len := length; tm := tm_alloc; if (tm = NIL) THEN BEGIN CantAlloc(StrCvt('ICMP'),StrCvt('timer')); IcEchoRequest := PGNOSND; exit(IcEchoRequest); END; tm_set(ECHOTMO, @pingtmo, NIL, tm); if (in_write(icmp, p, ICMPSIZE+length, host) <= 0) THEN BEGIN {$IFC DEBUG} WriteLn('ICMP: Couldn''t send echo request.'); {$ENDC} DummyBoolean := tm_clear(tm); DummyBoolean := tm_free(tm); in_free(p); IcEchoRequest := PGNOSND; exit(IcEchoRequest); END; {$IFC DEBUG} in_dump(p); {$ENDC} while (pingstate = PGWAITING) DO tk_block; DummyBoolean := tm_clear(tm); DummyBoolean := tm_free(tm); in_free(p); sent := NIL; requested := NIL { Where did this variable come from ? }; IcEchoRequest := pingstate; END; { End of IcEchoRequest } FUNCTION icmp_destun(host: in_name; ip: Ref_IP; Packet_type: Integer):Integer; VAR p: PACKET; d: ^destun; i:integer; Raw_Ptr1, Raw_Ptr2: Raw_Ptr_Type; BEGIN icmp_destun := 0; {$IFC DEBUG} Write('ICMP: sending '); CASE Packet_Type OF 0: Write('net'); 1: Write('host'); 2: Write('protocol'); 3: Write('port'); 4: Write('fragmentation needed'); 5: Write('source route failed'); OTHERWISE Write('unknown packet type'); END; { of case } Write(' dest unreachable to '); out_inaddr(host); WriteLn(''); {$ENDC} p := in_alloc(512, 0); if (p = NIL) THEN BEGIN CantAlloc(StrCvt('ICMP_DESTUN'),StrCvt('packet')); icmp_destun := 0; exit(icmp_destun); END; d := { (struct destun *)} POINTER(ORD4(in_data(in_head(p)))); d^.dtype := DESTIN; d^.dcode := Packet_type; FOR i := 0 TO (sizeof(ip)+8)-1 DO BEGIN Raw_Ptr1 := POINTER(ORD4(ip)); Raw_Ptr2 := POINTER(ORD4(@D^) + 8); { Note: Magic 8 in DIP dcl} Raw_Ptr2^[i] := Raw_Ptr1^[i]; END; d^.dchksum := 0; d^.dchksum := BitNOT(cksum(POINTER(ORD4(d)), BitSR(sizeof(destun),1))); i := in_write(icmp, p, sizeof(destun), host); if (i <= 0) THEN BEGIN {$IFC DEBUG} WriteLn('ICMP: Couldn''t send dest unreachable'); WriteLn('ICMP: in_write returns ',i); {$ENDC} END; in_free(p); END; { End of icmp_destun } { Route a packet. Takes an internet address as its argument and a pointer to an internet address. Fills in this address with the internet address of the machine on our net which this packet should be sent to. Two routing algorithms are implemented: the MIT subnet routing algorithm, and a net-routing algorithm. The former places this interpretation on internet addresses: net,subnet,rsvd,host. It says: if the net and subnet of the destination of this packet and of my address are the same, the destination is on my network and I can send it directly to him. Otherwise, use the default gateway. The latter only checks on the net number. ICMP manages a routing table based on redirects which this code uses. } {$SETC CMU := false} FUNCTION samenet(hi1: in_name; hi2:in_name): Boolean; { define some masks for testing addresses } CONST AMASK = $80; AADDR = $00; BMASK = $C0; BADDR = $80; CMASK = $E0; CADDR = $C0; VAR h1,h2: _ipname; BEGIN h1.in_lname := hi1; h2.in_lname := hi2; {$IFC CMU} samenet := ((h1.in_lst.in_net = h2.in_lst.in_net) AND (h1.in_lst.in_nets = h2.in_lst.in_nets)); exit(samenet); {$ELSEC} if (BitAND(h1.in_lst.in_net,AMASK) = AADDR) THEN BEGIN { We have a class A network } { IF custom.c_route <> 0 THEN samenet := (h1.in_lst.in_net = h2.in_lst.in_net) ELSE } samenet := ((h1.in_lst.in_net = h2.in_lst.in_net) AND (h1.in_lst.in_nets = h2.in_lst.in_nets)); exit(samenet); END; if (BitAND(h1.in_lst.in_net,BMASK) = BADDR) THEN BEGIN { We have a class B network } { IF custom.c_route <> 0 THEN samenet := (h1.in_lst.in_net=h2.in_lst.in_net) AND (h1.in_lst.in_nets = h2.in_lst.in_nets) ELSE } samenet := ( (h1.in_lst.in_net = h2.in_lst.in_net) AND (h1.in_lst.in_nets = h2.in_lst.in_nets) AND (h1.in_lst.in_netss = h2.in_lst.in_netss)); exit(samenet); END; if(BitAND(h1.in_lst.in_net,CMASK) = CADDR) THEN BEGIN { Got a class C network } samenet := ( (h1.in_lst.in_net = h2.in_lst.in_net) AND (h1.in_lst.in_nets = h2.in_lst.in_nets) AND (h1.in_lst.in_netss = h2.in_lst.in_netss)); exit(samenet); END; {$ENDC} {$IFC DEBUG} Write('bad address - '); out_inaddr(hi1); WriteLn(''); {$ENDC} samenet := false; END; { End of samenet } PROCEDURE inroute(host: in_name; hop1: Ref_in_name); VAR i: integer; BEGIN {$IFC DEBUG} Write('Making a routing through the network for host '); out_inaddr(host); WriteLn(''); {$ENDC} { first check through the redirect table for this host } FOR i := 0 TO REDIRTABLEN - 1 DO IF redtab[i].rd_dest = 0 THEN cycle ELSE if (redtab[i].rd_dest = host) THEN BEGIN hop1^ := redtab[i].rd_to; { destination unreachable messages cause a null entry to be inserted. This makes IP think it can't write to that host. However, these null entries are only good until the next attempt to connect. Eventually, they should have a time value and a limited lifespan. -- Tim } if redtab[i].rd_to = 0 then redtab[i].rd_dest := 0; exit(inroute); END; { Check if it is on my net } if (samenet(LocalIPAddr, host)) THEN BEGIN hop1^ := host; exit(inroute); END; { The host isn't on a net I'm on, so send it to the default gateway on IP } hop1^ := GWIPAddress; END; { end of inroute} { A tiny GGP which will respond to ggp echo requests. (c) 1983 Massachussetts Institute of Technology } CONST GGP_Prot = 3; VAR ggp: IPCONN; {$S InitSeg } PROCEDURE GgpInit; BEGIN ggp := in_open(GGP_Prot, @ggprcv); if ggp = NIL THEN CantConnect(StrCvt('GGP'),StrCvt('InterNet')) {$IFC DEBUG} else if BCBitAnd(NDEBUG,INFOMSG) THEN WriteLn('GGP: Opened InterNet connection.'); {$ENDC} END; {$S } TYPE ggping = PACKED RECORD gtype: byte; gcode: byte; gseq: integer; END; { GGP packet handler } PROCEDURE ggprcv(p:PACKET; len:Integer; host:in_name); VAR pip: Ref_IP; e: ^ggping; BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('GGP: Received packet from host '); out_inaddr(host); WriteLn('.'); END; {$ENDC} pip := in_head(p); e := { (struct ggping *)} POINTER(ORD4(in_data(pip))); if (e^.gtype = ECHOREQ) THEN BEGIN e^.gtype := ECHOREP; if (in_write(ggp, p, len, host) <= 0) THEN BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,BitOR(INFOMSG,PROTERR)) THEN WriteLn('GGP: Echo reply failed.'); {$ENDC} END {$IFC DEBUG} else if BCBitAnd(NDEBUG,INFOMSG) THEN WriteLn('GGP: Sent echo reply.'); {$ENDC} END {$IFC DEBUG} else if BCBitAnd(NDEBUG,BitOR(PROTERR,INFOMSG)) THEN BEGIN WriteLn('GGP: Received unhandled packet type',e^.gtype); END {$ENDC} ; in_free(p); END; { End of ggprcv } END. !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting net/ip_lib.text... cat >net/ip_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT IP_Lib; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-err_lib } Err_Lib, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-cust_lib } Cust_Lib, {$U net-calls } Call_Lib; {$L+} CONST AB_IP_Socket = 72; { Just some socket that everyone agrees on } TYPE { The buffer organization is somewhat intertwined with the queue organization. Each buffer is in a queue, either the free queue or the used queue (or the buffer is currently being used by a user program or the interrupt level routines, in which case it does not appear in a queue). When a buffer is in a queue, it has a pointer to the next buffer in the queue. If there is no next buffer, its pointer points at nullbuf. Also, each buffer knows its own length. } Packet = ^ net_buf; net_buf = PACKED RECORD nb_elt: Packet; { queue link } nb_type: QTypes; { for Mac queues } nb_buff: PTR; { The buffer } nb_tstamp: LongInt; { packet timestamp } nb_len: Integer; { Length of buffer } END; { Internet connection information } ip_iob = RECORD c_prot: byte; { protocol } c_handle: ProcPtr; { int returning procedure - see below: } END; IPCONN = ^ip_iob; { The IP connection type } in_name = LongInt; Ref_in_name = ^ in_name; ip = PACKED RECORD ip_ver: 0..15; { Header version } ip_ihl: 0..15; { Internet header length in 32 bit words } ip_tsrv: byte; { Type of service } ip_len: integer; { Total packet length including header } ip_id: integer; { ID for fragmentation } ip_flgs: 0..7; { flags } ip_foff: 0..8191; { Fragment offset } ip_time: byte; { Time to live (secs) } ip_prot: byte; { protocol } ip_chksum: integer; { Header checksum } ip_src: in_name; { Source name } ip_dest: in_name; { Destination name } END; Ref_IP = ^ IP; { IP header and internet names } in_lst_type = PACKED RECORD in_net: byte; in_nets: byte; in_netss: byte; in_host: byte; END; _ipname = PACKED RECORD CASE Integer OF 0: (in_lname: in_name); 1: (in_lst: in_lst_type); END; CONST AB_IP = 22; { Internet Protocol - unofficial number for debugging } IP_PROTOCOL = 0; CHAOS = 1; PUP = 2; SLP = 3; ADR = 4; { Dave Plummer's Address Resolution Protocol } IPHSIZ = sizeof(ip) { internet header size }; INETLEN = 576 { maximum size of internet packet (bytes) }; DSTNET = 0; DSTHOST = 1; DSTPROT = 2; DSTPORT = 3; DSTFRAG = 4; DSTSRC = 5; VAR LocalIPAddress, GWIPAddress: in_name; DefaultHost:STR255; Use_AB: Boolean; ip_queue:QHdr; IPStack:INTEGER; CONST Num_Name_Servers = 1; { Number of name servers for which we have addresses } Last_NS_Index = 0; { = Num_Name_Servers - 1 } VAR NSIPAddress: ARRAY [0..Last_NS_Index] OF in_name; CONST Num_Time_Servers = 1; { Number of time servers for which we have addresses } Last_TS_Index = 0; { = Num_Time_Servers - 1 } VAR TSIPAddress: ARRAY [0..Last_TS_Index] OF in_name; CONST Max_User_Name = 8; VAR User_Name: STRING[Max_User_Name]; FUNCTION in_open(prot:byte; handler: ProcPtr):IPCONN; FUNCTION in_alloc(datalen:Integer; optlen:Integer):PACKET; FUNCTION in_write(conn: IPCONN; p:PACKET; datalen:Integer; fhost: in_name):INTEGER; PROCEDURE in_free(p: PACKET); FUNCTION in_more: Boolean; PROCEDURE in_close; FUNCTION inverify(p:Packet):Boolean; PROCEDURE cvt_inaddr(fhost: in_name; VAR s:STR255); PROCEDURE indemux(Fake:PTR); PROCEDURE in_init; FUNCTION in_data(pip:Ref_IP): PTR; FUNCTION in_head(ppkt:Packet): Ref_IP; FUNCTION CkSum(BufPtr:PTR; Count:Integer): Integer; FUNCTION in_mymach(host: in_name): in_name; PROCEDURE in_stats; VAR freeq:QHdr; CurW_Hdl: ABRecHandle; IMPLEMENTATION { Functions from ICMP_LIB } FUNCTION icmp_destun(host: in_name; ip: Ref_IP; Packet_type: Integer):Integer; external; PROCEDURE inroute(host: in_name; hop1: Ref_in_name); external; CONST { Some useful definitions } Cur_IP_VER = 4 { internet version }; IP_IHL_Off = 5 { IN header length in longwords }; IP_TSRV = 0 { default type of service }; IP_ID = 0 { kernel fills in IN id }; NO_IP_FLGS = 0 { no fragmentation yet }; Cur_IP_FOFF = 0 { " " " }; IP_TIME = 255 { maximum time to live }; FirstIPXSUM = 0 { initial checksum }; FirstIPLEN = sizeof(ip) { internet header length }; NBUFINIT = 6; LBUFINIT = 600; { The following goodly macros will have to be changed to functions } FUNCTION in_head(ppkt:Packet): Ref_IP; BEGIN in_head := {(struct ip *)} POINTER(ORD4( ppkt^.nb_buff )); END; FUNCTION in_data(pip:Ref_IP): PTR; BEGIN { Note: This must be changed because of bit packing in the header } in_data := { (char *) } POINTER(ORD4(pip) + BitSL(pip^.ip_ihl,2)); END; FUNCTION in_options(pip:Ref_IP): PTR; BEGIN in_options := { (char *) } POINTER( ORD4(pip) + BitSL(IP_IHL_Off,2) ); END; FUNCTION in_optlen(pip: Ref_IP):Byte; BEGIN in_optlen := BitSL(pip^.ip_ihl - IP_IHL_Off,2); END; FUNCTION CkSum(BufPtr:PTR; Count:Integer): Integer; external; {$IFC DEBUG} { This function dumps an internet packet to the screen. It uses some of the screen handling functions to help in this tedious chore. } PROCEDURE in_dump(p:PACKET); VAR i,j : Integer; pip: Ref_IP; data: PTR; xsum, osum: integer; out: STR255; BEGIN pip := in_head(p); data := p^.nb_buff; WriteLn('Packet address = ',ORD4(p)); (* FOR i := 0 to 6 DO BEGIN WriteLong(ord4(data));Write(': '); FOR j := 0 to 23 DO BEGIN Num2HexStr(data^,out); out := copy(out,3,2); Write(out); data := POINTER(ORD4(Data) + 1); { Print a word at a time } END; WriteLn(''); END; *) { Display header info in reasonable form } WriteLn('Header length = ',pip^.ip_ihl, ', IP Length = ',{ bswap } (pip^.ip_len), ', Total Length = ',p^.nb_len); WriteLn('Version = ',pip^.ip_ver, ', Type of Ser = ',pip^.ip_tsrv, ', tProtocol = ',pip^.ip_prot, ', Time To Live = ',pip^.ip_time); WriteLn('Frag Offset = ',pip^.ip_foff,', Flags = ',pip^.ip_flgs); Write('Source = '); out_inaddr(pip^.ip_src); Write(', Destin = '); out_inaddr(pip^.ip_dest); WriteLn(''); WriteLn('ID = ',pip^.ip_id); WriteLn('Checksum = ',pip^.ip_chksum); osum := pip^.ip_chksum; pip^.ip_chksum := 0; xsum := BitNOT(cksum(POINTER(ORD4(pip)), BitSL(pip^.ip_ihl,1))); WriteLn('Computed xsum = ',xsum); pip^.ip_chksum := osum; if (xsum = osum) THEN WriteLn('Checksum is CORRECT.') ELSE WriteLn('Checksum is NOT CORRECT.'); END; { End of in_dump } {$ENDC} VAR ipdemux:Ref_Task; { demuxer task address } { internet statistics - all initialized in in_init } ipdrop: integer; { ip packets dropped } ipxsum: integer; { ip packets with bad checksums } iplen : integer; { ip packets with bad lengths } ipdest: integer; { ip packets with bad destinations } ipttl : integer; { ip packets with time to live := 0 } ipprot: integer; { no server for protocol } ipver : integer; { bad ip version number } iprcv : integer; { number of ip packets received } ipfrag: integer; { number of fragments received } ipwwop: Integer; { Number of times awakened w/o packets } ipmulti: Integer; { Number of times found > 1 packet on queue } { Allocate and internet packet. Has to grunge around with local net header sizes to do the right thing. } FUNCTION in_alloc(datalen:Integer; optlen:Integer):PACKET; VAR p: PACKET; pip: Ref_IP; len: integer; status:OSErr; BEGIN optlen := BitAND((optlen + 3),BitNOT(3)); { len := (IPHSIZ + optlen + datalen + 1) & ~1;} len := (IPHSIZ + optlen + datalen + 1); IF Odd(len) THEN len := len - 1; if (datalen > INETLEN) THEN BEGIN {$IFC DEBUG} WriteLn('IN_ALLOC: Packet size ',datalen,'is too large.'); {$ENDC} in_alloc := NIL; exit(in_alloc); END; p := POINTER(ORD4(freeq.qHead)); if p <> NIL then begin status := dequeue(freeq.qHead,@freeq); if status <> noErr then p := NIL; end; if p = NIL THEN BEGIN CantAlloc(StrCvt('IN_ALLOC'),StrCvt('packet')); in_alloc := NIL; END else begin pip := in_head(p); pip^.ip_ihl := IP_IHL_Off + (optlen DIV 4); in_alloc := p; end; END; {End of in_alloc} { Free up an internet packet } PROCEDURE in_free(p: PACKET); BEGIN enqueue(POINTER(ORD4(p)), @freeq); END; { Intialize the internet layer. } CONST LastIPConn = 9; VAR ipsnd: integer; uid: integer; ipconns: array [0..LastIPConn] of ipconn; { demux table } nipconns: Integer; { current posn in demux table } FUNCTION arp_init: Ref_Task; external; PROCEDURE ip_listen; external; PROCEDURE listeninit(it:Ref_Task; at:Ref_Task; q:QHdrPtr; fq:QHdrPtr); external; {$S InitSeg } PROCEDURE in_init; VAR i:INTEGER; temp_packet:PACKET; CR: CustRecord; TempSkt: Byte; status:OSErr; NBUF: LongInt; { # of packet buffers } divisor: LongInt; LBUF: Integer; { size of packet buffers } BEGIN {$IFC NOT DEBUG} ReadCustom(@CR); LocalIPAddress := CR.LocalIPAddr; GWIPAddress := CR.GateWIPAddr; NSIPAddress[0] := CR.NameServer; TSIPAddress[0] := CR.TimeServer; User_Name := CR.UserName; Use_AB := CR.UseAB; DefaultHost := CR.DefHost; {$ENDC} nipconns := 0; { current posn in demux table } ipsnd := 0; uid := 1 ; { internet statistics are initialized } ipdrop := 0; { ip packets dropped } ipxsum := 0; { ip packets with bad checksums } iplen := 0; { ip packets with bad lengths } ipdest := 0; { ip packets with bad destinations } ipttl := 0; { ip packets with time to live := 0 } ipprot := 0; { no server for protocol } ipver := 0; { bad ip version number } iprcv := 0; { number of ip packets received } ipfrag := 0; { number of fragments received } ipwwop := 0; ipmulti := 0; { Create the queue of free packets. Format each packet and add it to the tail of the queue } {$IFC DEBUG} WriteLn('About to allocate the packet pool'); {$ENDC} divisor := 32768; NBUF := ((ORD4(ApplicZone^.bkLim) - ORD4(ApplicZone)) div divisor) + NBUFINIT; LBUF := LBUFINIT; { size of packet buffers } { Initialize the receive queue } ip_queue.qFlags := 0; ip_queue.qHead := NIL; ip_queue.qTail := NIL; { Initialize the free packet queue } freeq.qFlags := 0; freeq.qHead := NIL; freeq.qTail := NIL; FOR i := 0 TO NBUF - 1 DO BEGIN temp_packet := POINTER(ORD4(NewPtr(sizeof(Net_Buf)))); if (temp_packet = NIL) THEN Fatal(StrCvt('in_init: can''t make free queue'),false); temp_packet^.nb_type := dummyType; temp_packet^.nb_tstamp := 0; temp_packet^.nb_len := 0; temp_packet^.nb_buff := POINTER(ORD4(NewPtr(LBUF))); if (temp_packet^.nb_buff = NIL) THEN Fatal(StrCvt('Ran out of packet storage during in_init'),false); enqueue(POINTER(ORD4(temp_packet)), @freeq); END; { DDP Send handle initialization } CurW_Hdl := POINTER(ORD4(NewHandle(ddpSize))); CurW_Hdl^^.abResult := noErr; { Another write can be done } ipdemux := tk_fork(tk_cur, @indemux, IPStack, 'IPDemux',NIL); listeninit(ipdemux,arp_init,@ip_queue,@freeq); { Initialize the AB } TempSkt := AB_IP_Socket; IF DDPOpenSocket(TempSkt,@ip_listen) <> 0 THEN CantConnect(StrCvt('DDPOpenSocket'),StrCvt('socket for Applebus IP')); END; { end of in_init } {$S } { Return true if there are any enqueued, unprocessed packets in system. } FUNCTION in_more: Boolean; BEGIN in_more := ip_queue.qHead <> NIL; END; { Return the address of our machine relative to a certain foreign host. } FUNCTION in_mymach(host: in_name): in_name; BEGIN in_mymach := LocalIPAddr; END; { End of my_mach } { Open a protocol connection on top of internet. Protocol information necessary for packet demultiplexing; handler is upcalled upon receipt of packet. handler is int handler(p, len, fhost) PACKET p int len in_name fhost } VAR IPConn_Table: ARRAY [0..LastIPConn] of ip_iob; FUNCTION in_open (prot:byte; handler: ProcPtr):IPCONN; VAR i: integer; conn: IPCONN; BEGIN FOR i := 0 to nipconns-1 DO IF (ipconns[i]^.c_prot = prot) THEN BEGIN in_open := NIL; exit(in_open); END; IF nipconns = LastIPConn + 1 THEN conn := NIL ELSE conn := @IPConn_Table[nipconns]; if (conn = NIL) THEN BEGIN in_open := NIL; exit(in_open); END; conn^.c_prot := prot; conn^.c_handle := handler; ipconns[nipconns] := conn; nipconns := nipconns + 1; in_open := conn; END; { end of in_open } { Fill in the internet header in the packet p and send the packet through the appropriate net interface. This will involve using routing. Packets for a certain connection are all routed at connection open time, but some facility should be provided to allow for later rerouting. } PROCEDURE IP2AB(IP: in_name; VAR AB:AddrBlock); external; FUNCTION in_write(conn: IPCONN; p:PACKET; datalen:Integer; fhost: in_name):INTEGER; CONST CantReachHost = -2; VAR firsthop: in_name; pip: Ref_ip; len: integer; DestAddr: AddrBlock; err: OSerr; BEGIN IF (datalen > LBUFINIT) THEN BEGIN {$IFC DEBUG} WriteLn('IN_WRITE: Received a stupid packet length',datalen); { if BCBitAnd(NDEBUG,BUGHALT) THEN HALT; } {$ENDC} in_write := -1; exit(in_write); END; {$IFC DEBUG} Write('IP sending packet of length ',datalen:1, ' protocol ',conn^.c_prot,' to '); out_inaddr(fhost); WriteLn('.'); {$ENDC} { perform routing. Have to route on each and every packet going out because have to find first hop. } inroute(fhost, @firsthop); if (firsthop = 0) THEN BEGIN {$IFC DEBUG} Write('IN_WRITE: Couldn''t route packet to host '); out_inaddr(fhost); WriteLn('.'); {$ENDC} in_write := 0; exit(in_write); END; {$IFC DEBUG} Write('IP packet routed to host '); out_inaddr(firsthop); WriteLn('.'); {$ENDC} pip := in_head(p); pip^.ip_ver := Cur_IP_VER; pip^.ip_time := IP_TIME; pip^.ip_flgs := NO_IP_FLGS; pip^.ip_foff := Cur_IP_FOFF; pip^.ip_id := { bswap } (uid); uid := uid + 1; pip^.ip_chksum := FirstIPXSUM; pip^.ip_src := LocalIPAddress; pip^.ip_dest := fhost; len := BitSL(pip^.ip_ihl,2) + datalen; pip^.ip_len := { bswap } (len); pip^.ip_tsrv := 0; pip^.ip_prot := conn^.c_prot; { It's CHECKSUM time!! } pip^.ip_chksum := BitNOT(cksum(POINTER(ORD4(pip)), BitSL(pip^.ip_ihl,1))); ipsnd := ipsnd + 1; {$IFC DEBUG} WriteLn('About to call the network to send'); {$ENDC} IP2AB(firsthop,DestAddr); IF DestAddr.ANode = 0 THEN BEGIN in_write := CantReachHost; {$IFC DEBUG} WriteLn('in_write: Cannot reach host - IP2AB translation failed'); {$ENDC} exit(in_write); END; {$IFC DEBUG} WriteLn('in_write to node ',DestAddr.ANode:1); {$ENDC} { Wait until previous write finished } WHILE CurW_Hdl^^.abResult = 1 DO tk_yield; { Fill in the disk block } WITH CurW_Hdl^^ DO BEGIN ddpType := AB_IP; ddpSocket := AB_IP_Socket; ddpAddress := DestAddr; ddpReqCount := len; ddpDataPtr := p^.nb_buff; END; { And do the write } err := DDPWrite(CurW_Hdl,false,true); {$IFC DEBUG} WriteLn('in_write: exiting'); {$ENDC} if err = noErr then in_Write := 1 else in_write := -3; END; { end of in_write } FUNCTION inverify(p:Packet):Boolean; VAR pip:Ref_IP; csum: integer; { packet checksum } BEGIN pip := in_head(p); IF (p^.nb_len < { bswap } (pip^.ip_len)) THEN BEGIN {$IFC DEBUG} WriteLn('Packet length bad; dropped.' ); { in_dump(p); } {$ENDC} iplen := iplen + 1; inverify := FALSE; exit(inverify); END; if (pip^.ip_ver <> Cur_IP_VER) THEN BEGIN {$IFC DEBUG} WriteLn('Version number bad, packet dropped.'); { in_dump(p); } {$ENDC} ipver := ipver + 1; inverify := FALSE; exit(inverify); END; csum := pip^.ip_chksum; pip^.ip_chksum := 0; if (csum <> BitNOT(cksum(POINTER(ORD4(pip)),BitSL(pip^.ip_ihl,1)))) THEN BEGIN pip^.ip_chksum := csum; {$IFC DEBUG} WriteLn('Checksum bad, packet dropped.'); { in_dump(p); } {$ENDC} ipxsum := ipxsum + 1; inverify := FALSE; exit(inverify); END; if(pip^.ip_dest <> LocalIPaddr) THEN BEGIN {$IFC DEBUG} WriteLn('Received packet not for me.'); {$ENDC} inverify := FALSE; exit(inverify); END; if (pip^.ip_foff <> 0) OR Odd(pip^.ip_flgs) THEN BEGIN {$IFC DEBUG} WriteLn('Fragment received; dropped.'); { in_dump(p); } {$ENDC} ipfrag := ipfrag + 1; inverify := FALSE; exit(inverify); END; inverify := TRUE; END; { This is the internet demultiplexing routine. It handles packets received by the per-net task, verifies their headers and does the upcall to the whoever should receive the packet. All the guts of demultiplexing is in this piece of code. If the packet doesn't belong to anyone, this gets logged and the packet dropped. } PROCEDURE indemux(Fake:PTR); VAR pip: Ref_IP; { the internet header } conn: IPCONN; { an internet connection } i: Integer; Dummy: integer; { To hold value of ICMP_DESTUN } p: Packet; len:INTEGER; success:BOOLEAN; BEGIN CheckTask; { Problem with stacks? } WHILE true DO BEGIN if ip_queue.qHead = NIL then tk_block; { Wait for something to come } {$IFC DEBUG} WriteLn('Running: IP Demuxer'); {$ENDC} { Get a packet } p := POINTER(ORD4(ip_queue.qHead)); if p = NIL THEN BEGIN { Awakened w/o a packet! } ipwwop := ipwwop + 1; {$IFC DEBUG} WriteLn('Awakened w/o any packets to process '); {$ENDC} cycle; END; if dequeue(POINTER(ORD4(p)),@ip_queue) <> noErr then begin {$IFC DEBUG} WriteLn('dequeue failure (p = ',ORD4(p),')'); {$ENDC} cycle; end; {$IFC DEBUG} WriteLn('Processing a packet of length ',p^.nb_len); {$ENDC} iprcv := iprcv + 1; if NOT inverify(p) THEN BEGIN ipdrop := ipdrop + 1; in_free(p); cycle; end; pip := in_head(p); len := { bswap } (pip^.ip_len); { The packet is now verified; the header is correct. Now we have to demultiplex it among our internet connections. } {$IFC DEBUG} WriteLn('IP: Received packet of length ',len-FirstIPLEN:1, ' in protocol ',pip^.ip_prot:1); {$ENDC} success := false; FOR i := 0 to nipconns - 1 DO BEGIN conn := ipconns[i]; if (conn^.c_prot = pip^.ip_prot) THEN if (conn^.c_handle = NIL) THEN leave else BEGIN {$IFC DEBUG} WriteLn('handler found, delivering...'); {$ENDC} CALL3(POINTER(ORD4(p)), len - FirstIPLEN, { ??? } pip^.ip_src, conn^.c_handle); CheckTask; success := true; {$IFC DEBUG} WriteLn('finished executing IP handler'); {$ENDC} tk_yield; leave; END; END; if not success then begin { Didn't manage to demultiplex the packet. We should drop it and go away. } {$IFC DEBUG} WriteLn('Discarding pkt for unhandled protocol ',pip^.ip_prot:1); {$ENDC} Dummy := icmp_destun(pip^.ip_src, pip, DSTPROT); ipprot := ipprot + 1; ipdrop := ipdrop + 1; in_free(p); end; END; { end of infinite while } END; { End of indemux } PROCEDURE in_close; { This procedure shuts down the IP connection } VAR Status : Integer; BEGIN Status := DDPCloseSocket(AB_IP_Socket); END; { pretty print the statistics } FUNCTION lfailures:INTEGER; external; PROCEDURE in_stats; VAR s:STR255; dp:DialogPtr; iType:INTEGER; itemHndl:Handle; box:Rect; i: Integer; tmpP:Packet; PROCEDURE SetIt(item:INTEGER; number:INTEGER); BEGIN NumToStr(number,s); GetDItem(dp,item,iType,itemHndl,box); SetIText(itemHndl,s); END; BEGIN dp := GetNewDialog(77,NIL,POINTER(-1)); SetIt(1,iprcv); SetIt(2,ipsnd); SetIt(3,ipdrop); SetIt(4,ipxsum); SetIt(5,ipprot); SetIt(6,ipver); SetIt(7,iplen); SetIt(8,ipttl); SetIt(9,ipfrag); tmpP := POINTER(ORD4(freeq.qHead)); i := 0; while tmpP <> NIL do begin i := i+1; tmpP := tmpP^.nb_elt; end; SetIt(10,i); SetIt(11,lfailures); {$IFC DEBUG} Write('packet addresses: '); tmpP := POINTER(ORD4(freeq.qHead)); while tmpP <> NIL do begin Write(ORD4(tmpP),' '); tmpP := tmpP^.nb_elt; end; WriteLn(''); {$ENDC} MsgRegister(dp); END; {End of in_stats} { CROCK routines. output an internet address in pretty octal form } PROCEDURE cvt_inaddr(fhost: in_name; VAR s:STR255); VAR host:_ipname; s1,s2,s3,s4: STR255; begin host.in_lname := fhost; NumToString(BitAND(host.in_lst.in_net, 255),s1); NumToString(BitAND(host.in_lst.in_nets, 255),s2); NumToString(BitAND(host.in_lst.in_netss,255),s3); NumToString(BitAND(host.in_lst.in_host, 255),s4); s := concat(s1,'.',s2,'.',s3,'.',s4); end; {$IFC DEBUG} PROCEDURE out_inaddr(fhost: in_name); VAR host:_ipname; BEGIN host.in_lname := fhost; Write(BitAND(host.in_lst.in_net,255):1,'.', BitAND(host.in_lst.in_nets,255):1,'.', BitAND(host.in_lst.in_netss,255):1,'.', BitAND(host.in_lst.in_host,255):1); END; { End of out_inaddr } {$ENDC} END. !E!O!F! # # echo extracting net/ip_listen.text... cat >net/ip_listen.text <<'!E!O!F!' .Title 'IP Socket Listener' ; code for Appletalk IP socket listener ; written by Tim Maroney, C-MU, July 1985 ; modified 13 Sept 85 to eliminate use of A5 for global data (because of Switcher) ; please see the copyright notice in the file copyright/notice .nomacrolist .PAGE .INCLUDE -UPPER-TLASM-SYSEQU .INCLUDE -UPPER-TLASM-ATALKEQU .INCLUDE -UPPER-TLASM-TOOLEQU .INCLUDE -UPPER-TLASM-TOOLMACS AB_IP .EQU 22 ; DDP protocol number for IP AB_ARP .EQU 23 ; DDP protocol number for ARP IPsize .EQU 600 ; length of an IP packet buffer ARPSize .EQU 24 ; length of an ARP buffer AB_IP_Socket .EQU 72 ; static (sigh) IP socket number nb_len .EQU 14 ; offset of the length field in a packet nb_buff .EQU 6 ; offset of the buffer pointer in a packet nb_tstamp .EQU 10 ; offset of the time stamp field in a packet ; Here is the code for the socket listener. See the Appletalk Manager ; documentation to understand this strange routine. .PROC ip_listen .REF ARPbuffer,IPEnable,arp_wake,ip_wake,ARPAllowed .REF IPrdpkt,IPrdbuf,failcnt ; paranoia: make sure the packet is for our socket cmpi.b #AB_IP_Socket,d0 beq.s @1 bra recvfail @1 ; First we have to find out the protocol, so we can figure out where ; to store the packet. This requires figuring out whether this DDP ; header is long or short. lea toRHA(a2),a3 ; a3 := addr of top of RHA (LAP header) cmpi.b #shortDDP,lapType(a3) ; short DDP header? ; the addq may be used here beause an addq to an address register ; leaves the condition codes untouched addq.l #lapHdSz,a3 ; a3 := addr of DDP Header (RHA + 3) bne.s @2 ; if not, it's long move.b sDDPType(a3),d0 ; d0 := ddp protocol byte from short hdr bra.s @3 @2 move.b ddpType(a3),d0 ; d0 := ddp protocol byte from long hdr @3 ; d0 now contains the protocol ID from the DDP Header. cmpi.b #AB_IP,d0 ; is this a packet for IP? bne.s @4 ; if not, maybe for ARP ; IP packet handling lea IPrdbuf,a3 tst.l (a3) ; is there an ip buffer to read into? bne.s @5 ; if there is, continue clr.w d3 ; else signal ReadRest to throw away packet jsr 2(a4) ; ReadRest jsr IPEnable ; re-enable IP receive -- IPEnable will not work ; if called in a re-entrant way, so do this at the ; high interrupt priority lea failcnt,a0 addq.w #1,(a0) rts @5 movea.l IPrdpkt,a5 ; get packet address to store packet length move.w d1,nb_len(a5) ; IPrdpkt^.nb_len := d1 (pkt length from caller) move.w #IPsize,d3 ; size of buffer for ReadRest movea.l IPrdbuf,a3 ; get address of buffer arg to ReadRest jsr 2(a4) ; ReadRest ; disable reception of further packets for now lea IPrdbuf,a0 clr.l (a0) ; put the packet on the IP queue and wake up the IP demultiplexer lea IPrdpkt,a0 move.l (a0),-(sp) ; push arg for later ip_wake call jsr IPEnable ; can't be re-entrant move.w vSCCEnable(a2),sr ; decrease priority move.l (sp)+,a0 ; pop arg for ip_wake lea Ticks,a1 ; a1 := address of Ticks move.l (a1),nb_tstamp(a0) ; packet's timestamp := Ticks jsr ip_wake rts ; The packet wasn't for IP. It had better be for ARP or I'll take my ; ball and go home. @4 cmpi.b #AB_ARP,d0 ; is this a packet for ARP? beq.s @9 ; if so, continue to process it bra.s recvfail @9 ; ARP packet handling: it's simple because there's just one fixed buffer lea ARPAllowed,a3 tst.b (a3) ; is ARP allowed right now? bne.s @10 ; if not zero, continue bra.s recvfail @10 move.w #ARPsize,d3 ; size of ARP buffer for ReadRest lea ARPbuffer,a3 ; address of ARP buffer for ReadRest jsr 2(a4) ; call ReadRest tst.l d3 ; check return status from ReadRest beq.s @11 ; if not exactly ARPsize received, return rts @11 lea ARPAllowed,a0 clr.b (a0) ; disallow further ARP until processed ; by ARP task (which calls ARPEnable) move.w vSCCEnable(a2),sr ; lower priority for the wakeup jsr arp_wake ; wake up ARP handling task rts ; demultiplex failure -- read the rest of the packet into nowhere recvfail clr.w d3 ; signals ReadRest to throw away packet jsr 2(a4) ; ReadRest lea failcnt,a0 addq.w #1,(a0) rts ; ARPEnable: just set ARPAllowed to 1 .PROC ARPEnable .DEF ARPAllowed lea ARPAllowed,a0 move.b #1,(a0) rts ARPAllowed .BYTE ; 0 if ARP reception not allowed right now ; initialization routine for the socket listener, called from Pascal .PROC listeninit .DEF IPrdbuf,IPrdpkt,ipqaddr,dmxaddr,freeqaddr,failcnt,ATaskAddr .REF ipenable lea dmxaddr,a0 move.l 16(sp),(a0) lea ATaskAddr,a0 move.l 12(sp),(a0) lea ipqaddr,a0 move.l 8(sp),(a0) lea freeqaddr,a0 move.l 4(sp),(a0) lea failcnt,a0 clr.w (a0) jsr ipenable rts .align 2 failcnt .WORD ; count of number of failures IPrdbuf .LONG ; pointer to packet buffer for IP read IPrdpkt .LONG ; pointer to packet for IP read ipqaddr .LONG ; address of demuxer queue dmxaddr .LONG ; address of demuxer task freeqaddr .LONG ; address of free packet queue ATaskAddr .LONG ; address of ARP task .PROC arp_wake .REF ATaskAddr,tk_wake lea ATaskAddr,a0 move.l (a0),-(sp) jsr tk_wake rts ; ip_wake takes a single parameter, the address of the received packet, in a0 .PROC ip_wake .REF ipqaddr,dmxaddr,tk_wake lea ipqaddr,a1 ; queue arg to enqueue (ip_queue address) move.l (a1),a1 _Enqueue lea dmxaddr,a0 move.l (a0),-(sp) ; task arg to tk_wake (ipdemux address) jsr tk_wake rts ; ipenable was formerly a Pascal routine. It tries to enable the next read by ; pulling a packet off the free packet queue. .PROC ipenable .REF IPrdpkt,IPrdbuf,freeqaddr move.l a4,-(sp) ; save a4; used as local variable lea freeqaddr,a0 move.l (a0),a1 ; a1 := address of free queue tst.l qHead(a1) ; is free queue empty? beq.s nofree ; if so, go to nofree move.l qHead(a1),a0 ; element arg to dequeue move.l a0,a4 ; save free packet addr in a4 _Dequeue tst.w d0 ; did dequeue operation succeed? (d0 is status) bne.s nofree ; if not, go to nofree lea IPrdpkt,a0 move.l a4,(a0) lea IPrdbuf,a0 move.l nb_buff(a4),(a0) bra.s return nofree lea IPrdpkt,a0 ; clear packet address clr.l (a0) lea IPrdbuf,a0 ; clear buffer address clr.l (a0) return move.l (sp)+,a4 ; restore pushed a4 rts ; getarpbuf: returns the address of the ARP buffer, for use by Pascal code ; FUNCTION getarpbuf:ARP_PACKET; EXTERNAL; .FUNC getarpbuf .DEF ARPbuffer lea ARPbuffer,a0 move.l a0,4(sp) rts .align 2 ARPbuffer .block ARPsize ; buffer used for receiving ARP packets ; FUNCTION lfailures:INTEGER; .FUNC lfailures .REF failcnt lea failcnt,a0 move.w (a0),4(sp) rts .END !E!O!F! # # echo extracting net/name_host.text... cat >net/name_host.text <<'!E!O!F!' {$X-} {$M+} {$D-} {$R-} {$0V-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT NameHost; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-IP_Lib } IP_Lib; {$L+} FUNCTION NameRemoteHost(var name:STR255):Boolean; IMPLEMENTATION CONST FHostDialog = 31; OKbut = 1; CancelBut = 2; NameItem = 3; FUNCTION NameRemoteHost(var name:STR255):Boolean; VAR itemHit:INTEGER; MyDialog:DialogPtr; itemType:INTEGER; itemHndl:Handle; itemBox:Rect; BEGIN MyDialog := GetNewDialog(FHostDialog,NIL,POINTER(-1)); GetDItem(MyDialog,NameItem,itemType,itemHndl,itemBox); SetIText(itemHndl,DefaultHost); SelIText(MyDialog,NameItem,0,16000); ModalDialog(NIL,itemHit); if itemHit = 1 then GetIText(itemHndl,name); DisposDialog(MyDialog); NameRemoteHost := (itemHit = 1); END; END. !E!O!F! # # echo extracting net/name_user.text... cat >net/name_user.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT Name_User; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-err_lib } Err_Lib, {$U net-ip_lib } IP_Lib, {$U net-udp_lib } UDP_Lib; {$L+} CONST { Name user return codes } NAMETMO = 1 { Name user timed out on all requests }; NAMEUNKNOWN = 0 { Name not known }; FUNCTION udpname(var name: STR255): in_name; FUNCTION convert_name(var name: STR255): in_name; FUNCTION resolve_name (var name: STR255): in_name; IMPLEMENTATION CONST NAMESOCK = 42; { A Well Known Socket } NI_NAME = 1; NI_ADDR = 2; NI_ERR = 3; { This is the header for the nameserver stuff that sits on UDP. } TYPE Ref_nmitem = ^ nmitem; nmitem = PACKED RECORD { + 0 } nm_type: byte; { + 1 } nm_len: byte END; {$S } VAR nresp: Integer; { := 0 } { # of responses to name request } name_conns: ARRAY [0..4] OF UDPCONN; address: in_name; name_task: Ref_Task; { Resolve a host name into an internet address. Three name formats are * accepted: * 1) A character string host name * 2) An octal host number, in the form: * <net>,<subnet>,<rsd>,<host> * or a decimal host number, in the form: * <net>.<subnet>.<rsd>.<host> * Any of the <net>, <subnet>, and <rsd> may be left blank or left out * entirely; they default to the local net/subnet. * 3) A thirty-two bit hex number, preceeded by a '#', which is used * without interpretation as the host number. * If a character string name is supplied, it is first looked up in a * local host table. If it is not found there, the routine goes off to * internet name servers to try to resolve the name. * * The following routines are included in this file: * resolve_name Resolve a name as specified above * gethmch Parse a hex machine address specification * getomch Parse an octal machine address specification } CONST INSZ = 4; MYNET = 128; { CMU B class network numbers } MYSNET = 2; { " " " " } MYRSD = 0; { Empty subnet and host values} MYHOST = 0; TYPE ByteName = PACKED ARRAY[1..INSZ] OF BYTE; Addr_Type = PACKED RECORD CASE Integer OF 0: (bytes: ByteName); 1: (name: in_name); END; FUNCTION getomch(var name: STR255): in_name; forward; FUNCTION gethmch(var name: STR255): in_name; forward; FUNCTION getdmch(var name: STR255): in_name; forward; { Resolve foreign host internet address * Scan table of host names and nicknames. * For each name, see if our string is a prefix. If so, keep checking - * could be ambiguous. * If ambiguous, return 0. * When find no matches, try internet name servers. } { resolve_name resides in the blank segment. This is so it can unload the name service segment when it completes. It is an infrequently used protocol, and one not time-critical. -- Tim } {$S } FUNCTION resolve_name(var name: STR255): in_name; VAR result:in_name; BEGIN result := convert_name(name); if result = 0 then result := udpname(name); UnloadSeg(@udpname); resolve_name := result; END; FUNCTION convert_name(var name: STR255): in_name; VAR L,i,limit:Integer; flag:Boolean; BEGIN L := Length(name); if L = 0 THEN convert_name := 0 else if name[1] in ['0'..'9'] THEN BEGIN flag := false; if L < 8 then limit := L else limit := 8; for i := 2 to limit do begin if (name[i] = '.') then flag := true end; if flag THEN convert_name := getdmch(name) ELSE convert_name := getomch(name); END else if (name[1] = '#') OR (name[1] = '$') THEN convert_name := gethmch(name) else convert_name := 0; UnloadSeg(@gethmch); END; { convert_name } {$S UDPNameS } { Parse foreign host number input as hex string } FUNCTION gethmch(var name: STR255): in_name; VAR Result: LongInt; i: Integer; BEGIN IF NOT (Length(name) in [2..9]) THEN gethmch := 0 else begin Result := 0; for i := 2 to length(name) do if name[i] in ['0'..'9'] then Result := Result * 16 + (ord(name[i])-ord('0')) else if name[i] in ['a'..'f'] then Result := Result * 16 + (ord(name[i])-ord('a')+10) else if name[i] in ['A'..'F'] then Result := Result * 16 + (ord(name[i])-ord('A')+10) else Result := Result * 16; gethmch := Result; end; END; { gethmch } { Parse foreign host number input as octal string } FUNCTION getomch(var name: STR255): in_name; LABEL 8890, 9999; { Loop exits } VAR tmp: ByteName; i, j: Integer; n: Integer; addr: Addr_Type; StrCtr: Integer; LastFilled: Integer; BEGIN addr.bytes[1] := MYNET; addr.bytes[2] := MYSNET; addr.bytes[3] := MYRSD; addr.bytes[4] := MYHOST; StrCtr := 1; FOR i := 1 to INSZ DO BEGIN n := 0; WHILE (StrCtr <= Length(name)) AND (name[StrCtr] in ['0'..'7']) DO BEGIN n := (n * 8) + ORD(name[StrCtr]) - ORD('0'); if (n > 255) THEN BEGIN getomch := 0; EXIT(getomch); END; StrCtr := StrCtr + 1; END; tmp[i] := n; LastFilled := i; if StrCtr > length(name) THEN GOTO 8890; if name[StrCtr] = ',' THEN StrCtr := StrCtr + 1 ELSE BEGIN getomch := 0; EXIT(getomch); END; END; 8890: if StrCtr <= length(name) THEN BEGIN getomch := 0; EXIT(getomch); END; FOR j := 4 DOWNTO 1 DO BEGIN addr.bytes[j] := tmp[LastFilled]; LastFilled := LastFilled - 1; if LastFilled < 1 THEN GOTO 9999; END; 9999: getomch := addr.name; END; { getomch } { Parse foreign host number input as decimal string } FUNCTION getdmch(var name: STR255): in_name; LABEL 8890, 9999; { Loop exits } VAR tmp: ByteName; i, j: Integer; n: Integer; addr: Addr_Type; StrCtr: Integer; LastFilled: Integer; BEGIN addr.bytes[1] := MYNET; addr.bytes[2] := MYSNET; addr.bytes[3] := MYRSD; addr.bytes[4] := MYHOST; StrCtr := 1; FOR i := 1 to INSZ DO BEGIN n := 0; WHILE (StrCtr <= Length(name)) AND (name[StrCtr] in ['0'..'9']) DO BEGIN n := (n * 10) + ORD(name[StrCtr]) - ORD('0'); if (n > 255) THEN BEGIN getdmch := 0; EXIT(getdmch); END; StrCtr := StrCtr + 1; END; tmp[i] := n; LastFilled := i; if StrCtr > length(name) THEN GOTO 8890; if name[StrCtr] = '.' THEN StrCtr := StrCtr + 1 ELSE BEGIN getdmch := 0; EXIT(getdmch); END; END; 8890: if StrCtr <= length(name) THEN BEGIN getdmch := 0; EXIT(getdmch); END; FOR j := 4 DOWNTO 1 DO BEGIN addr.bytes[j] := tmp[LastFilled]; LastFilled := LastFilled - 1; if LastFilled < 1 THEN GOTO 9999; END; 9999: getdmch := addr.name; END; { This code implements a UDP name user compatible with the servers on Mit-Multics, Mit-XX and Mit-Spooler. } PROCEDURE name_rcv(p: PACKET; len: Integer; host: in_name; foo_data: PTR); forward; PROCEDURE name_wake(Dummy:PTR); BEGIN tk_wake(name_task); END; FUNCTION udpname(var name: STR255): in_name; LABEL 8888; CONST NameTimeout = 9; VAR len,i,Dummy:Integer; p:PACKET; s:PTR; tm:ref_Timer; BEGIN { Check the local table for a match } { Note: This code is missing: some day it should be added. } { grovel, grovel...check if the name is 'me'. If it is, special case it and use my net 0 ip address } if name = 'me' THEN BEGIN udpname := LocalIPaddr; EXIT(udpname); END; len := length(name); p := udp_alloc(len + 3, 0); if p = NIL THEN BEGIN CantAlloc(StrCvt('UDPNAME'),StrCvt('packet')); udpname := 0; EXIT(udpname); END; s := POINTER(ORD4(udp_data(udp_head(in_head(p))))); s^ := NI_NAME; s := POINTER(ORD4(s)+1); s^ := len; s := POINTER(ORD4(s)+1); for i := 1 to len do begin s^ := byte(name[i]); s := POINTER(ORD4(s)+1); end; s^ := 0; name_task := tk_cur; address := 0; nresp := 0; FOR i := 0 TO Num_Name_Servers DO BEGIN if i = Num_Name_Servers THEN BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,CBitOr(PROTERR,NETERR)) THEN WriteLn('UDP_NAME: Too many name servers!!'); if BCBitAnd(NDEBUG,BUGHALT) THEN BEGIN WriteLn('BUGHALT!'); HALT; END; {$ENDC} GOTO 8888; {break;} END; name_conns[i] := udp_open(NSIPAddress[i], NAMESOCK, udp_socket, @name_rcv, NIL); {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('NAME: Sending request to server '); out_inaddr( NSIPAddress[i] ); WriteLn('.'); END; {$ENDC} Dummy := udp_write(name_conns[i], p, len+3); END; { end of for loop } 8888: udp_free(p); tm := tm_alloc; if tm=NIL THEN BEGIN CantAlloc(StrCvt('UDPNAME'),StrCvt('timer')); udpname := 0; exit(udpname); END; tm_set(NameTIMEOUT, @name_wake, NIL, tm); tk_block; { Now one of two things should have happened: we should have received the resolved address or we should have timed out. If we've gotten the address, we should clear the timer. If we've timed out, we should just deallocate it. } Dummy := ORD(tm_clear(tm)); Dummy := ORD(tm_free(tm)); { Clean up the udp connections. } for i:=0 to Num_Name_Servers do udp_close(name_conns[i]); if (nresp = 0) THEN udpname := NAMETMO else udpname := address; END; PROCEDURE name_rcv{(p: PACKET; len: Integer; host: in_name; foo_data: PTR)}; VAR pnm: Ref_nmitem; pname:Ref_in_name; BEGIN nresp := nresp + 1; {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('NAME_RCV: Got response from foreign host '); out_inaddr(host); WriteLn('.'); END; {$ENDC} pnm := {(struct nmitem *)} POINTER(ORD4(udp_data(udp_head(in_head(p))))); pnm := {(struct nmitem *)} POINTER(ORD4(pnm) + pnm^.nm_len + 2); if (pnm^.nm_type = NI_ADDR) AND (address = 0) THEN BEGIN name_wake(NIL); pname := POINTER(ORD4(pnm)+2); address := pname^; END else BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('NAME_RCV: Name server '); out_inaddr(host); WriteLn(' couldn''t resolve the name.'); END; {$ENDC} if (nresp = Num_Name_Servers) THEN name_wake(NIL); END; udp_free(p); END { name_rcv }; END. !E!O!F! # # echo extracting net/task_asm.text... cat >net/task_asm.text <<'!E!O!F!' .Title 'Task Switching routines' ;__________________________________________________________________ ; ; Pascal interface to the Task Switching Routines ; ; Mark Sherman, July 1984 ; Version 0.0 ; ;__________________________________________________________________ .nomacrolist .PAGE ;___________________________________________________________ ; ; Definitions of Pascal data structure offsets ;___________________________________________________________ ; ; ; task = RECORD { a task - top of its stack } ; tk_sp: PTR; { task's current stack ptr } ; tk_nxt: Ref_Task; { pointer to next task } ; tk_size: Integer; { Size of allocated task } ; tk_unique: Ref_Task; { Unique Identification for collision } ; ev_flg: Task_State; { flag set if task is scheduled } ; tk_stack: packed array[0..0] of stack; { task's stack } ; END; ; .PROC tk_swtch,2 ;___________________________________________________________________________ ; ; tk_switch - Task Switch; swap the current task with the proposed task ; ; Call: Old_Task: Task to block ; New_Task: Task to start running ; ; ; PROCEDURE tk_swtch(Old_Task:Ref_Task;New_Task:Ref_Task); ; ; Stack setup upon entry: ; +0 .LONG Return address ; +4 .LONG Address of New_Task (+0 after popping RA) ; +8 .LONG Address of Old_Task (+4 after popping RA) ; ; Other stack perversity: ; When entering, all of the registers execpt for A0, A1 and D0 belong ; to the old task. These will be stacked on the old stack and replaced ; by values from the new stack. Hence a complete context shift is ; done, providing the caller of task switch with the environment from ; the last block of the task. (Note: by using the old stack, we ; will use the return address of that left by the task when it blocked ; by calling tk_block. Similarly, the current task's stack will be ; saved for its later restarting. Also see tk_fram below ; ;_____________________________________________________________________________ ; STOP #10; ; Drop into debugger LEA SaveRA,A0 MOVE.L (SP)+,(A0) ; Save the return address MOVEA.L SP,A1 ; Hold on to old SP for Args ; Save all of the registers -- who ; knows what will be munged MOVEM.L D3/D4/D5/D6/D7/A2/A3/A3/A4/A5/A6,-(SP) MOVE.L 4(A1),A0 ; A0 -> TCB for old task MOVE.L SP,(A0) ; Save current SP in TCB (start of rec) MOVE.L (A1),A0 ; A0 -> TCB for new task MOVE.L (A0),SP ; Restore the stack pointer ; Put back the registers MOVEM.L (SP)+,D3/D4/D5/D6/D7/A2/A3/A3/A4/A5/A6 ADD.L #8,SP ; Pop the stack of arguments ; More trickery: we need return address MOVEA.L SaveRA,A1 ; from old stack! JMP (A1) ; Back to tk_blk ! .align 2 SaveRA .LONG ; ; ; .PROC tk_frame,4 .REF _cdump ;___________________________________________________________________________ ; ; tk_frame - Setup a frame as if a task had called tk_block ; ; Call: PTCB: Pointer to a task control block ; StackSize: Number of bytes pointed at by PTCB ; Proc_Start: Starting address of task ; Proc_Arg: Pointer argument for task ; ; Returns: Filled in task control block for use in tk_block ; ; PROCEDURE tk_frame (PTCB:Ref_Task; StackSize: LongInt; ; Proc_Start: ProcPtr; Proc_Arg: PTR); ; ; Stack setup upon entry: ; +0 .LONG Return address ; +4 .LONG Pointer value for Proc_Arg ; +8 .LONG Address of task code ; +12 .LONG Number of bytes in TCB + Stack ; +16 .LONG Pointer to the TCB -- TCB values at bottom, stack at top ; and grows downward. Disaster is stack grows into TCB. ; ; To properly look like this task had called tk_block (and hence is ; returning), there must be a stack frame for tk_block. Further, since ; the return from tk_block will cause the procedure to start, its ; parameter must also be available. ; ; What we are trying to simulate is the situation where a call to ; tk_block has been made, followed by a call to tk_swtch. The simulation ; of these calls (up till the actual task switch) is ; Push args to tk_block = NOP (no arguments) ; JSR to tk_block = PUSH return address = start address of task ; LINK A6,Locals => Push saved A6, Assign SP to A6, alloc locals ; (Note: this saved A6 will become SP upon return of tk_block) ; Push args to tk_swtch (PUSH Oldtask, Push NewTask) ; JSR to tkswicth = PUSH return address ; POP R.A. inside of tk_swtch ; PUSH Saved registers (note the value of A6 and A5!) ; Save the SP at this point ; ; ;Hence the stack we must build is: ; ; ; parameter for task (since task thinks it is being called!) ; return address for "caller" of task = error handling routine ; parameters to tk_block to be popped ; R.A for tk_block = starting address of task ; A6-> linked value of "old" A6 (nonexistant value) ; local variables of tk_block ; parameters to tk_swtch (only to be popped here) ; saved registers from inside of tk_swtch ; (save SP must go inside of TCB) ; ;_____________________________________________________________________________ ; STOP #12 BlockLocs .Equ 20 ; Max space taken by tk_block's locals LEA OldA6,A0 MOVE.L A6,(A0) ; Save it for later -- we will clobber 6 MOVE.L 16(SP),A0 ; A0 -> TCB ADD.L 12(SP),A0 ; A0 -> top of Stack (grows down) ; So now we start pushing everything ; on the stack denoted by A0 MOVE.L 4(SP),-(A0) ; Push the task's parameter LEA _cdump,A1 ; Ret. Addr. of "caller" of the task MOVE.L A1,-(A0) ; NOP ; Nothing - no parameters to tk_block yt MOVE.L 8(SP),-(A0) ; Push the return address for tk_block = ; starting address of task MOVE.L A1,-(A0) ; A fake A6 linkage register to UNLNK ; This is what stack pointer will be ; loaded with !!!! (See above) MOVEA.L A0,A6 ; Here is fake linkage !!! SUBA.L #BlockLocs,A0 ; Allocate tk_block locals (some value) MOVE.L A1,-(A0) ; Fake Old_Task_Ptr MOVE.L A1,-(A0) ; Fake New_Task_Ptr ; Put in plenty of registers to restore MOVEM.L D3/D4/D5/D6/D7/A2/A3/A3/A4/A5/A6,-(A0) ; ; All done setting up stack, so save the stack pointer into TCB ; MOVE.L 16(SP),A1 ; A1 -> TCB MOVE.L A0,(A1) ; Place SP, i.e., A0, into TCB DoneIt MOVE.L (SP)+,A0 ; Get return address of tk_frame ADDA.L #16,SP ; Pop off the parameters MOVEA.L OldA6,A6 ; Restore the frame pointer JMP (A0) ; return to called of tk_frame .align 2 OldA6 .LONG ;___________________________________________________________________________ ; ; stk_init - Stack initialization for tasking ; ; Call: Size: maximum amount of stack space required by main prog ; ; ; PROCEDURE stk_init(size:Integer); ; ; Stack setup upon entry: ; +0 .LONG Return address ; +4 .WORD Number of bytes to allocate beyond current stack ; ; ;____________________________________________________________________________ .PROC stk_init,1 .DEF GlobStk MOVE.L SP,D0 ; Save the stack pointer MOVE.L (SP)+,A0 ; Save the return address CLR.L D1 ; Clear out the whole word MOVE.W (SP)+,D1 ; Get the length SUB.L D1,D0 ; Allocate the hypothetical stack ptr (D0) LEA GlobStk,A1 ; Just for the store. Sigh. MOVE.L D0,(A1) ; Save the magic stack ptr JMP (A0) ; And return .align 2 GlobStk .LONG ;___________________________________________________________________________ ; ; stk_alloc -- allocate a stack for a task ; ; Call: Size: number of bytes to allocate ; ; Returns: A Pointer to the lowest address (bottom ) of the stack ; ; ; FUNCTION stk_alloc(size: Integer):PTR; ; ; Stack setup upon entry: ; +0 .LONG Return address ; +4 .WORD Number of bytes to provide ; +6 .LONG Address of stack block of storage (return value) ; ; ; ;_____________________________________________________________________________ .FUNC stk_alloc,1 .REF GlobStk MOVE.L (SP)+,A0 ; Save the return address CLR.L D0 ; Clear out register for count MOVE.W (SP)+,D0 ; Get the count ; STOP #8 LEA GlobStk,A1 ; Get a ptr to the variable for next inst SUB.L D0,(A1) ; Advanced the stack MOVE.L GlobStk,(SP) ; And return the "bottom" of the stack JMP (A0) ; And return ; utility routine to read the stack pointer. .FUNC GetSP,0 MOVE.L (SP)+,A0 ; save the r.a MOVEA.L SP,A1 ; save the sp MOVE.L A1,(SP) ; give back the sp JMP (A0) ; return .END !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting net/task_lib.text... cat >net/task_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT Task_Lib; { Please note the copyright notice in the file "copyright/notice" } { This file contains the routines which implement the Macintosh multitasking * system. Only the (hopefully) machine-independent routines are included * herein; the machine dependent routines are in the file * net-task_asm. The following routines are included: * tk_init initializes the tasking system and creates the first task * tk_fork creates additional tasks * tk_block blocks the current, goes through the circular list * of tasks task in round robbin order until it finds * one that is awake and starts it running * tk_wake awakens a task by seting its event flag * * Tasks are allocated dynamically as needed; they may * be of any size but their size is fixed once they are allocated. They are * then uniquely and permanantly associate with a task control block. * * Tasks form a circular list that is strung together by pointers (tk_nxt). * Currently tasks are placed in the list when they are created and they never * change their position. * Tasks have three states: blocked, awake, and running. The running task is * the task that is currently executing. Only one task runs at any given time * and its event flag (ev_flg) determines whether it will be blocked or awake * after it finishes running. If a task is not running, it is awake if its * event flag is set, and it is blocked if its event flag is not set. * tk_block resets a task's event flag just before as it starts to run. * A running task may thus awaken itself. A task gives up control of the * processor by calling tk_block. Tk_block used the tk_nxt pointer of the * task that is giving up control, to find the next task in the circular list. * If this next task is awake, tk_block will set it running. If it is blocked, * tk_block will use its tk_nxt pointer to find the next element in the * circular list. If it is awake, tk_block will set it running. If not,...etc. * The currently running task is identified by the global variable tk_cur. } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U net-err_lib } Err_Lib; {$L+} CONST MaxTaskName = 8; { Maximum characters in a task name } TYPE stack = byte; { type of data in task stacks } Ref_Stack = ^ Stack; Ref_Task = ^ task; Task_State = (Blocked, Awake, Running); Task_Name = String[MaxTaskName]; { For all but the initial task, the TCB below sits at the very bottom of allocated stack space for the task. Since the stack grows downward, a stack that grows too large will clobber its TCB } task = RECORD { a task - top of its stack } tk_sp: PTR; { task's current stack ptr } tk_nxt: Ref_Task; { pointer to next task } tk_size: Integer; { Size of allocated task } tk_unique: Ref_Task; { Unique Identification for collision } ev_flg: Task_State; { flag set if task is scheduled } tk_stack: packed array[0..0] of stack; { task's stack } END; VAR Main_Task: Ref_Task; { main task - always around } tk_cur: Ref_Task; { a pointer to the current task } TDEBUG: Integer { := 0 }; { DEBUG flag } PROCEDURE tk_wake(tk:Ref_Task); PROCEDURE tk_yield; FUNCTION tk_init(stksiz:Integer): Ref_Task; FUNCTION tk_fork(prev_tk:Ref_Task; start:ProcPtr; stksiz: Integer; name:Task_Name; arg:Ptr): Ref_Task; PROCEDURE tk_block; PROCEDURE tk_exit; PROCEDURE CheckTask; PROCEDURE _cdump; { This makes the decl external for the assembly routine } IMPLEMENTATION { Names are kept in a separate table so they can be retrieved when tasks overflow. If they are kept in the TCB, they will be overwritten at overflow. Here we declare the table and the routines which manipulate it. } CONST MAXTASKS = 16; TYPE tk_name_entry = packed record theTCB:Ref_task; theName:Task_Name; inUse:Boolean end; VAR nametable: array [1..MAXTASKS] of tk_name_entry; PROCEDURE initnames; VAR i:INTEGER; BEGIN for i := 1 to MAXTASKS do nametable[i].inUse := false; END; PROCEDURE nametask(tk:Ref_task; name:Task_Name); VAR i:INTEGER; BEGIN i := 1; while (i <= MAXTASKS) & (nametable[i].inUse) do i := i + 1; if i <= MAXTASKS then begin nametable[i].theTCB := tk; nametable[i].theName := name; nametable[i].inUse := true; end; END; FUNCTION getname(tk:Ref_task):StringPtr; VAR i:INTEGER; BEGIN for i := 1 to MAXTASKS do if nametable[i].inUse then if nametable[i].theTCB = tk then begin getname := StrCvt(nametable[i].theName); exit(getname); end; getname := NIL; END; PROCEDURE delname(tk:Ref_task); VAR i:INTEGER; BEGIN for i := 1 to MAXTASKS do if nametable[i].inUse then if nametable[i].theTCB = tk then begin nametable[i].inUse := false; exit(delname); end; END; CONST AlertResource = 666; VAR death: Boolean { := false }; { Claim that a task should be killed } died: Ref_Task; { Task that died } Next_tk : Ref_Task; { the next task to run during scheduling} { This is global to minimize stack munging } Free_Task_List: Ref_Task; { Where tasks that died have their storage placed } FirstTask: Task; FUNCTION GetSP: Ptr; External; PROCEDURE tk_alert(tk:Ref_Task); forward; { Two assembly language routines for munging the stack } PROCEDURE tk_frame (PTCB:Ref_Task; StackSize: LongInt; Proc_Start: ProcPtr; Proc_Arg: PTR);EXTERNAL; PROCEDURE tk_swtch(Old_Task:Ref_Task;New_Task:Ref_Task); EXTERNAL; { STK_INIT(size) is essentially something else... } PROCEDURE stk_init(size:Integer); EXTERNAL; { STK_ALLOC(size) is sort of what it is. } FUNCTION stk_alloc(size: Integer):PTR; EXTERNAL; { Initialize the tasking system. Create the first task, and set its stack pointer to the main program stack. The first task will always use the main program stack, even though tk_init sets aside space for a stack of size stksiz.(?!) The circular list of tasks contains only the original task, so originally set its next task pointer to point to itself. This routine returns to the caller with a pointer to the first task. } {$S InitSeg } FUNCTION tk_init(stksiz:Integer): Ref_Task; VAR tk : Ref_Task; { pointer to the first task } Actual_Size: Integer; BEGIN { size of the stack that is never used--in bytes } { CouldAlert(AlertResource); } { Need task alert box in memory } { Initialize the globals } TDEBUG := 0; { DEBUG flag } death := false ; { No task should be killed } Free_Task_List := NIL; { No killed tasks on the list yet } Actual_Size := stksiz + sizeof(task); IF Odd(Actual_Size) THEN Actual_Size := Actual_Size + 1; { Now allocate the stack space for the main program to use } { Although the TCB is not really used here, we leave it just in case this stack space is reused. } Stk_Init(Actual_Size); { create the first task } tk := @FirstTask; tk_cur := tk; { It is the currently running task } tk^.ev_flg := Blocked; { Since it is running it does not } { need to be awakened, but it will } { sleep when it blocks unless it } { resets its flag. } initnames; nametask(tk,'Main'); { tk^.tk_name := 'Main'; } tk^.tk_Size := Actual_Size; tk^.tk_unique := @FirstTask; tk^.tk_nxt := tk; { It is the next task since it is } { the only task. } tk_init := tk; { Return the initial task } END; {$S } PROCEDURE CheckTask; { This checks for task over flow } BEGIN IF tk_cur <> @FirstTask THEN IF (ORD4(GetSP) <= ORD4(tk_cur)) OR (tk_cur <> tk_cur^.tk_Unique) THEN tk_alert(tk_cur); END; PROCEDURE tk_wake(tk:Ref_Task); BEGIN tk^.ev_flg := Awake; END; PROCEDURE tk_yield; BEGIN tk_wake(tk_cur); tk_block; END; { Create a new task with stksiz bytes of stack, and place it in the circular list of tasks after prev_tk. Awaken it so that it will run, and set it up so that when it does run, it will start runing routine start. This routine does not affect the execution of the currently running task. It returns a pointer to the new task. } FUNCTION tk_fork(prev_tk:Ref_Task; start:ProcPtr; stksiz: Integer; name:Task_Name; arg:Ptr): Ref_Task; {task *prev_tk;} { predecessor to the new task } {int (*start) ();} { Where the new task starts execution. } {int stksiz;} { The size of the stack of the new task. } {char *name;} { The task's name as a string } {unsigned arg;} { argument to the task } VAR tk: Ref_Task; { a pointer to the new task } TempTK, OTemp: Ref_Task;{ For checking free task list } size: integer; { size of the new task } BEGIN size := stksiz + sizeof(task); IF Odd(size) THEN size := size + 1; { create the new task } { Either reuse some storage from before or create a new block } TempTK := Free_Task_List; OTemp := NIL; tk := NIL; WHILE (TempTK <> NIL) AND (tk = NIL) DO IF TempTK^.tk_Size >= Size THEN BEGIN { Found a block that was large enough -- any fit strategy } tk := TempTK; Size := TempTK^.tk_Size; { Don't lose any space } IF OTemp = NIL THEN Free_Task_List := TempTK^.tk_nxt ELSE OTemp^.tk_nxt := TempTK^.tk_nxt; END ELSE BEGIN OTemp := TempTK; TempTK := TempTK^.tk_Nxt; END; IF tk = NIL THEN tk := { (task *)} POINTER(ORD4(stk_alloc (size))) ; { set it up to run } tk_frame (tk, size, start, arg); tk^.ev_flg := Awake; { Schedule the task to run. } tk^.tk_nxt := prev_tk^.tk_nxt; { Fit it in after prev_tk. } prev_tk^.tk_nxt := tk; { tk^.tk_name := name; } { Set its name } nametask(tk,name); tk^.tk_Size := size; { Set its size } tk^.tk_unique := tk; { Set its unique ID } {$IFC DEBUG} Write('Task ',name,' has TCB at '); WriteLong(ORD4(tk));WriteLn(''); {$ENDC} tk_fork := tk; { Return ptr to TCB } END; { Block the currently running task and run the next task in the circular list of tasks that is awake. Before returning, see if any cleanup has to be done for another task. } PROCEDURE tk_block; BEGIN CheckTask; { Make sure we are still OK ! } Next_tk := tk_cur; { Get the current task and block it } {$IFC DEBUG} IF (TDEBUG <> 0) THEN WriteLn('TASK: Task blocking: ', getname(tk_cur)^); {$ENDC} REPEAT Next_tk := Next_tk^.tk_nxt; IF Next_tk <> Next_tk^.tk_Unique THEN tk_alert(tk_cur); UNTIL (Next_tk^.ev_flg <> Blocked); Next_tk^.ev_flg := Blocked; { Reset its event flag before it runs } if Next_tk <> tk_Cur THEN tk_swtch (tk_cur,Next_tk); { Run the next task. } tk_cur := Next_tk; {$IFC DEBUG} IF (TDEBUG <> 0) THEN WriteLn('TASK: Task now running: ', getname(tk_cur)^); {$ENDC} IF death THEN BEGIN { free up the task } death := FALSE; { cfree(POINTER(ORD4(died))); } {Can't free stack in the Mac, so instead we just keep a list of the old stack space for possible reuse } died^.tk_Nxt := Free_Task_List; Free_Task_List := died; END; END; { end of tk_block } { tk_exit() : destroy the current task. Accomplished by setting a flag indicating that an exit has occured and then entering the scheduler loop. When tk_block() returns for some other task and finds this flag set, it deallocates the task which exited. This is nry because we still need a stack to run on. The task removes itself from the circular list of tasks in the system so that it cannot be awoken after it has exited. Otherwise, the exit might be done in the context of the task itself, which would prove disastrous. Yes, this routine never returns (not really). } PROCEDURE tk_exit; VAR tk: Ref_Task; BEGIN { hunt for the task which tk_cur is the successor of } tk := tk_cur; WHILE (tk^.tk_nxt <> tk_cur) DO tk := tk^.tk_nxt; { now patch around tk_cur } tk^.tk_nxt := tk_cur^.tk_nxt; death := TRUE; died := tk_cur; delname(tk_cur); tk_block; Fatal(StrCvt('Disaster: tk_exit() returning!!!'),false); END; { end of tk_exit } PROCEDURE _cdump; BEGIN Fatal(StrCvt(concat('Task ',getname(tk_cur)^,' is trying to return')),false); END; { end of _cdump } PROCEDURE tk_alert(tk:Ref_Task); VAR dummy:INTEGER; SPStr,TCBStr:STR255; BEGIN NumToString(ORD4(GetSP),SPStr); NumToString(ORD4(tk_cur),TCBStr); ParamText(SPStr,TCBStr,getname(tk)^,''); dummy := StopAlert(AlertResource,NIL); reboot; END; END. !E!O!F! # # echo extracting net/term_lib.text... cat >net/term_lib.text <<'!E!O!F!' {$X-} {$M+} {$R+} {$0V-} {$D+} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL VT102} {$SETC VT102 := false} UNIT Term_Lib; { The emulation code is taken from, and modified to Pascal from, the source for Macintosh Kermit... which is: Copyright (c) 1985, Trustees of Columbia University, New York. Non-commercial use permitted with this notice. } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf; PROCEDURE em_init; PROCEDURE em_reset; PROCEDURE em(c:char); PROCEDURE EmStr(s:STR255); PROCEDURE EmLn(s:STR255); PROCEDURE em_input(eptr:PTR; VAR s:str255); PROCEDURE em_flush; PROCEDURE IoUpdate; PROCEDURE IoIdle; PROCEDURE IoActivate(modifiers:INTEGER); PROCEDURE IoClick(where: INTEGER; pt: Point; modifiers: INTEGER); VAR myWindow: windowptr; { the window we're emulating in... } IMPLEMENTATION CONST RETURN = chr($d); { carriage return } LINEFEED = chr($a); { linefeed } C_L = chr($c); { Control-L } MAXLIN = 24; { Number of lines of text on screen } MAXCOL = 80; { Number of chars across screen } NUMTABS = 9; { max number of tabstops on line } LINEHEIGHT = 12; { pixels height per line of text in window } TOPMARGIN = 3; BOTTOMMARGIN = 301; { (LINEHEIGHT * MAXLIN + TOPMARGIN) } LEFTMARGIN = 3; LINEADJ = 3; { amount of char below baseline for row } RIGHTMARGIN = 483; { (CHARWIDTH * MAXCOL + LEFTMARGIN) } CF_OUTC = 0; { character type flags } CF_SESC = 1; CF_MESC = 2; CF_TOSS = 3; TYPE sparm = array [0..5] of SignedByte; VAR scroll: rect; lastblink: LongInt; bmargin: Integer; nxtlin: array [0..MAXLIN-1] of Integer; { Linked list of lines } botlin: Integer; scrtop, scrbot: Integer; tabstops: array[0..NUMTABS-1] of integer; insert, { insert or overwrite text? } autowrap: Boolean; { autowrap on exceed right margin? } scr: array[0..MAXLIN-1] of packed array[0..MAXCOL-1] of char; toplin: integer; { offset of top of screen from top of array } abslin: integer; curlin, curcol: integer; { cursor position } savlin, savcol: integer; { saved cursor position } charflg: integer; invmode: Boolean; {reverse video mode} textstyle: style; prvchr: char; width: integer; { width of characters } outbuf: packed array[0..MAXCOL] of char; outcnt,outcol: Integer; dumptr: RgnHandle; num1, num2: integer; { numeric version of parameters } numone, numtwo: sparm; cursor_on: boolean; { cursor currently drawn } numptr: Ptr; {$S Terminal} procedure makerect( var r: rect; lin, col, numlin, numcol: integer ); { make a rectangle given the passed boundaries } begin r.top := lin * LINEHEIGHT + TOPMARGIN; r.left := col * width + LEFTMARGIN; r.bottom := r.top + numlin * LINEHEIGHT; r.right := r.left + numcol * width; end; PROCEDURE flushbuf; var r:rect; e:EventRecord; begin if outcnt = 0 then exit(flushbuf); makerect(r,abslin,outcol,1,outcnt); if invmode then FillRect(r,black) else EraseRect(r); DrawText(@outbuf,0,outcnt); outcnt := 0; ObscureCursor; end; PROCEDURE buf_char(c:char); begin if outcnt = 0 then outcol := curcol; outbuf[outcnt] := c; outcnt := outcnt + 1; end; FUNCTION fndrel(linum:Integer):Integer; VAR lin,i:Integer; begin lin := toplin; for i := 0 to linum - 1 do lin := nxtlin[lin]; fndrel := lin; end; FUNCTION fndabs(linum:Integer):Integer; VAR i,lin:Integer; BEGIN lin := toplin; i := 0; while lin <> linum do begin i := i + 1; lin := nxtlin[lin]; end; fndabs := i; END; FUNCTION fndprv(linum:Integer):Integer; VAR lin:Integer; BEGIN lin := toplin; while nxtlin[lin] <> linum do lin := nxtlin[lin]; fndprv := lin; END; procedure relmove( hor, ver: integer ); { move a relative number of lines and chars. Both can be negative } begin Move(hor*width,ver*LINEHEIGHT); abslin:= abslin + ver; curcol:= curcol + hor; curlin:= fndrel(abslin); end; procedure absmove( hor, ver: integer ); { move to absolute row and column as specified. } begin MoveTo(hor*width+LEFTMARGIN,(ver+1)*LINEHEIGHT+TOPMARGIN-LINEADJ); abslin := ver; curcol := hor; curlin := fndrel(ver); end; procedure zeroline( n: integer ); { wipe specified line of the RAM version of the screen to all spaces } var index: integer; begin for index := 0 to MAXCOL-1 do scr[n, index] := ' '; end; { terminal operations } procedure cursor_erase; begin Line(-width,0); cursor_on := false; end; procedure cursor_draw; begin Line(width,0); cursor_on := true; end; procedure back_space; begin if (curcol > 0) then relmove(-1,0); end; procedure carriage_return; begin absmove(0, abslin); end; procedure rev_line_feed; VAR tbot, ttop, tlout: Integer; begin if curlin = scrtop then begin scrollrect(scroll,0,lineheight,dumptr); zeroline(scrbot); tbot := scrbot; ttop := scrtop; tlout := nxtlin[scrbot]; nxtlin[scrbot] := scrtop; scrtop := scrbot; scrbot := fndprv(scrbot); if ttop = toplin then toplin := scrtop else nxtlin[fndprv(ttop)] := scrtop; if tbot = botlin then begin botlin := scrbot; nxtlin[botlin] := -1; end else nxtlin[scrbot] := tlout; curlin := scrtop; end else relmove(0,-1); end; procedure line_feed; VAR tbot, ttop, tlout: Integer; begin if curlin = scrbot then begin scrollrect(scroll,0,-lineheight,dumptr); zeroline(scrtop); tbot := scrbot; ttop := scrtop; tlout := nxtlin[scrbot]; nxtlin[scrbot] := scrtop; scrbot := scrtop; scrtop := nxtlin[scrtop]; if ttop = toplin then toplin := scrtop else nxtlin[fndprv(ttop)] := scrtop; if tbot = botlin then begin botlin := scrbot; nxtlin[botlin] := -1; end else nxtlin[scrbot] := tlout; curlin := scrbot; end else relmove(0,1); end; procedure insert_char; VAR i:Integer; r:Rect; begin makerect(r,abslin,curcol,1,MAXCOL-curcol); ScrollRect(r,width,0,dumptr); for i := MAXCOL-1 downto curcol+2 do scr[abslin,i-1] := scr[abslin,i]; scr[abslin,curcol] := ' '; end; procedure erase_char; { Erase the current character location to the background color } var r: rect; begin scr[curlin,curcol] := ' '; makerect(r,abslin,curcol,1,1); if invmode then FillRect(r,black) else EraseRect(r); end; procedure tab; var i: integer; begin for i := 0 to NUMTABS - 1 do begin if (tabstops[i] > curcol) then begin absmove(tabstops[i],abslin); leave; end; end; end; procedure bell; begin sysbeep(3); end; procedure cursor_save; begin savcol := curcol; savlin := abslin; end; procedure cursor_restore; begin absmove(savcol,savlin); end; procedure query_terminal; { respond to a request from the host querying our terminal.... } VAR err:OSErr; begin {$IFC VT102} err := PostEvent(keyDown,27{escape}); err := PostEvent(keyDown,ord('[')); err := PostEvent(keyDown,ord('?')); err := PostEvent(keyDown,ord('6')); err := PostEvent(keyDown,ord('c')); {$ENDC} end; procedure insert_mode; begin if (prvchr <> '?') & (num1 = 4) then insert := true; end; procedure end_insert_mode; begin if (prvchr <> '?') & (num1 = 4) then insert := false; end; procedure up; begin if num1 = 0 then num1 := 1; relmove(0,-num1); end; procedure down; begin if num1 = 0 then num1 := 1; relmove(0,num1); end; procedure left; begin if num1 = 0 then num1 := 1; relmove(-num1,0); end; procedure right; begin if num1 = 0 then num1 := 1; relmove(num1,0); end; procedure cursor_position; begin num1 := num1 - 1; if num1 < 0 then num1 := 0; num2 := num2 - 1; if num2 < 0 then num2 := 0; absmove(num2,num1); end; procedure clear_screen; { clear the entire screen to nada.... } var index: integer; r: rect; begin makerect(r,0,0,MAXLIN,MAXCOL); eraserect(r); for index := 0 to MAXLIN - 1 do zeroline(index); end; procedure clear_line; var i: integer; r: rect; begin case num1 of 0: begin { here to the right } makerect(r,abslin,curcol,1,maxcol-curcol); for i:= curcol to maxcol - 1 do scr[curlin,i] := ' '; end; 1: begin { left to here } makerect(r,abslin,0,1,curcol+1); for i:= 0 to curcol do scr[curlin,i] := ' '; end; 2: begin { entire line } makerect(r,abslin,0,1,maxcol); zeroline(curlin); end; end; eraserect(r); end; procedure erase_display; var i: integer; r: rect; begin case num1 of 0: begin clear_line; makerect(r,abslin+1,0,maxlin-abslin,maxcol); eraserect(r); for i:= abslin+1 to maxlin-1 do zeroline(fndrel(i)); end; 1: begin clear_line; makerect(r,0,0,abslin,maxcol); eraserect(r); for i:= 0 to abslin do zeroline(fndrel(i)); end; 2: clear_screen; end; end; {$IFC VT102} procedure insert_line; { not part of VT100 code, only VT102 } VAR r:rect; begin if num1 = 0 then num1 := 1; makerect(r,abslin,0,0,MAXCOL); r.bottom := bmargin; if num1 > fndabs(scrbot) - abslin then num1 := fndabs(scrbot) - abslin; ScrollRect(r,0,num1*LINEHEIGHT,dumptr); { Do the book keeping!!! } end; procedure delete_line; { not part of VT100 code, only VT102 } VAR r:rect; begin if num1 = 0 then num1 := 1; makerect(r,abslin,0,0,MAXCOL); r.bottom := bmargin; if num1 > fndabs(scrbot) - abslin then num1 := fndabs(scrbot) - abslin; ScrollRect(r,0,-num1*LINEHEIGHT,dumptr); { Do the book keeping!!! } end; {$ENDC} procedure delete_char; var i: integer; r: rect; begin if num1 = 0 then num1 := 1; makerect(r,abslin,curcol,1,maxcol-curcol); if (num1 > maxcol-curcol-1) then num1 := maxcol-curcol-1; scrollrect(r,-width*num1,0,dumptr); { scroll the little sucker out... } for i := curcol to (maxcol-num1)-1 do scr[abslin,i]:= scr[abslin,i+num1]; while i< maxcol do begin scr[abslin,i]:= ' '; i:= i+1; end; end; procedure text_mode; begin case num1 of 0: begin invmode := false; textstyle := []; textface([]); textmode(srcor); end; 1: begin textstyle := textstyle + [bold]; textface(textstyle); end; 4: begin textstyle := textstyle + [underline]; textface(textstyle); end; 7: begin invmode := true; textmode(srcbic); end; 22: if bold in textstyle then begin textstyle:= textstyle - [bold]; textface(textstyle); end; 24: if underline in textstyle then begin textstyle := textstyle - [underline]; textface(textstyle); end; 27: begin invmode := false; textmode(srcor); end; end; end; procedure home_cursor; begin absmove(0,0); end; procedure set_scroll_region; begin num1 := num1 - 1; if num1 < 0 then num1 := 0; { make top of line (prev line) } if num2 = 0 then num2 := 24; { zero means whole screen } scroll.top := (num1 * LINEHEIGHT) + TOPMARGIN; scroll.bottom := (num2 * LINEHEIGHT) + TOPMARGIN; bmargin := scroll.bottom; scrtop := fndrel(num1); scrbot := fndrel(num2 - 1); home_cursor; end; { parsing routines } procedure do_ctrl( ch: char ); { perform operation corresponding to the passed control character } begin case ord(ch) of 7: bell; 8: back_space; 9: tab; 10, 11, 12: line_feed; 13: carriage_return; 27: charflg := CF_SESC; end; end; procedure multi_char; { initialize state for multi-character escape sequence } begin numone[0] := 0; numone[1] := 0; numtwo[0] := 0; numtwo[1] := 0; numptr := @numone; prvchr := chr(0); charflg := CF_MESC; end; procedure do_sesc( ch: char ); { given a single char, assume it is the second half of a simple escape sequence and call the appropriate processing routine. } begin case ord(ch) of 35, 40, 41: charflg := CF_TOSS; 55: cursor_save; 56: cursor_restore; 69: line_feed; 77: rev_line_feed; 90: query_terminal; 91: multi_char; end; end; procedure do_mesc( ch: char ); { given a single char, and up to two numeric parameters, perform the requested multi-character escape sequence } begin case ord(ch) of 65: up; 66: down; 67: right; 68: left; 72: cursor_position; 74: erase_display; 75: clear_line; {$IFC VT102} 76: insert_line; { not part of VT100 } 77: delete_line; { not part of VT100 } {$ENDC} 80: delete_char; 99: query_terminal; 102: cursor_position; 104: insert_mode; 108: end_insert_mode; 109: text_mode; 114: set_scroll_region; end; end; function str_to_int( s: sparm ): integer; var result: integer; index: integer; begin result := 0; index := 0; while s[index] <> 0 do begin if (s[index] >= ord('0')) & (s[index] <= ord('9')) then result := (result * 10) + (s[index] - ord('0')) else if s[index] = ord('-') then result := -result; index := index + 1; end; str_to_int := result; end; procedure MDrawChar( ch: char ); { actually draw or otherwise handle the char passed to us. Update all relevent pointers } begin if (ch < ' ') then begin flushbuf; do_ctrl(ch); end else if ( ord(ch) < 128 ) then begin if (curcol >= MAXCOL) then begin { do we need to handle end of line? } if autowrap then begin { if so, do we want to autowrap... } flushbuf; carriage_return; line_feed; end else begin { ...or just stick on the right margin? } back_space; if outcnt > 0 then outcnt := outcnt - 1; end; end; if insert then begin { are we in insert mode? } insert_char; erase_char; DrawChar(ch); end else buf_char(ch); { save char in RAM array for update later } scr[curlin,curcol] := ch; curcol:= curcol+1; end; end; procedure printit( ch: char ); begin ch := chr(BitAnd(ord(ch),$7f)); case charflg of CF_OUTC: MDrawChar(ch); { just output the char } CF_SESC: begin { handle single-char escape sequence } charflg := CF_OUTC; do_sesc(ch); end; CF_MESC: { multi-char escape sequences } if (ord(ch) >= 32) & (ord(ch) < 64) then begin if (ch >= '<') & (ch <= '?') then prvchr := ch else if ((ch >= '0')&(ch <= '9')) | (ch = '-') | (ch = '+') then begin numptr^ := ORD(ch); numptr := POINTER(ORD4(numptr)+1); numptr^ := 0; end else if (ch = ';') then numptr := @numtwo; end else begin { we've finished sequence, so } { convert strings numone and numtwo to numbers num1 and num2 } num1 := str_to_int(numone); num2 := str_to_int(numtwo); do_mesc(ch); charflg:= CF_OUTC; end; CF_TOSS: charflg := CF_OUTC; { just ignore this char and go on } end; end; { framework routines } PROCEDURE em_reset; BEGIN numptr := @numone; textsize(9); textmode(srcOr); textstyle := []; textface(textstyle); outcnt := 0; invmode := false; insert := false; lastblink := TickCount; charflg := CF_OUTC; { initialize to normal char outputting } scroll.left := LEFTMARGIN; scroll.right := RIGHTMARGIN; scroll.top := TOPMARGIN; scroll.bottom := (MAXLIN * LINEHEIGHT) + TOPMARGIN; bmargin := scroll.bottom; scrtop := toplin; scrbot := botlin; END; PROCEDURE em_init; var index: integer; FontNumber: Integer; bounds: Rect; i: Integer; begin GetFNum('monaco',FontNumber); bounds.top := 40; bounds.left := 4; bounds.bottom := 340; bounds.right := 508; myWindow := newwindow(nil,bounds,concat('MacTELNET of ',COMPDATE),true, 4,pointer(-1),false,0); setport(myWindow); textfont(fontnumber); textsize(9); textmode(srcOr); textstyle := []; textface(textstyle); dumptr := NewRgn; PenMode(patXor); width := charwidth('W'); toplin := 0; botlin := MAXLIN - 1; for i := 0 to MAXLIN-1 do nxtlin[i] := i + 1; nxtlin[botlin] := -1; autowrap := true; em_reset; for index := 0 to NUMTABS-1 do tabstops[index] := 8*(index+1); for index := 0 to MAXLIN-1 do zeroline(index); clear_screen; home_cursor; cursor_save; cursor_on := false; end; PROCEDURE em(c:char); { Write character to terminal window } begin if cursor_on then cursor_erase; printit(c); end; PROCEDURE EmStr(s:STR255); { Output a string of chars to screen } VAR i: INTEGER; begin if cursor_on then cursor_erase; for i := 1 to length(s) do printit(s[i]); end; PROCEDURE em_flush; { makes sure all pending output was done } BEGIN if cursor_on then cursor_erase; flushbuf; END; PROCEDURE EmLn(s:STR255); { Output passed string followed by a carriage return and linefeed } begin EmStr(s); flushbuf; carriage_return; line_feed; end; { event handling routines } PROCEDURE em_input(eptr:PTR; VAR s:str255); { handle a keypress from the user } VAR e:^EventRecord; c:char; BEGIN e := POINTER(ORD4(eptr)); { For now each character only maps to one character } s := 'X'; {makes it have one character length } c := chr(e^.message MOD 128); if (BitAND(e^.modifiers,cmdKey) <> 0) & (c >= '@') then begin if (c >= 'a') & (c <= 'z') then c := chr(ord(c) - $20); { convert to upper } s[1] := chr(ord(c) - $40) { extract control character } end else if (c = '`') then begin if (BitAND(e^.modifiers,cmdKey) = 0) then s[1] := chr(27) { escape } else s[1] := c; end else if (c = ' ') then begin if (BitAND(e^.modifiers,cmdKey) = 0) then s[1] := ' ' else s[1] := chr(0); { command-space equals null } end else s[1] := c; { normal char } END; PROCEDURE IoUpdate; var i,lin: integer; { redraw the screen in response to an update event by writing out the contents of our RAM image of the screen to the physical screen... } BEGIN BeginUpdate(myWindow); lin := toplin; for i := 0 to MAXLIN-1 do begin MoveTo(LEFTMARGIN,(i+1)*LINEHEIGHT+TOPMARGIN-LINEADJ); DrawText(@scr[lin],0,MAXCOL); lin := nxtlin[lin]; end; MoveTo(curcol*width + LEFTMARGIN, (abslin+1)*LINEHEIGHT + TOPMARGIN - LINEADJ); cursor_draw; EndUpdate(myWindow); END; PROCEDURE IoIdle; { called repeatedly when nothing is happening... use to blink cursor, etc. } BEGIN flushbuf; if TickCount - lastblink >= GetCaretTime then begin if not cursor_on then cursor_draw else cursor_erase; lastblink := TickCount; end; END; PROCEDURE IoActivate(modifiers:INTEGER); BEGIN END; PROCEDURE IoClick(where: INTEGER; pt: Point; modifiers: INTEGER); { gets called when some fool presses the button on the mouse.... } BEGIN END; END. { of unit } !E!O!F! # # echo extracting net/tftp_defs.text... cat >net/tftp_defs.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT TFTP_Defs; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-err_lib } Err_Lib, {$U net-IP_Lib } IP_Lib, {$U net-UDP_Lib } UDP_Lib; {$L+} CONST { TFTP randoms } GET = 10 { GET file from other host to here }; PUT = 11 { PUT file on other host }; ASCII = 1 { transfer as netascii }; IMAGE = 2 { transfer as image }; TEST = 3 { test mode - diskless }; OCTET = 4 { octet mode - same as image }; MACINTOSH = 5; { Macintosh mode - exchange Finder info } { TFTP error codes } ERRTXT = 0 { see the enclosed text }; FNOTFOUND = 1 { file not found }; ACCESS = 2 { access violation }; DISKFULL = 3 { don't even ask. }; ILLTFTP = 4 { illegal TFTP operation }; BADTID = 5 { unkown transfer ID }; FEXISTS = 6 { file already exists }; NOUSER = 7 { no such user }; { TFTP opcodes } RRQ = 1 { read request }; WRQ = 2 { write request }; DATA = 3 { data packet }; ACK = 4 { acknowledgement packet }; ERRPCK = 5 { error packet }; TYPE tf_FilePart = ( tf_DataPart, tf_RsrcPart, tf_FindPart); { The TFTP connection structure. Contains connection info,and data for timeout calculations. Also, I have added header information for Mac queues so the connections can be kept on tfconnq } Ref_TFconn = ^ tfconn; tfconn = RECORD tf_next:Ref_TFconn; { queue pointer to next element } tf_qtype:QTypes; { second part of queue header } tf_udp: UDPCONN; { udp connection for this transfer } tf_outp: PACKET; { last sent packet } tf_lastlen: integer; { length of last sent pkt } tf_expected: integer; { most recently processed block } tf_fport: integer; { foreign port } tf_task: Ref_task; { main task for tftp connection } tf_tm: Ref_timer; { our timer } tf_state: integer; { state of connection } tf_done:ProcPTR; { Procedure to call on close } tf_tries: integer; { # of retries already done } tf_mode: integer; { mode := IMAGE, [net]ASCII, ... } tf_dir: integer; { direction of the transfer } tf_size: LongInt; { # of bytes transferred } tf_rcv: integer; { # of packets received } tf_snt: integer; { # of packets sent } tf_ous: integer; { # of out of sequence packets } tf_tmo: integer; { # of timeouts } tf_rsnd: integer; { # of resends } tf_trt: LongInt; { round trip time } tf_rt: LongInt; { current timeout } tf_NR: Byte; { number rexmissions of this pkt } tf_NR_last: Byte; { ' ' ' of prev pkt } tf_K: Byte; { tuning constant } tf_SAWCR: Boolean; { did last packet end in CR? } tf_sent: LongInt; { time that pkt was sent } tf_PB: ParmBlkPtr; { parameter block for I/O } tf_fp: tf_FilePart; { Which part of Macintosh } tf_volume: Integer; { volume identifier of file } tf_fn: STR255; { name of the file - what a storage waste } END; { Generic TFTP Packet } tfpacket = PACKED RECORD tf_op: Integer; { op code } tf_block: Integer; { Block of type dependent data } END; Ref_tftp = ^ tfpacket; VAR ntftps: Byte; refusedt: LongInt; { time of most recent transfer refusal } FUNCTION tftp_head(p:PACKET):Ref_TFTP; IMPLEMENTATION {$S TFTPSeg} FUNCTION tftp_head(p:PACKET):Ref_TFTP; BEGIN tftp_head := POINTER(ORD4(udp_data(udp_head(in_head(p))))); END; { tftp_head } END. !E!O!F! # # echo extracting net/tftp_file.text... cat >net/tftp_file.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL BUNDLE} {$SETC BUNDLE := true} {$DECL ALLOCT} {$SETC ALLOCT := false} UNIT TFTP_File; { Please note the copyright notice in the file "copyright/notice" } { TFTP_LIB module using UDP over the Applebus, file operations } { by Tim Maroney (C-MU) } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-err_lib } Err_Lib, {$U net-IP_Lib } IP_Lib, {$U net-UDP_Lib } UDP_Lib, {$U net-calls } Call_Lib, {$U net-tftp_defs } TFTP_Defs; {$L+} FUNCTION ForkZero(cn:Ref_tfconn):BOOLEAN; procedure CStr2PStr(PC: PTR; PS: StringPtr); procedure PStr2CStr(PS: StringPtr; PC:Ptr); PROCEDURE ScratchIt(cn:Ref_tfconn; VAR OSStatus:OSErr); FUNCTION OpenOK(cn:Ref_tfconn; OSStatus:OSErr; p:PACKET):BOOLEAN; FUNCTION MacPart(cn:Ref_tfconn; expected:INTEGER):Boolean; PROCEDURE tfudperr(udpc: UDPCONN; p: PACKET; code:Integer; text: StringPtr); {$IFC BUNDLE} PROCEDURE setbundle(fn:StringPtr; vol:integer); {$ENDC} IMPLEMENTATION {$S TFTPSeg } FUNCTION ForkZero(cn:Ref_tfconn):BOOLEAN; VAR OSStatus: OSErr; BEGIN OSStatus := PBGetEOF(cn^.tf_PB,FALSE); IF OSStatus <> noERR THEN ForkZero := TRUE ELSE ForkZero := (ORD4(cn^.tf_PB^.ioMisc) = 0); END; procedure CStr2PStr(PC: PTR; PS: StringPtr); { Take a pointer to a C string and make it into a Pascal string } VAR Size: 0..255; ZeroPtr: PTR; BEGIN ZeroPtr := PC; FOR Size := 0 TO 255 DO IF ZeroPtr^ = 0 THEN BEGIN { Found end of C string, now copy it } BlockMove(PC,POINTER(ORD4(PS)+1),Size); ZeroPtr := POINTER(ORD4(PS)); ZeroPtr^ := Size; EXIT(CStr2PStr); END ELSE ZeroPtr := POINTER(ORD4(ZeroPtr) + 1); { Advance the C ptr } { Did not find a null byte !} PS^ := ''; END; procedure PStr2CStr(PS: StringPtr; PC:Ptr); VAR TempPtr: PTR; BEGIN TempPtr := POINTER(ORD4(PS)+1); BlockMove(TempPtr,PC,Length(PS^)); PC := POINTER(ORD4(PC)+Length(PS^)); PC^ := 0; { Place a null byte at end of string } END; PROCEDURE ScratchIt(cn:Ref_tfconn; VAR OSStatus:OSErr); BEGIN IF OSStatus = NoErr THEN begin { Already there, see if we can delete it } OSStatus := PBClose(cn^.tf_PB,FALSE); if OSStatus <> noErr then exit(ScratchIt); cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; OSStatus := PBDelete(cn^.tf_PB,FALSE); if OSStatus <> noErr then exit(ScratchIt); OSStatus := fnfErr end; IF OSStatus = fnfErr THEN BEGIN { Hm, name not present, let's create it } cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; OSStatus := PBCreate(cn^.tf_PB,FALSE); IF OSStatus = noErr THEN BEGIN { Have to set the finder info } cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioFlCrDat := TickCount; cn^.tf_PB^.ioFlMdDat := TickCount; cn^.tf_PB^.ioFlFndrInfo.fdCreator := '????'; cn^.tf_PB^.ioFlFndrInfo.fdFlags := 0; cn^.tf_PB^.ioFlFndrInfo.fdFldr := fDisk; { fdLocation is irrelevant; will be inited by Finder } if cn^.tf_fp = tf_RsrcPart then cn^.tf_PB^.ioFlFndrInfo.fdType := 'APPL' else cn^.tf_PB^.ioFlFndrInfo.fdType := 'TEXT'; OSStatus := PBSetFInfo(cn^.tf_PB,FALSE); END; IF OSStatus = noErr THEN BEGIN { Created it, now open it } cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioPermssn := fsRdWrPerm; cn^.tf_PB^.ioMisc := NIL; IF cn^.tf_fp = tf_DataPart THEN OSStatus := PBOpen(cn^.tf_PB,FALSE) ELSE OSStatus := PBOpenRF(cn^.tf_PB,FALSE); END; END; { end of fnfErr} { If everything is still OK, set mark to beginning of file } IF OSStatus = noErr THEN BEGIN cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB,FALSE); END; END; { of Scratch It } FUNCTION OpenOK(cn:Ref_tfconn; OSStatus:OSErr; p:PACKET):BOOLEAN; VAR LongDummy:LONGINT; BEGIN if (OSStatus <> noErr) THEN BEGIN FOpenErr(StrCvt(cn^.tf_fn),OSStatus); refusedt := TickCount; {$IFC ALLOCT} Write('OpenOK: '); {$ENDC} if p <> NIL then udp_free(p); ntftps := ntftps - 1; OpenOK := false; END ELSE OpenOK := true; END; { OpenOK } { Mac mode transfers involve three file parts. This is managed by checking EOF's in Mac mode. If there is an EOF and this isn't yet the data part, MacPart is called to switch to the next part. } FUNCTION MacPart(cn:Ref_tfconn; expected:INTEGER):Boolean; VAR OSStatus:OSErr; retval:Boolean; BEGIN cn^.tf_expected := expected; if (cn^.tf_fp = tf_RsrcPart) then begin {$IFC DEBUG} WriteLn('MacPart: changing to data fork'); {$ENDC} { Open file descriptor with data fork } OSStatus := PBClose(cn^.tf_PB,FALSE); cn^.tf_fp := tf_DataPart; cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioMisc := NIL; if cn^.tf_dir = GET then BEGIN cn^.tf_PB^.ioPermssn := fsRdWrPerm; OSStatus := PBOpen(cn^.tf_PB,FALSE); END ELSE BEGIN cn^.tf_PB^.ioPermssn := fsRdPerm; OSStatus := PBOpen(cn^.tf_PB,FALSE); IF OSStatus = noErr THEN BEGIN cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB,FALSE); END; END; retval := OpenOK(cn,OSStatus,NIL); if not retval then tfudperr(cn^.tf_udp, cn^.tf_outp, FNOTFOUND, StrCvt('Could not change to data fork')); MacPart := retval; end else if (cn^.tf_fp = tf_FindPart) then begin { Open file descriptor with resource file } {$IFC DEBUG} WriteLn('MacPart: changing to resource fork'); {$ENDC} cn^.tf_fp := tf_RsrcPart; cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioMisc := NIL; if cn^.tf_dir = GET then BEGIN cn^.tf_PB^.ioPermssn := fsRdWrPerm; OSStatus := PBOpenRF(cn^.tf_PB, FALSE); END else BEGIN cn^.tf_PB^.ioPermssn := fsRdPerm; OSStatus := PBOpenRF(cn^.tf_PB, FALSE); IF OSStatus = noErr THEN BEGIN cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB,FALSE); END; END; retval := OpenOK(cn,OSStatus,NIL); if not retval then tfudperr(cn^.tf_udp, cn^.tf_outp, FNOTFOUND, StrCvt('Could not change to resource fork')); MacPart := retval; end; END; {$IFC BUNDLE} { If this file has a bundle in its resource fork, then set the bundle bit } PROCEDURE setbundle(fn:StringPtr; vol:integer); VAR info:FInfo; refnum,count1,count2:integer; h:handle; o:^OSType; begin {$IFC DEBUG} WriteLn('setbundle: called on ',fn^,', volume ',vol:1); {$ENDC} count1 := CountResources('BNDL'); if setvol(NIL,vol) <> noErr then exit(setbundle); refnum := OpenResFile(fn^); if refnum = -1 then exit(setbundle); count2 := CountResources('BNDL'); if count2 > count1 then { there's a bundle in this file } begin h := GetIndResource('BNDL',1); if (h = NIL) | (GetFInfo(fn^,vol,info) <> noErr) then begin CloseResFile(refnum); exit(setbundle); end; o := POINTER(ORD4(h^)); info.fdCreator := o^; info.fdFlags := BitOR(info.fdFlags,fHasBundle); if SetFInfo(fn^,vol,info) <> noErr then begin CloseResFile(refnum); exit(setbundle); end; { TODO: Open the desk top and remove the version data resource } end; CloseResFile(refnum); end; {$ENDC} PROCEDURE tfudperr(udpc: UDPCONN; p: PACKET; code:Integer; text: StringPtr); { error packet definitions } CONST tf_err_offset = 4; TYPE tferr = PACKED RECORD { +0 } tf_op: integer; { would be 5 } { +2 } tf_code: integer; { +4 } tf_err: packed array[0:0] of byte; END; Ref_tferr = ^ tferr; VAR len: integer; perr: ^tferr; dummy: integer; FakeStr: StringPtr; BEGIN len := 4; perr := { (struct tferr *) } POINTER(ORD4(tftp_head(p))); perr^.tf_op := { bswap} { No byte swap on 68000 } (ERRPCK); perr^.tf_code := { bswap } { No bte swap on 68000 } (code); if(code=ERRTXT) THEN BEGIN FakeStr := Text; PStr2CStr( text, { @perr^.tf_err } POINTER(ORD4(perr) + tf_err_offset)); len := len + length(text^)+1; { 1 for the null byte } END else BEGIN CASE code OF 0: FakeStr := StrCvt('The JNC Memorial BUGHALT'); 1: FakeStr := StrCvt('File not found'); 2: FakeStr := StrCvt('Access violation'); 3: FakeStr := StrCvt('Disk full'); 4: FakeStr := StrCvt('Illegal TFTP operation'); 5: FakeStr := StrCvt('Unknown transfer ID'); 6: FakeStr := StrCvt('File already exists'); 7: FakeStr := StrCvt('No such user'); otherwise FakeStr := StrCvt('Unknown error'); END; { end of CASE } PStr2CStr( FakeStr, { @perr^.tf_err } POINTER(ORD4(perr) + tf_err_offset)); len := len + length(FakeStr^)+1; { 1 for the null byte } END; {$IFC DEBUG} WriteLn('TFTP: Sending error packet, ',code,' <',FakeStr^,'>'); {$ENDC} dummy := udp_write(udpc, p, len); END; { End of tfudperr } END. !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting net/timer_lib.text... cat >net/timer_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} UNIT Timer_Lib; { Please note the copyright notice in the file "copyright/notice" } { This file contains the declarations for the timer management package. } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf; {$L+} CONST TPS = 60; { Clock ticks per second } TIMERHIWATER = 30; { number of free timers to keep } TYPE Ref_timer = ^ timer; { This is an extended version of the Mac vertical retrace queue format } timer = PACKED RECORD { a timer } tm_qLink:Ref_timer; { next element in vertical retrace queue } tm_qType:INTEGER; { will be ORD(vType) } tm_vblAddr:ProcPtr; { points to tm_PSHARG, below } tm_vblCount:INTEGER; { timer frequency } tm_vblPhase:INTEGER; { timer phase } tm_tmLink:Ref_timer; { secondary link, for appl. timers only } { The following is 68000 machine code } tm_PSHA5:INTEGER; { MOVE.L A5,-(SP) } tm_LEAA0:INTEGER; { LEA [word immediate],A0 } tm_CURA5:INTEGER; { system global CurrentA5 (word addr) } tm_GETA5:INTEGER; { MOVE.L (A0),A5 } tm_PSHARG:INTEGER; { MOVE.L [long immediate],-(SP) } tm_arg:Ptr; { argument to pass to subroutine } tm_JSR:INTEGER; { JSR [long immediate] } tm_subr: ProcPtr; { timer subroutine to call } tm_POPA5:INTEGER; { MOVE.L (SP)+,A5 } tm_RTS:INTEGER { RTS } END; time_q = packed record { the queue of free timers } qFlags: Integer; qHead: Ref_timer; qTail: Ref_timer end; { This file contains the routines which make up the routines for setting * and clearing timers. * The following routines are included in this package: * tm_set set a timer to fire after number of seconds * tm_mset set a timer, argument in milliseconds * tm_tset set a timer, argument in clock ticks * tm_reset reset a timer to go off at a different time * tm_clear clear a previously set timer * tm_main the main routine of the timer task * tm_init init the timer system } { Addition of tm_tset and tm_mset, 12/83. <J.H. Saltzer> } VAR TIMERDEBUG: Boolean; PROCEDURE tm_set(nsecs:LongInt; subr:PROCPTR; arg:PTR; tm: Ref_Timer); PROCEDURE tm_mset(msecs:LongInt; subr:PROCPTR; arg:PTR; tm:Ref_Timer); PROCEDURE tm_tset (nticks:LongInt; subr:PROCPTR; arg:PTR; tm:Ref_Timer); FUNCTION tm_reset (nsecs: LongInt; tm: Ref_Timer): Boolean; FUNCTION tm_clear(tm: Ref_Timer): Boolean; PROCEDURE tm_init; FUNCTION tm_alloc:Ref_Timer; FUNCTION tm_free(t: Ref_Timer): Boolean; PROCEDURE tm_allFree; IMPLEMENTATION CONST RTS = $4E75; { instruction format of 68000 RTS } PSHARG = $2F3C; { MOVE.L [next longword],-(SP) } JSR = $4EB9; { JSR [next longword] } PSHA5 = $2F0D; { MOVE.L A%,-(SP) } LEAA0 = $41F8; { LEA [next word],A0 } CURA5 = $0904; { location of system global CurrentA5 } POPA5 = $2A5F; { MOVE.L (SP)+,A5 } GETA5 = $2A50; { MOVE.L (A0),A5 } { Internal variables } VAR freetmq: time_q; { queue of free timers } allTimers:Ref_timer; { list of timers allocated } { Initialize the timer package. } {$S InitSeg } PROCEDURE tm_init; BEGIN TIMERDEBUG := false; freetmq.qFlags := 0; { queue of free timers } freetmq.qHead := NIL; freetmq.qTail := NIL; allTimers := NIL; END; { tm_init } {$S } { Set a timer to go off after nticks clock ticks. When the timer goes * off, call the specified subroutine with the specified argument. * This routine runs in the context of the caller's task; * it just enqueues the timer. } PROCEDURE tm_tset(nticks:LongInt; { timer expiration time } subr:PROCPTR; { subroutine to call on expiration } arg:PTR; { arg to pass to subr. } tm:Ref_Timer); { place to return timer id } VAR Dummy: OSErr; BEGIN {$IFC DEBUG} IF TIMERDEBUG THEN BEGIN WriteLn('TM_SET: setting timer ',ORD4(tm), ' for ',nticks,' ticks.'); END; {$ENDC} { make sure not already queued. } Dummy := VRemove(POINTER(ORD4(tm))); tm^.tm_qLink := NIL; { no next element } tm^.tm_vblCount := nticks; { timer frequency } tm^.tm_vblPhase := 0; { timer phase } tm^.tm_vblAddr := POINTER(ORD4(tm)+18); { addr of procedure } tm^.tm_PSHA5 := PSHA5; tm^.tm_LEAA0 := LEAA0; tm^.tm_CURA5 := CURA5; tm^.tm_GETA5 := GETA5; tm^.tm_PSHARG := PSHARG; tm^.tm_arg := arg; { argument to pass } tm^.tm_JSR := JSR; tm^.tm_subr := subr; { subroutine to call } tm^.tm_POPA5 := POPA5; tm^.tm_RTS := RTS; Dummy := VInstall(POINTER(ORD4(tm))); END; { end of tm_tset() } { Reset a (running) timer to go off in nsecs seconds * instead of at the time it is currently set for. If in fact the * timer is not already set, return FALSE; otherwise return TRUE. * Does not modify the upcall in the timer. } FUNCTION tm_reset(nsecs: LongInt; tm: Ref_Timer): Boolean; VAR expired: Boolean; BEGIN expired := (tm^.tm_vblCount = 0); { if NOT expired THEN expired := (VRemove(POINTER(ORD4(tm))) <> noErr); } if expired THEN BEGIN {$IFC DEBUG} IF TIMERDEBUG THEN WriteLn('TIMER_RESET: timer already expired.'); {$ENDC} tm_reset := False; exit(tm_reset); { timer expired, give up } END; {$IFC DEBUG} IF TIMERDEBUG THEN BEGIN WriteLn('TIMER_RESET: timer reset for ',nsecs,' seconds.'); END; {$ENDC} tm^.tm_qLink := NIL; { no next element } tm^.tm_vblCount := nsecs*TPS; { timer expiration time } tm_reset := (VInstall(POINTER(ORD4(tm))) = noErr); END; { end of tm_reset() } { set timer in seconds } PROCEDURE tm_set(nsecs:LongInt; subr:PROCPTR; arg:PTR; tm: Ref_Timer); BEGIN tm_tset(nsecs*TPS, subr, arg, tm); END; { end of tm_set } { set timer in milliseconds } PROCEDURE tm_mset(msecs:LongInt; subr:PROCPTR; arg:PTR; tm:Ref_Timer); BEGIN tm_tset (((msecs*TPS) DIV 1000), subr, arg, tm) ; END; { end of tm_mset } { Clear the timer specified by the passed timer identifier. The timer * identifier gives a pointer to the timer to be cleared. * Free the timer's storage * (into the free list up to TIMERHIWATER elements). * Returns FALSE if the specified timer was not found in the queue, * TRUE otherwise. } FUNCTION tm_clear(tm: Ref_Timer): Boolean; BEGIN IF (tm^.tm_vblCount = 0) THEN BEGIN {$IFC DEBUG} IF TIMERDEBUG THEN BEGIN WriteLn('TIMERCLEAR: timer ',ORD4(tm),' already expired.'); END; {$ENDC} tm_clear := FALSE; exit(tm_clear); END; {$IFC DEBUG} IF TIMERDEBUG THEN BEGIN WriteLn('TIMERCLEAR: clearing timer ',ORD4(tm)); END; {$ENDC} { tm^.tm_vblCount := 0; } if VRemove(POINTER(ORD4(tm))) <> noErr THEN tm_clear := FALSE else tm_clear := TRUE; END; { end of tm_clear(); } { Allocate a timer and return a pointer to it } FUNCTION tm_alloc:Ref_Timer; VAR t: Ref_timer; sz:Size; Dummy:OSErr; BEGIN t := POINTER(ORD4(freetmq.qHead)); if t <> NIL then Dummy := dequeue(POINTER(ORD4(t)),@freetmq) else BEGIN sz := sizeof(timer); t := {(timer *)} POINTER(ORD4(NewPtr(sz))); if t = NIL THEN BEGIN tm_alloc := NIL; exit(tm_alloc); END; END; t^.tm_qLink := NIL; t^.tm_qType := ORD(vType); t^.tm_vblCount := 0; t^.tm_tmLink := allTimers; allTimers := t; tm_alloc := t; END; { Free up a timer. Returns true if successful, false otherwise } FUNCTION tm_free(t: Ref_Timer): Boolean; VAR tmp:Ref_timer; l:Integer; Dummy:OSErr; flag:Boolean; BEGIN { Check if the timer is enqueued } if VRemove(POINTER(ORD4(t))) = noErr THEN begin {$IFC DEBUG} WriteLn('Tried to free active timer.'); {$ENDC} Dummy := VInstall(POINTER(ORD4(t))); tm_free := false; exit(tm_free); end; { Have to remove it from allTimers } if allTimers = t then allTimers := t^.tm_tmLink else begin tmp := allTimers; flag := true; while flag and (tmp^.tm_tmLink <> NIL) do begin if tmp^.tm_tmLink = t then begin tmp^.tm_tmLink := t^.tm_tmLink; flag := false; end else tmp := tmp^.tm_tmLink; end; end; l := 0; tmp := freetmq.qHead; while tmp <> NIL do begin l := l+1; tmp := tmp^.tm_qLink; end; if l < TIMERHIWATER THEN enqueue(POINTER(ORD4(t)),@freetmq) else DisposPtr(POINTER(ORD4(t))); tm_free := TRUE; END; { tm_free } { Free all timers from the vertical retrace queue -- done at application death } PROCEDURE tm_allFree; VAR Dummy:OSErr; tm:Ref_timer; BEGIN tm := allTimers; while tm <> NIL do begin Dummy := VRemove(POINTER(ORD4(tm)));{ doesn't matter if it's there or not } tm := tm^.tm_tmLink; end; allTimers := NIL; END; END. { End of Timer Library } !E!O!F! # # echo extracting net/tn_lib.text... cat >net/tn_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL LISTEN} {$SETC LISTEN := true} UNIT TN_Lib; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-err_lib } Err_Lib, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-ip_lib } IP_Lib, {$U net-tcp_lib } TCP_Lib, {$U net-udp_lib } UDP_Lib, {$U net-name_user } Name_User, {$U net-tftp_defs } TFTP_Defs, {$U net-tftp_lib } TFTP_Lib, {$U net-calls } Call_Lib, {$U net-name_host } NameHost, {$U net-term_lib } Term_Lib; {$L+} { Definitions for telnet } CONST TNTKSZ = 3072; TYPE ucb = record u_state:Integer; { XESTAB or XCLOSING } u_tcpfull:Boolean; { is tcp's output buffer full? } u_tftp:Boolean; { is tftp running passively? } u_rstate:char; { Read terminal state } { NORMALMODE } { BLOCK - don't read terminal } u_rspecial:char; { Read terminal character handling } { NORMALMODE } { TCPFULL - tcp output buffer full } u_wstate:char; { Write terminal state } { NORMALMODE } { URGENTM - urgent mode } { ignore nonspecial chars } u_wspecial:char; { Write terminal character handling } { NORMALMODE } { RETURN (13), IAC, WILL, WONT, DO, DONT - processing special chars } u_sendm:char; { Send mode } { EVERYC - send to net on every char } { NEWLINE - send to net on newline } u_echom:char; { Echo mode } { LOCAL - local echo } { REMOTE - remote echo } u_echongo:char; { Echo negotiation request outstanding } { NORMALMODE } { LECHOREQ - IAC DONT ECHO was sent } { RECHOREQ - IAC DO ECHO was sent } u_echoback:Boolean; { whether to echo all received chars back to the user } u_tcconn:Ref_tcconn; { the TCP connection associated with the TELNET } u_tnshost:STR255; { name of remote host } end; ucbptr = ^ucb; PROCEDURE TelnetMenuCmd(item:Integer; theMenu:MenuHandle); PROCEDURE TNFixMenu(theMenu:MenuHandle); PROCEDURE tel_init; PROCEDURE gt_usr(c:char); PROCEDURE showstats; FUNCTION tn_conn(var s:STR255; fsock:Integer):ucbptr; FUNCTION tntftp(fhost:in_name; var filename:STR255; dir:unsigned):Integer; PROCEDURE tntfdn(success:Integer); PROCEDURE SendMenuCmd(item:Integer; theMenu:MenuHandle); PROCEDURE FixSendMenu(theMenu:menuHandle); VAR tn_done:Boolean; tn_tftp:Boolean; { whether or not to do the TFTP server } IMPLEMENTATION VAR TheName:STR255; TheFSock:Integer; ucb_extern:ucb; MyDialog:DialogPtr; ServerRunning: Boolean; nm_task:Ref_task; pucb:ucbptr; CONST tntfId = 11; NORMALMODE = chr(0); { SPECIAL = chr(1); TEST = chr(2); CONFIRM = chr(3); } HOLD = chr(4); BLOCK = chr(1); NOBLOCK = chr(2); URGENTM = chr(1); EVERYC = chr(1); NEWLINE = chr(2); LOCAL = chr(1); REMOTE = chr(2); LECHOREQ = chr(1); RECHOREQ = chr(2); NOP = chr(241); IAC = chr(255); WILL = chr(251); WONT = chr(252); DOIT = chr(253); DONT = chr(254); DM = chr(242); INTP = chr(244); AO = chr(245); AYT = chr(246); GA = chr(249); OPTECHO = chr(1); OPTSPGA = chr(3); OPTTMARK = chr(6); RETURN = chr($d); LINEFEED = chr($a); XESTAB = 1; XCLOSING = 2; XCLOSED = 3; TELNETSOCK = 23; { Telnet well known socket no. } C_L = chr($c); { Control-L } PROCEDURE echolocal(pucb:ucbptr); forward; PROCEDURE echoremote(pucb:ucbptr); forward; PROCEDURE ttechoremote(pucb:ucbptr); forward; PROCEDURE ttecholocal(pucb:ucbptr); forward; PROCEDURE tcpfull; forward; FUNCTION tn_alloc:ucbptr; BEGIN pucb := @ucb_extern; pucb^.u_state := XESTAB; pucb^.u_tcpfull := false; pucb^.u_tftp := false; pucb^.u_rstate := NORMALMODE; pucb^.u_rspecial := NORMALMODE; pucb^.u_wstate := NORMALMODE; pucb^.u_wspecial := NORMALMODE; pucb^.u_echoback := false; pucb^.u_echom := LOCAL; pucb^.u_echongo := NORMALMODE; pucb^.u_tcconn := NIL; pucb^.u_tnshost := 'Unknown'; pucb^.u_sendm := EVERYC; tn_alloc := pucb; END; {$S InitSeg} PROCEDURE tel_init; begin pucb := NIL; ServerRunning := false; nm_task := NIL; em_init; end; {$S } PROCEDURE tn_free; begin if tn_tftp AND pucb^.u_tftp then begin { File transfer service turned off. } tfs_off; pucb^.u_tftp := FALSE; end; { Don't really throw away connection storage } pucb := NIL; end; { Return true if telnet must run; false otherwise. } FUNCTION mst_run:Boolean; begin mst_run := TRUE; end; PROCEDURE bfr; begin ucb_extern.u_tcpfull := false; end; { gt_usr Process a char from user's terminal } PROCEDURE gt_usr(c:char); VAR i:Integer; ich:Integer; dummy:Boolean; begin if pucb = NIL then exit(gt_usr); if (pucb^.u_rspecial <> HOLD) AND (pucb^.u_tcconn^.conn_state = ESTAB) then begin case (pucb^.u_rspecial) of NORMALMODE: begin if(pucb^.u_tcpfull) then begin tcpfull; exit(gt_usr); end; if(pucb^.u_echom = LOCAL) then begin em(c); if c = RETURN then em(LINEFEED); end; if c = RETURN then c := LINEFEED else if c = LINEFEED then dummy := tc_put(pucb^.u_tcconn,RETURN) else if c = IAC then dummy := tc_put(pucb^.u_tcconn,IAC); if(pucb^.u_sendm = EVERYC) then begin if tc_fput(pucb^.u_tcconn,c) then tcpfull; end else begin if tc_put(pucb^.u_tcconn,c) then begin tcpfull; exit(gt_usr); end; if (c = LINEFEED) then tcp_ex(pucb^.u_tcconn); end; end; {$IFC DEBUG} otherwise begin WriteLn('Telnet BUG ',ord(pucb^.u_rspecial):1); pucb^.u_rspecial := NORMALMODE; end; {$ENDC} end; end; { of if } end; { of gt_usr } { wr_usr manage chars coming from net and going to user Process received telnet special chars and option negotiation. When wstate is URGENTM, only process special chars. } PROCEDURE wr_usr(c:char); VAR dummy:Boolean; theEvent:EventRecord; { used to get keyboard chars for DO TIMING MARK } BEGIN if pucb = NIL then exit(wr_usr); case (pucb^.u_wspecial) of NORMALMODE: begin if (c = IAC) then pucb^.u_wspecial := IAC else { Don't print ^L because Multics sends them quite often } if(pucb^.u_wstate <> URGENTM) AND (c <> C_L) then begin em(c); if pucb^.u_echoback then begin dummy := tc_put(pucb^.u_tcconn,c); if c = RETURN then dummy := tc_put(pucb^.u_tcconn,LINEFEED); if pucb^.u_sendm = EVERYC then tcp_ex(pucb^.u_tcconn); end; end; end; IAC: case (c) of IAC: begin if(pucb^.u_wstate <> URGENTM) then em(c); pucb^.u_wspecial := NORMALMODE end; AO: begin dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,DM); tcpurgent(pucb^.u_tcconn); pucb^.u_wspecial := NORMALMODE end; WILL,WONT,DOIT,DONT: pucb^.u_wspecial := c; GA: begin pucb^.u_wspecial := NORMALMODE; emstr('*GA*'); end; NOP: begin pucb^.u_wspecial := NORMALMODE; end; otherwise { Ignore IAC x } pucb^.u_wspecial := NORMALMODE; end; { case c } WILL: begin case c of OPTECHO: begin case (pucb^.u_echongo) of NORMALMODE: begin { This host did not initiate echo negot, so respond } if(pucb^.u_echom <> REMOTE) then echoremote(pucb); end; LECHOREQ: { Rejecting my IAC DONT ECHO (illegit) } ttechoremote(pucb); RECHOREQ: { Everything is OK } ; end; { case pucb^.u_echongo } pucb^.u_echongo := NORMALMODE; end; OPTSPGA: { suppress GA's } begin dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,DOIT); dummy := tc_fput(pucb^.u_tcconn,c); end; otherwise begin dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,DONT); dummy := tc_fput(pucb^.u_tcconn,c); end; end; { case c } pucb^.u_wspecial := NORMALMODE; end; WONT: begin if c = OPTECHO then begin case (pucb^.u_echongo) of NORMALMODE: { This host did not initiate echo negot, so respond } if pucb^.u_echom <> LOCAL then echolocal(pucb); RECHOREQ: { Rejecting my IAC DOIT ECHO } ttecholocal(pucb); LECHOREQ: { Everything is OK } ; end; { case pucb^.echongo } pucb^.u_echongo := NORMALMODE; end; pucb^.u_wspecial := NORMALMODE; end; DOIT: begin if c = OPTECHO then begin pucb^.u_echoback := true; dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,WILL); dummy := tc_fput(pucb^.u_tcconn,OPTECHO); end else if c = OPTTMARK then begin em_flush; while GetNextEvent(keyDownMask+autoKeyMask,theEvent) do gt_usr(chr(theEvent.message MOD 256)); dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,WILL); dummy := tc_fput(pucb^.u_tcconn,OPTTMARK); end else begin dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,WONT); dummy := tc_fput(pucb^.u_tcconn,c); end; pucb^.u_wspecial := NORMALMODE; end; DONT: begin if c = OPTECHO then begin pucb^.u_echoback := false; dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,WONT); dummy := tc_fput(pucb^.u_tcconn,OPTECHO); end; pucb^.u_wspecial := NORMALMODE; end; end; { case pucb^.u_wspecial } end; { of wr_usr } PROCEDURE echolocal(pucb:ucbptr); VAR dummy:Boolean; begin dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,DONT); dummy := tc_fput(pucb^.u_tcconn,OPTECHO); pucb^.u_echom := LOCAL; end; PROCEDURE ttecholocal(pucb:ucbptr); begin pucb^.u_echom := LOCAL; end; PROCEDURE echoremote(pucb:ucbptr); VAR dummy:Boolean; begin dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,DOIT); dummy := tc_fput(pucb^.u_tcconn,OPTECHO); pucb^.u_echom := REMOTE; end; PROCEDURE ttechoremote(pucb:ucbptr); begin pucb^.u_echom := REMOTE; end; PROCEDURE opn_usr; VAR pucb:ucbptr; begin EmLn('Open'); pucb := @ucb_extern; echoremote(pucb); pucb^.u_echongo := RECHOREQ; end; PROCEDURE opn_host; BEGIN EmLn('Open'); END; PROCEDURE cls_usr; begin EmLn('Closed'); tn_free; end; PROCEDURE tmo_usr; begin NotResponding(StrCvt('TELNET')); end; PROCEDURE pr_dot; begin em('.'); end; { Functions that interface with the TFTP server } { function called when file transfer is requested } FUNCTION tntftp(fhost:in_name; var filename:STR255; dir:unsigned):Integer; VAR ItemType:Integer; ItemHndl:Handle; ItemBox:Rect; itemHit:Integer; dowhat:string[7]; tntfDialog:DialogPtr; begin cvt_inaddr(fhost,Msg); if dir = PUT then dowhat := 'get' else dowhat := 'put'; ParamText(Msg,dowhat,filename,''); tntfDialog := GetNewDialog(tntfId,NIL,POINTER(-1)); ModalDialog(NIL, itemHit); DisposDialog(tntfDialog); if itemHit = 1 then tntftp := 1 else tntftp := 0; end; { function called when file transfer is done. } PROCEDURE tntfdn(success:Integer); begin Msg := 'TELNET TFTP Server: File transfer '; if success = 1 then insert('succeeded.',Msg,length(Msg)+1) else insert('failed.',Msg,length(Msg)+1); EmLn(Msg); end; { procedures to handle the two user menus unique to telnet } PROCEDURE TelnetMenuCmd(item:Integer; theMenu:MenuHandle); VAR dummy:Boolean; TempText:STR255; begin case (item) of 1: begin if pucb = NIL then begin if not NameRemoteHost(TempText) then exit(TelnetMenuCmd); em_reset; if tn_conn(TempText,TELNETSOCK) = NIL then begin CantConnect(StrCvt('TELNET'),@TempText); end else begin if tn_tftp then begin if NOT ServerRunning then BEGIN tfsinit(@tntftp,@tntfdn); ServerRunning := true; END; tfs_on; pucb^.u_tftp := TRUE; end; end; end; end; 2: begin if (pucb^.u_tcconn^.conn_state = ESTAB) then begin tcp_close(pucb^.u_tcconn); pucb^.u_state := XCLOSING; pucb^.u_rstate := BLOCK; end else if (pucb^.u_tcconn^.conn_state = LISTEN) or (pucb^.u_tcconn^.conn_state = SYNSENT) or (pucb^.u_tcconn^.conn_state = SYNRCVD) THEN begin tcp_close(pucb^.u_tcconn); end end; 3: begin if pucb^.u_sendm = EVERYC then pucb^.u_sendm := NEWLINE else pucb^.u_sendm := EVERYC; end; 4: begin if (pucb^.u_tftp) then begin { File transfer service turned off. } tfs_off; pucb^.u_tftp := FALSE; end else begin { File transfer service turned on. } tfs_on; pucb^.u_tftp := TRUE; end; end; {$IFC LISTEN} 5: begin pucb := tn_alloc; em_reset; pucb^.u_tcconn := tcp_listen(23, TCPWINDOW, TCPLOWIND, @opn_host,@wr_usr,@mst_run,@cls_usr,@tmo_usr,@pr_dot,@bfr); pucb^.u_echoback := false; pucb^.u_echom := LOCAL; if tn_tftp then begin if Not ServerRunning then BEGIN tfsinit(@tntftp,@tntfdn); ServerRunning := true; END; tfs_on; pucb^.u_tftp := TRUE; end; end; {$ENDC} 6: begin if pucb <> NIL then tn_free; tn_done := TRUE; end; end; { of item cases } end; { of TelnetMenuCmd } PROCEDURE TNFixMenu(theMenu:MenuHandle); BEGIN if (pucb <> NIL) then begin DisableItem(theMenu,1); if (pucb^.u_tcconn^.conn_state = ESTAB) or (pucb^.u_tcconn^.conn_state = LISTEN) or (pucb^.u_tcconn^.conn_state = SYNSENT) or (pucb^.u_tcconn^.conn_state = SYNRCVD) THEN EnableItem(theMenu,2) else DisableItem(theMenu,2); {$IFC LISTEN} CheckItem(theMenu,5,pucb^.u_tcconn^.conn_state=LISTEN); DisableItem(theMenu,5); {$ENDC} if (pucb^.u_tcconn^.conn_state = ESTAB) then begin CheckItem(theMenu,3,pucb^.u_sendm=EVERYC); CheckItem(theMenu,4,pucb^.u_tftp); EnableItem(theMenu,3); if tn_tftp then EnableItem(theMenu,4); DisableItem(theMenu,6) end else EnableItem(theMenu,6); end else begin CheckItem(theMenu,3,false); CheckItem(theMenu,4,false); EnableItem(theMenu,1); DisableItem(theMenu,2); DisableItem(theMenu,3); DisableItem(theMenu,4); {$IFC LISTEN} CheckItem(theMenu,5,false); EnableItem(theMenu,5); {$ENDC} EnableItem(theMenu,6); end; END; PROCEDURE SendMenuCmd(item:Integer; theMenu:MenuHandle); VAR dummy:Boolean; i:INTEGER; begin if NOT (pucb^.u_tcconn^.conn_state = ESTAB) then exit(SendMenuCmd); case item of 1: begin if(pucb^.u_echom = REMOTE) then begin echolocal(pucb); pucb^.u_echongo := LECHOREQ; end else begin { Remote echo mode. } echoremote(pucb); pucb^.u_echongo := RECHOREQ; end; end; 2: begin { Sending Are You There. } if tc_put(pucb^.u_tcconn,IAC) then tcpfull else if tc_fput(pucb^.u_tcconn,AYT) then tcpfull; end; 3: begin { Sending abort output. } dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_fput(pucb^.u_tcconn,AO); end; 4: begin { Sending break. } dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,INTP); dummy := tc_put(pucb^.u_tcconn,IAC); dummy := tc_put(pucb^.u_tcconn,DM); tcpurgent(pucb^.u_tcconn); end; 5: begin { Expediting data. } tcp_ex(pucb^.u_tcconn); end; (* 6: begin { Send Internet address } cvt_inaddr(in_mymach(0),Msg); for i := 1 to length(Msg) do gt_usr(Msg[i]); end; *) end; { of case } END; PROCEDURE FixSendMenu(theMenu:menuHandle); BEGIN if pucb = NIL then begin DisableItem(theMenu,0); CheckItem(theMenu,1,false); exit(FixSendMenu); end; CheckItem(theMenu,1,pucb^.u_echom=LOCAL); if pucb^.u_echongo = NORMALMODE then EnableItem(theMenu,1) else DisableItem(theMenu,1); if (pucb^.u_tcconn^.conn_state = ESTAB) then EnableItem(theMenu,0) else DisableItem(theMenu,0); if (pucb^.u_sendm = EVERYC) then DisableItem(theMenu,5) else EnableItem(theMenu,5); END; { Print the foreign host message } PROCEDURE pr_tn; begin if (pucb = NIL) or (pucb^.u_tcconn = NIL) then Error(StrCvt('TELNET: No open connections')) else begin EmStr('To host '); EmStr(pucb^.u_tnshost); EmStr(' ('); cvt_inaddr(pucb^.u_tcconn^.ForeignHost,Msg); EmStr(Msg); EmLn(')'); end; end; { Print telnet statistics } PROCEDURE showstats; begin if pucb = NIL then exit(showstats); pr_tn; case (pucb^.u_echom) of LOCAL: msg := 'local'; REMOTE: msg := 'remote'; otherwise msg := 'invalid state'; end; WriteLn(''); WriteLn('Echo Mode: ',msg); case (pucb^.u_sendm) of EVERYC: msg := 'every character'; NEWLINE: msg := 'newline'; otherwise msg := 'invalid state'; end; WriteLn('Send Mode: ',msg); tc_status(pucb^.u_tcconn); end; PROCEDURE tcpfull; begin pucb^.u_tcpfull := true; Message(StrCvt('TELNET'),StrCvt('Output buffer full')); end; { figure out a neat telnet socket } FUNCTION tn_sock:unsigned; VAR temp:LongInt; begin temp := TickCount; temp := BitAnd(temp,$0000ffff); if(temp < 1000) then temp := temp + 1000; tn_sock := temp; end; { This has to be a separate task because resolve_name blocks } PROCEDURE ResolveTask(Dummy:PTR); VAR fhost:in_name; BEGIN fhost := resolve_name(TheName); if (fhost = 0) then begin Error3(StrCvt('Foreign host '),@TheName,StrCvt(' not known.')); tn_free; nm_task := NIL; tk_exit; end; if (fhost = 1) then begin Error(StrCvt('Name servers not responding.')); tn_free; nm_task := NIL; tk_exit; end; pucb^.u_tnshost := copy(TheName,1,length(TheName)); pucb^.u_tcconn := tcp_open(@fhost,TheFSock,tn_sock,TCPWINDOW,TCPLOWIND, @opn_usr,@wr_usr,@mst_run,@cls_usr,@tmo_usr,@pr_dot,@bfr); pr_tn; EmStr('Trying...'); nm_task := NIL; tk_exit; END; FUNCTION tn_conn(var s:STR255; fsock:Integer):ucbptr; BEGIN if nm_task <> NIL then begin Error(StrCvt('Already trying to connect.')); tn_conn := NIL; exit(tn_conn); end; TheName := s; TheFSock := fsock; nm_task := tk_fork(tk_cur,@ResolveTask,TNTKSZ,'ResNam',NIL); if nm_task = NIL then begin CantAlloc(StrCvt('TELNET'),StrCvt('name resolution task')); tn_conn := NIL; exit(tn_conn); end; tn_conn := tn_alloc; end; { Here begins commented-out code saved for possible later use } (* union { in_name _l; char _c[4]; } him; static char nmbuffer[20]; him._l = custom.c_fhost; if(him._l == 0L) then begin Error('No telnet state saved, can''t continue.'); netclose; halt; end; sprintf(nmbuffer, "%o,%o,%o,%o", him._c[0]&0xff, him._c[1]&0xff, him._c[2]&0xff, him._c[3]&0xff); s := nmbuffer; tnhost := custom.c_fhost; pr_banner(s); EmStr('Trying...'); tcp_restore; 2: begin if(NOT tcp_save) then begin Error('Suspend failed!'); exit(TelnetMenuCmd); end; tn_free; end; 13: begin me._l := in_mymach(tnhost); sprintf(buffer, '%u.%u.%u.%u ', me._c[0]&0xff, me._c[1]&0xff, me._c[2]&0xff, me._c[3]&0xff); ich := 0; while buffer[ich] <> chr(0) do begin if(tc_put(pucb^.u_tcconn,buffer[ich])) then begin tcpfull; goto 418; end; if(pucb^.u_echom = LOCAL) then Write(buffer[ich]); ich := ich + 1; end; 418: if(pucb^.u_sendm = EVERYC) then tcp_ex(pucb^.u_tcconn); pr25(0,'Send my internet address in decimal.'); end; 14: begin me._l := in_mymach(tnhost); sprintf(buffer, '%o,%o,%o,%o ', me._c[0]&0xff, me._c[1]&0xff, me._c[2]&0xff, me._c[3]&0xff); ich := 0; while buffer[ich]<>chr(0) do begin if(tc_put(pucb^.u_tcconn,buffer[ich])) then begin tcpfull; goto 93; end; if(pucb^.u_echom = LOCAL) then v(buffer[ich]); ich := ich + 1; end; 93: if(pucb^.u_sendm = EVERYC) then tcp_ex(pucb^.u_tcconn); pr25(0,'Send Internet address in octal'); end; *) end. !E!O!F! # # echo extracting net/udp_lib.text... cat >net/udp_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL ALLOCT} {$SETC ALLOCT := false} UNIT UDP_Lib; { Please note the copyright notice in the file "copyright/notice" } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-err_lib } Err_Lib, {$U net-ip_lib } IP_Lib, {$U net-icmp_lib } ICMP_Lib, {$U net-calls } Call_Lib; {$L+} TYPE { UDP Header structure } udp = PACKED RECORD ud_srcp: integer; { source port } ud_dstp: integer; { dest port } ud_len: integer; { length of UDP packet } ud_cksum: integer; { UDP checksum } END; Ref_udp = ^ udp; { The UDP Connection structure } REF_udp_conn = ^ udp_conn; udp_conn = PACKED RECORD u_next: REF_udp_conn; u_lport: integer; { local port } u_fport: integer; { foreign port } u_fhost: in_name; { foreign host } {int (*u_rcv)(); } { incoming packet handler } u_rcv: ProcPTR; { incoming packet handler } u_data: PTR; { fooish thing } END; UDPCONN = REF_udp_conn; PROCEDURE UdpInit; FUNCTION udp_head(pip: Ref_ip):Ref_Udp; FUNCTION udp_data(pup: Ref_Udp): PTR; PROCEDURE udp_free(pkt:Packet); FUNCTION udp_alloc(datalen: Integer; optlen: Integer): PACKET; FUNCTION udp_socket: Integer; FUNCTION udp_open(fhost: in_name; fsock: Integer; lsock: Integer; handler: ProcPtr; data: PTR): UDPCONN; PROCEDURE udp_close(con: UDPCONN); FUNCTION udp_write(u: UDPCONN; p: PACKET; len: Integer): Integer; FUNCTION udp_ckcon(fhost: in_name; fsock:integer): UDPCONN; PROCEDURE udp_table; IMPLEMENTATION { Some UDP internals } CONST NAMESOCK = 42; { A Well Known Socket } NI_NAME = 1; NI_ADDR = 2; NI_ERR = 3; INPKTSIZ = INETLEN; TYPE ph = PACKED RECORD ph_src: in_name; { source address } ph_dest: in_name; { dest address } ph_zero: byte; { zero (reserved) } ph_prot: byte; { protocol } ph_len: integer; { udp length } END; { Some goodly constants, macros and an external } CONST UDPPROT = 17 { UDP Internet protocol number }; UDPLEN = sizeof(udp); {$S } VAR socket : integer; { Initialized in UDPInit } firstudp: UDPCONN; { Initialized in UdpInit := 0; } udp_ip_connection: IPCONN; { IP connection used by UDP } {$S UDPShare} PROCEDURE udpdemux(p: PACKET; len: Integer; host: in_name); forward; FUNCTION udp_head{(pip: Ref_ip):Ref_Udp}; BEGIN udp_head := { (struct udp *) } POINTER(ORD4(in_data(pip))); END; FUNCTION udp_data{(pup:Ref_Udp): PTR}; BEGIN udp_data := { (char *) } POINTER(ORD4(pup) + sizeof(udp) ); END; {$S InitSeg } { Initialize the UDP layer; get an internet connection, initialize the demux table } PROCEDURE UdpInit; BEGIN FirstUDP := NIL; socket := 0; udp_ip_connection := in_open(UDPPROT, @udpdemux); if (udp_ip_connection = NIL) THEN BEGIN CantConnect(StrCvt('UDP'),StrCvt('InterNet')); END {$IFC DEBUG} else if BCBitAnd(NDEBUG,INFOMSG) THEN WriteLn('UDP: Opened InterNet connection.'); {$ENDC} END; { UdpInit } {$S UDPShare} FUNCTION udp_alloc(datalen: Integer; optlen: Integer): PACKET; VAR len: Integer; BEGIN { len := (datalen + sizeof(udp) + 1) & ~1; } len := datalen + sizeof(udp) + 1; IF Odd(len) THEN len := len - 1; udp_alloc := in_alloc(len, optlen); END; { udp_alloc } PROCEDURE udp_free(pkt:Packet); {$IFC ALLOCT} VAR TmpStr:STR255; {$ENDC} BEGIN {$IFC ALLOCT} Write('free: '); NumToString(ORD4(pkt),TmpStr); WriteLn(TmpStr); {$ENDC} in_free(pkt); END; { Create a UDP connection and enter it in the demux table. } FUNCTION udp_open(fhost: in_name; fsock: Integer; lsock: Integer; handler: ProcPtr; data: PTR): UDPCONN; { fhost: in_name; } { foreign host } { fsock: integer; } { foreign socket } { lsock: integer; } { local socket } { int (*handler)(); } { upcalled on receipt of a packet } { data: PTR; } { random data } VAR i: Integer; con: UDPCONN; ocon: UDPCONN; BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('UDP_OPEN: On host '); out_inaddr(fhost); Write(', local sock ',lsock,', forn sock ',fsock,', foo '); WriteLong(ORD4(data)); WriteLn(''); END; {$ENDC} con := firstudp; WHILE (con <> NIL ) DO BEGIN if (con^.u_lport = lsock) AND (con^.u_fport = fsock) AND (con^.u_fhost = fhost) THEN BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR) THEN WriteLn('UDP: Connection already exists.'); {$ENDC} udp_open := NIL; EXIT(udp_open); END; ocon := con; con := con^.u_next; END; con := {(UDPCONN*) } POINTER(ORD4(NewPtr(sizeof(udp_conn)))); if (con = NIL) THEN BEGIN CantAlloc(StrCvt('UDP'),StrCvt('connection')); udp_open := NIL; EXIT(udp_open); END; if (firstudp <> NIL) THEN ocon^.u_next := con ELSE firstudp := con; con^.u_next := NIL; con^.u_lport := lsock; { fill in connection info } con^.u_fport := fsock; con^.u_fhost := fhost; con^.u_rcv := handler; con^.u_data := data; udp_open := con; END; { udp_open } FUNCTION udp_ckcon(fhost: in_name; fsock:integer): UDPCONN; VAR con: UDPCONN; BEGIN con := firstudp; while con <> NIL DO BEGIN IF (con^.u_fport = fsock) AND (con^.u_fhost = fhost) THEN leave ELSE con := con^.u_next; END; udp_ckcon := con; END; { end of udp_ckcon() } { close a udp connection - remove the connection from udp's list of connections and deallocate it. But only if the connection is not null. 1/16/84 <J. H. Saltzer> } PROCEDURE udp_close(con: UDPCONN); VAR pcon: UDPCONN; BEGIN if (con = NIL) THEN EXIT(udp_close); { This next line of code makes no sense: if the firstudp happens to be deallocated (closed), then the entire chain is thrown away, since there is no way to pick up the chain -- I think that firstudp should be made to point at the next udp connection in the list, just as if other udp connections were closed, then they would be linked over (to preserve other connections). } if (firstudp = con) THEN BEGIN { This is rewritten to meet my expectations } { was: firstudp := NIL } firstudp := firstudp^.u_next; END else BEGIN pcon := firstudp; WHILE (pcon <> NIL) DO BEGIN IF pcon^.u_next = con THEN leave; pcon := pcon^.u_next; END; if (pcon = NIL) THEN BEGIN {$IFC DEBUG} WriteLn('UDPClose: could not find connection to close'); {$ENDC} EXIT(udp_close); END; pcon^.u_next := con^.u_next; END; DisposPtr(POINTER(ORD4(con))); END; { udp_close} FUNCTION udp_socket{: Integer}; BEGIN if (socket<> 0) THEN BEGIN udp_socket := Socket; Socket := Socket + 1; EXIT(udp_socket); END; socket := LoWord(TickCount); if (socket < 1000) THEN socket := socket + 1000; udp_socket := socket; socket := socket + 1; END; { udp_socket } { This routine handles incoming UDP packets. They're handed to it by the internet layer. It demultiplexes the incoming packet based on the local port and upcalls the appropriate routine. } PROCEDURE udpdemux(p: PACKET; len: Integer; host: in_name); VAR pip: Ref_IP; pup: Ref_udp; php: ph; con: UDPCONN; osum, xsum: integer; data: PTR; plen: integer; RawDataPtr: PTR; Dummy: Integer; { Return value from ICMP call } BEGIN CheckTask; { First let's verify that it's a valid UDP packet. } {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('UDP: Received packet of length ',len, ' from host ');out_inaddr(host); WriteLn('.'); END; {$ENDC} pip := in_head(p); pup := udp_head(pip); plen := { bswap } (pup^.ud_len); if(plen > len) THEN BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR) THEN BEGIN WriteLn('UDP: Received bad len: rcvd: ',len, ', hdr: ',{ bswap } (pup^.ud_len) + UDPLEN); END; {$ENDC} in_free(p); exit(udpdemux); END; osum := pup^.ud_cksum; if (osum <> 0) THEN BEGIN if Odd(len) THEN BEGIN RawDataPtr := POINTER( ORD4(pup) + len); RawDataPtr^ := 0; END; php.ph_src := host; php.ph_dest := pip^.ip_dest; php.ph_zero := 0; php.ph_prot := UDPPROT; php.ph_len := pup^.ud_len; pup^.ud_cksum := cksum(@php, sizeof(ph) div 2); xsum := BitNOT(cksum(POINTER(ORD4(pup)),(len+1) div 2)); pup^.ud_cksum := osum; if (xsum <> osum) THEN BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR) THEN WriteLn('UDP: Received bad checksum.'); {$ENDC} in_free(p); exit(udpdemux); END; END; { udpswap(pup); } { Swapping unnecessary since 68000 doesnot byte swap integer representations } {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('UDP: Got packet for port ',pup^.ud_dstp, ' from port ',pup^.ud_srcp,' on host '); out_inaddr(host); WriteLn('.'); END; {$ENDC} { ok, accept it. run through the demux table and try to upcall it } con := firstudp; WHILE (con <> NIL) DO BEGIN if (con^.u_lport <> 0) AND (con^.u_lport <> pup^.ud_dstp) THEN con := con^.u_next ELSE BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('UDPDEMUX: Foo Data := '); WriteLong(ORD4(con^.u_data)); WriteLn(''); END; {$ENDC} if (con^.u_rcv <> NIL) THEN CALL4A(POINTER(ORD4(p)),plen-UDPLEN,host, con^.u_data,con^.u_rcv); exit(udpdemux); END; END; {$IFC DEBUG} if BCBitAnd(NDEBUG,INFOMSG) OR BCBitAnd(NDEBUG,PROTERR) THEN WriteLn('UDP: No connection for packet; packet dropped.'); {$ENDC} { send destination unreachable } Dummy := icmp_destun(host, in_head(p), DSTPORT); udp_free(p); END; { udpdemux } { Fill in the udp header on a packet, checksum it and pass it to Internet. } FUNCTION udp_write(u: UDPCONN; p: PACKET; len: Integer): Integer; VAR pup: Ref_UDP; php: ph; udplen: Integer; host: in_name; RawDataPtr: PTR; BEGIN {$IFC DEBUG} if BCBitAnd(NDEBUG,TCTRACE) THEN BEGIN WriteLn('UDP: Sending packet, length ',len, ' lport ',u^.u_lport,', fport ',u^.u_fport,'.'); END; if BCBitAnd(NDEBUG,INFOMSG) THEN BEGIN Write('UDP: buffer address '); WriteLong(ORD4(p^.nb_buff)); WriteLn(''); END; {$ENDC} pup := udp_head(in_head(p)); udplen := len + sizeof(udp); if Odd(udplen) THEN BEGIN { (( byte*)pup)[udplen] := 0; } RawDataPtr := POINTER(ORD4(pup) + udplen); RawDataPtr^ := 0; END; host := u^.u_fhost; pup^.ud_len := udplen; pup^.ud_srcp := u^.u_lport; pup^.ud_dstp := u^.u_fport; { udpswap(pup); } { No swapping is needed on the 68000 } php.ph_src := in_mymach(host); php.ph_dest := host; php.ph_zero := 0; php.ph_prot := UDPPROT; php.ph_len := pup^.ud_len; { The next two statements look very strange: why is the checksum being done twice on two different blocks with two different lengths and put in the same place? I have a suspicion that the first statement does a checksum over the header and stores that checksum in the header, and then the entire packet with header is checksumed and that value is then placed into the header as well. This seems like a silly way to do checksumming } pup^.ud_cksum := cksum(@php, sizeof(ph) div 2); pup^.ud_cksum := BitNOT(cksum(POINTER(ORD4(pup)),(udplen+1) div 2)); udp_write := in_write(udp_ip_connection, p, udplen, host); END; {udp_ write} (* PROCEDURE udpswap(pup:Ref_udp); { This procedure is unnecessary on the 68000 but is left for historical reasons } BEGIN pup^.ud_srcp := { bswap } (pup^.ud_srcp); pup^.ud_dstp := { bswap } (pup^.ud_dstp); pup^.ud_len := { bswap } (pup^.ud_len); pup^.ud_cksum := { bswap }(pup^.ud_cksum); END; { udpswap } *) { Dump the internal table of UDP connections. } PROCEDURE TabTo(fromPos,toPos:Integer); VAR i:Integer; BEGIN for i := fromPos+1 to toPos do Write(' '); END; PROCEDURE udp_table; CONST initItem = 6; udpDialog = 93; VAR con: UDPCONN; tmp:STR255; it:INTEGER; dp:DialogPtr; iType:INTEGER; itemHndl:Handle; box:Rect; PROCEDURE SetIt(number:LONGINT); BEGIN NumToStr(number,tmp); GetDItem(dp,it,iType,itemHndl,box); SetIText(itemHndl,tmp); END; BEGIN dp := GetNewDialog(udpDialog,NIL,POINTER(-1)); con := firstudp; it := initItem; WHILE (con <> NIL) DO BEGIN SetIt(con^.u_lport); it := it + 1; SetIt(con^.u_fport); it := it + 1; cvt_inaddr(con^.u_fhost,tmp); GetDItem(dp,it,iType,itemHndl,box); SetIText(itemHndl,tmp); it := it + 1; SetIt(ORD4(con^.u_rcv)); it := it + 1; SetIt(ORD4(con^.u_data)); it := it + 1; con := con^.u_next; END; MsgRegister(dp); END; { udp_table } END. !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting net/tcp_lib.text... cat >net/tcp_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL SAVEREST} {$SETC SAVEREST := false} {$DECL LISTEN} {$SETC LISTEN := true} { Please note the copyright notice in the file "copyright/notice" } UNIT TCP_Lib; INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-err_lib } Err_Lib, {$U net-ip_lib } IP_Lib, {$U net-term_lib } Term_Lib, {$U net-calls } Call_Lib; {$L+} CONST TCPWINDOW = 1000; { normal advertised window } TCPLOWIND = 500; { low water mark on window } TCPRTK = 3072; TCPSTK = 3072; TCPTKSZ = TCPRTK + TCPSTK; BUFSIZE = 2048; TOTALCHUNKS = 16; TYPE unsigned = Integer; seq_t = LongInt; unshort = Integer; seq_no = RECORD CASE Integer OF 0: (a: seq_t); 1: (l: Integer; h: Integer); END; { p } Ref_tcp = ^tcp; tcp = PACKED RECORD { a tcp header } tc_srcp: unshort; { source port } tc_dstp: unshort; { dest port } tc_seq: seq_t; { sequence number } tc_ack: seq_t; { acknowledgement number } tc_thl: 0..15; tc_res1: 0..15; tc_res2: 0..3; tc_furg: BOOLEAN; tc_fack: BOOLEAN; tc_psh: BOOLEAN; tc_rst: BOOLEAN; tc_syn: BOOLEAN; tc_fin: BOOLEAN; tc_win: unshort; { window } tc_cksum: unshort; { checksum } tc_urg: unshort; { urgent pointer } END; { TCP pseudo-header structure, used for checksumming } tcpph = PACKED RECORD { psuedo-header } tp_src: in_name; { source addr } tp_dst: in_name; { dest addr } tp_zero: BYTE; { always 0 } tp_pro: BYTE; { protocol } tp_len: Integer; { length } END; Ref_tcpph = ^ tcpph; Ref_tcconn = ^tcp_conn; tcp_conn = PACKED record { record representing a TCP connection } tc_next: Ref_tcconn; { queue link } tc_type: QTypes; { type word for Mac queues } conn_state: Integer; { connection state } ForeignHost: in_name; { what foreign host it's connected to } SourcePort: Integer; { id of local port } DestPort: Integer; { id of remote port } TelLowWin: Integer; TelWin: Integer; tcptm: Ref_Timer; { The resend timer } tmack: Ref_Timer; { ack timer } opbi: PACKET; { ptr to output packet buffer } otp: Ref_tcp; { ptr to output pkt tcp hdr } odp: PTR; { ptr to start of output pkt data } ophp: Ref_tcpph; { tcp pseudo hdr for cksum calc } frn_win: unsigned; { Size of foreign window. } ack_time: LongInt; { When last ACK was sent. } odlen: Integer; { bytes of data in output pkt } dally_time: Integer; { time to delay ack (ticks) } retry_time: Integer; { retransmission timeout (ticks)} resend: Integer; { flag indicating data must be resent } taken: Integer; { number of bytes of circbuf processed } avail: Integer; { number of circbuf bytes unprocessed } fin_offset: Integer; { offset to finish of data } circbuf: packed array [0..BUFSIZE-1] of char; { data buffer } numchunks: Integer; maxchunks: Integer; lastchunk: Integer; ceilchunk: Integer; first: packed array [0..TOTALCHUNKS-1] of Integer; last: packed array [0..TOTALCHUNKS-1] of Integer; free: packed array [0..TOTALCHUNKS-1] of Boolean; {int (*tc_ofcn)();} { user function called on open } {int (*tc_dispose)();} { user function to receive data } {int (*tc_yield)();} { Predicate set when user must run. } {int (*tc_cfcn)();} { user function called on close } {int (*tc_tfcn)();} { user function called on icp tmo } {int (*tc_rfcn)();} { user function called on icp resend } {int (*tc_buff)();} { user function to upcall when output buffer space is available } tc_ofcn, tc_dispose, tc_yield, tc_cfcn, tc_tfcn, tc_rfcn, tc_buff: ProcPtr; blk_inpt: Boolean; { prevents new data after close req } fin_rcvd: Boolean; { received end of file (FIN) } newsend: Boolean; { flag indicating new data to send } hasrecvd: Boolean; { does connection have data to process ? } mustsend: Boolean; { does connection need a send done? } end; PROCEDURE tcp_init(stksiz: Integer); FUNCTION tcp_open(fh: Ref_in_name; fs: unsigned; ls: unsigned; win:Integer; lowwin: Integer; ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr) :Ref_tcconn; {$IFC LISTEN} FUNCTION tcp_listen(ls: unsigned; win:Integer; lowwin: Integer; ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr) :Ref_tcconn; {$ENDC} PROCEDURE tcp_close(connection:Ref_tcconn); FUNCTION tc_fput(connection:Ref_tcconn; c: char): Boolean; FUNCTION tc_put(connection:Ref_tcconn; c: char): Boolean; PROCEDURE tcpurgent(connection:Ref_tcconn); PROCEDURE tcp_ex(connection:Ref_tcconn); PROCEDURE tc_status(connection:Ref_tcconn); { tcp connection states } CONST CLOSED = 0; { nothing has happenned } { CLOSED,SYNSENT,ESTAB,TIMEWAIT same as in tcp spec } SYNSENT = 1; { connection requested } SYNRCVD = 2; ESTAB = 3; { connection established } FINSENT = 4; { local user wishes to close } { same as FIN-WAIT-1 in tcp spec } FINRCVD = 5; { foreign host wishes to close } { same as CLOSE-WAIT in tcp spec } SIMUL = 6; { local user and foreign host wish to close } { same as CLOSING in tcp spec } FINACKED = 7; { local user's close request acked } { same as FIN-WAIT-2 in tcp spec } R_AND_S = 8; { frgn close req rcvd, local close req sent } { same as LAST-ACK in tcp spec } TIMEWAIT = 9; { last ack is being sent } LISTEN = 10; { waiting for connection request } { new for Macintosh version } { Picture from TCP Specs (RFC 793): TCP Header Format 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Source Port | Destination Port | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Acknowledgment Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Data | |U|A|P|R|S|F| | | Offset| Reserved |R|C|S|S|Y|I| Window | | | |G|K|H|T|N|N| | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Checksum | Urgent Pointer | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Options | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | data | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ } IMPLEMENTATION {$IFC DEBUG} PROCEDURE WriteHex(c:char); { writes a char as two hex digits } VAR temp:Integer; BEGIN temp := BitAnd(BitSR(ord(c),4),$000f); if (temp <= 9) then temp := temp + ord('0') else temp := temp + ord('A') - 10; Write(chr(temp)); temp := BitAnd(ord(c),$000f); if (temp <= 9) then temp := temp + ord('0') else temp := temp + ord('A') - 10; Write(chr(temp)); END; PROCEDURE Print_TCP(t:Ref_TCP); BEGIN Write('TCP Header: '); WriteLn('Source Port = ',t^.tc_srcp:1,', Dest Port = ',t^.tc_dstp:1); WriteLn('Sequence number = ',t^.tc_seq:1,', Ack number = ',t^.tc_ack:1); { Write('Bits = '); WriteHex(chr(BitAND($00ff,BitSR(t^.tc_bits,8)))); WriteHex(chr(BitAND($00ff,t^.tc_bits))); } Write('Window = ',t^.tc_win:1); WriteLn(', Checksum = ',t^.tc_cksum:1,', Urgent Pointer = ',t^.tc_urg:1); END; {$ENDC} CONST TCPPROT = 6; { tcp protocol number } TCPMAXWIND = 4096; { maximum tcp window that this implementation supports } { Translation of Dave Clark's Alto TCP into C. This file contains a bare-bones minimal TCP, suitable only for user Telnet and other protocols which do not need to transmit much data. Its primary weakness is that it does not pay any attention to the window advertised by the receiver; it assumes that it will never be sending much data and hence will never run out of receive window. (Note: this not really that much of a weakness, since the other side should ignore any data over its window. Still, it would be more efficient.) Its other major weakness is that it will not handle out-of-sequence packets; any out-of-sequence data received is ignored. This TCP requires the tasking package to run. It runs as two tasks: TCPsend, which handles all data transmission, and TCPprocess, which processes the incoming data from the circular buffer. The data is placed in the circular buffer by tcp_rcv, which is upcalled from internet on an arriving packet. } CONST ACKDALLY = 30; { wait between ACKs (ticks) } INITRT = 4; { retry time -- initial request } {$IFC DEBUG} CONNRT = 20; {$ELSEC} CONNRT = 2; { retry time -- after connected } {$ENDC} MAXBUF = 500; { maximum output buffer size } ICPTMO = 5; { initial conn. timeout } { Connection queue structure: compatible with Mac queues } TYPE TCconnQ = PACKED RECORD qFlags:INTEGER; qHead:Ref_tcconn; qTail:Ref_tcconn end; TCPGlobals = RECORD tcpconns:^TCconnQ; { Global connection queue } still_data: Boolean; { There are still incoming pkts to process. } tcpfd: IPCONN; { connection ID used when calling Internet } TCPsend: Ref_Task; { tcp sending task } TCPprocess: Ref_Task; { TCP data processing task } first_offset: Integer; { TCP statistics collection variables } tcppsnt:unsigned; { number of packets sent } tcpprcv:unsigned; { number of packets received } tcpbsnt:unsigned; { number of bytes sent } tcpbrcv:unsigned; { number of bytes received } tcprack:unsigned; { # of bytes received and acked } tcpsock:unsigned; { # of packets not for my socket } tcpresend:unsigned; { # of resent packets } tcprercv:unsigned; { # of old packets received } tcpous:unsigned; { # of out-of-sequence packets } bd_chk:unsigned; { Number of pkts rcvd with bad checksums.} ign_win:unsigned; { Num pkts rcvd that ignored our window. } END; Ref_TCPGlobals = ^TCPglobals; VAR global:Ref_TCPGlobals; PROCEDURE tcp_rcv(inpkt: PACKET; len: Integer; fhost: in_name); FORWARD; PROCEDURE tcp_send(NoArg:PTR); FORWARD; PROCEDURE tcp_process(NoArg:PTR); FORWARD; PROCEDURE tmhnd(connection:Ref_tcconn); FORWARD; PROCEDURE tcp_ack(connection:Ref_tcconn); FORWARD; PROCEDURE send_wake(connection:Ref_tcconn); FORWARD; { translated macro } FUNCTION NOT_YET(a, b: Integer): Boolean; BEGIN NOT_YET := ((a) < (b)); END; { NOT YET } { This routine is called to initialize the TCP. It starts up the tasking system, initiates the timer, TCPsend, and TCPrcv tasks. It does not attempt to open the connection; that function is performed by tcp_open. } {$S InitSeg} PROCEDURE tcp_init(stksiz: Integer); BEGIN global := POINTER(ORD4(NewPtr(sizeof(TCPglobals)))); if global = NIL then Fatal(StrCvt('TCP_INIT: can''t get globals'),false); WITH global^ DO BEGIN tcpfd := in_open(TCPPROT, @tcp_rcv); if (tcpfd = NIL) THEN Fatal(StrCvt('TCP_INIT: can''t open Internet'),false); TCPconns := POINTER(ORD4(NewPtr(sizeof(TCconnQ)))); if TCPconns = NIL then Fatal(StrCvt('TCP_INIT: can''t allocate connection queue'),false); { Initialize connection queue } TCPconns^.qFlags := 0; TCPconns^.qHead := NIL; TCPconns^.qTail := NIL; tcppsnt := 0; { initially, no pkts sent } tcpprcv := 0; { or received } tcpbsnt := 0; { no bytes sent } tcpbrcv := 0; { foreign host sent no bytes } tcprack := 0; { we acked no bytes of foreign host} tcpresend := 0; tcprercv := 0; ign_win := 0; still_data := FALSE; TCPsend := tk_fork(tk_cur, @tcp_send, TCPSTK, 'TCPsend', NIL); TCPprocess := tk_fork(TCPsend, @tcp_process, TCPRTK, 'TCPproc', NIL); { let the other tasks get started } tk_yield; END; { WITH } END; { end of tcp_init} {$S } { tcp_alloc: allocate and return a tcp connection (for now just one allowed) the pointers to the up-callable user routines for open, received data, close and timeout. } FUNCTION tcp_alloc:Ref_tcconn; VAR Dummy:Boolean; connection:Ref_tcconn; i:Integer; Fake:OSErr; BEGIN { Get connection from memory manager; put on queue of connections in use } connection := POINTER(ORD4(NewPtr(sizeof(tcp_conn)))); if connection = NIL then begin CantAlloc(StrCvt('TCP'),StrCvt('connection')); tcp_alloc := NIL; exit(tcp_alloc); end; connection^.tc_next := NIL; connection^.tc_type := dummyType; enqueue(POINTER(ORD4(connection)),POINTER(ORD4(global^.TCPconns))); WITH connection^ do begin { alloc and set up output pkt } opbi := in_alloc(INETLEN, 0); if (opbi = NIL) THEN BEGIN CantAlloc(StrCvt('TCP'),StrCvt('packet')); Fake := dequeue(POINTER(ORD4(connection)), POINTER(ORD4(global^.TCPconns))); DisposPtr(POINTER(ORD4(connection))); tcp_alloc := NIL; exit(tcp_alloc); END; otp := POINTER(ORD4(in_data(in_head(opbi)))); odp := POINTER(ORD4(otp) + sizeof(tcp)); { allocate pseudoheader for outgoing packet } ophp := POINTER(ORD4(NewPtr(sizeof(tcpph)))); if (ophp = NIL) THEN BEGIN Fake := dequeue(POINTER(ORD4(connection)), POINTER(ORD4(global^.TCPconns))); DisposPtr(POINTER(ORD4(connection))); tcp_alloc := NIL; exit(tcp_alloc); END; ophp^.tp_zero := 0; ophp^.tp_pro := TCPPROT; global^.tcpsock := 0; conn_state := CLOSED; { initially, conn. closed } retry_time := INITRT*TPS; newsend := FALSE; ForeignHost := 0; resend := 0; fin_offset := 0; fin_rcvd := FALSE; ack_time := 0; odlen := 0; blk_inpt := FALSE; hasrecvd := FALSE; mustsend := FALSE; otp^.tc_seq := 0; otp^.tc_ack := 0; otp^.tc_res1 := 0; otp^.tc_res2 := 0; otp^.tc_thl := 0; otp^.tc_furg := false; otp^.tc_fack := false; otp^.tc_psh := false; otp^.tc_rst := false; otp^.tc_fin := false; otp^.tc_syn := true; otp^.tc_urg := 0; global^.bd_chk := 0; taken := -1; avail := -1; tcptm := tm_alloc; if(tcptm = NIL) THEN BEGIN Fake := dequeue(POINTER(ORD4(connection)), POINTER(ORD4(global^.TCPconns))); DisposPtr(POINTER(ORD4(connection))); tcp_alloc := NIL; exit(tcp_alloc); END; tmack := tm_alloc; if(tmack = NIL) THEN BEGIN Dummy := tm_free(tcptm); Fake := dequeue(POINTER(ORD4(connection)), POINTER(ORD4(global^.TCPconns))); DisposPtr(POINTER(ORD4(connection))); tcp_alloc := NIL; exit(tcp_alloc); END; numchunks := 0; maxchunks := 0; lastchunk := 0; ceilchunk := 0; FOR i := 0 to TOTALCHUNKS - 1 DO free[i] := TRUE; end;{with} tcp_alloc := connection; END; PROCEDURE tcp_free(VAR conn:Ref_tcconn); VAR Dummy:OSErr; Fake:Boolean; BEGIN { dequeue the connection from the active list and the tcp_process queue, then free its storage } Dummy := dequeue(POINTER(ORD4(conn)),POINTER(ORD4(global^.TCPconns))); in_free(conn^.opbi); Fake := tm_clear(conn^.tcptm); Fake := tm_free(conn^.tcptm); Fake := tm_clear(conn^.tmack); Fake := tm_free(conn^.tmack); DisposPtr(POINTER(ORD4(conn^.ophp))); DisposPtr(POINTER(ORD4(conn))); conn := NIL END; { Initiate the TCP closing sequence. This routine will return immediately; when the close is complete the user close function will be called. } PROCEDURE tcp_close(connection:Ref_tcconn); BEGIN if connection = NIL then exit(tcp_close); WITH connection^ do begin CASE conn_state OF LISTEN, SYNRCVD, CLOSED: BEGIN CALL(tc_cfcn); tcp_free(connection); END; ESTAB: BEGIN conn_state := FINSENT; otp^.tc_fin := true; otp^.tc_psh := false; blk_inpt := TRUE; newsend := TRUE; send_wake(connection); END; SYNSENT: BEGIN otp^.tc_rst := TRUE; blk_inpt := TRUE; newsend := TRUE; send_wake(connection); END; END; end;{with} END; { end of tcp_close } { Close and reset a tcp connection. } PROCEDURE tc_clrs(connection:Ref_tcconn; p: PACKET; fhost: in_name); VAR ltemp: LongInt; myport:unsigned; ptp: Ref_tcp; php: tcpph; err:Integer; BEGIN if connection = NIL then exit(tc_clrs); WITH connection^,global^ do begin ptp := POINTER(ORD4(in_data(in_head(p)))); { swap port numbers } myport := ptp^.tc_dstp; ptp^.tc_dstp := ptp^.tc_srcp; ptp^.tc_srcp := myport; { fill in the rest of the header } ltemp := ptp^.tc_seq; ptp^.tc_seq := ptp^.tc_ack; ptp^.tc_ack := ltemp; ptp^.tc_furg := false; ptp^.tc_fack := false; ptp^.tc_psh := false; ptp^.tc_fin := false; ptp^.tc_syn := false; ptp^.tc_thl := sizeof(tcp) div 4; ptp^.tc_rst := true; ptp^.tc_win := 0; {tcp_swab(ptp);} { Not on 68000 } { Set up the tcp pseudo header } php.tp_src := in_mymach(fhost); php.tp_dst := fhost; php.tp_zero := 0; php.tp_pro := TCPPROT; php.tp_len := sizeof(tcp); { checksum the packet } ptp^.tc_cksum := cksum(@php, sizeof(tcpph) div 2); ptp^.tc_cksum := BitNOT(cksum(POINTER(ORD4(ptp)),sizeof(tcp) div 2)); err := in_write(tcpfd, p, sizeof(tcp), fhost); end;{with} tcp_free(connection); END; {tc_clrs} PROCEDURE cleanup(why: StringPtr); BEGIN Error2(StrCvt('Closed:'),why); END; { Just shift the data in a buffer, moving len bytes from from to to. } PROCEDURE shift(from_ptr, to_ptr: PTR; len:Integer); BEGIN {$IFC DEBUG} if (len < 0) THEN BEGIN WriteLn('tcp: shift: bad arg--len < 0'); EXIT(shift); END; {$ENDC} WHILE (len<>0) DO BEGIN len := len - 1; to_ptr^ := from_ptr^; to_ptr := POINTER(ORD4(to_ptr)+1); from_ptr := POINTER(ORD4(from_ptr)+1); END; END; { shift } { This routine forms the body of the TCP data receiver task. It attempts to read and process incoming packets. The processing of each packet is divided into three phases: 1) Processing acknowlegments. This involves 'shifting' the data in the output packet to account for acknowledged data. 2) Processing state information: syn's, fin's, urgents, etc. 3) Processing the received data. This is done by calling the user's 'input data' function (specified in his call to tcp_open), passing it the address and length of the received data. } PROCEDURE tcp_rcv(inpkt: PACKET; len: Integer; fhost: in_name); VAR itp: Ref_tcp; { input pkt tcp hdr } idp: PTR; { input pkt data ptr } needed_acking: Integer; { number of outstanding bytes } acked: Integer; { # outstanding bytes acked by this packet } data_acked: Integer; { # outstanding bytes acked by this packet } { number of previously received seq. numbers} prev_rcvd: Integer; { (data + fin bit) in current packet} idlen: Integer; { len of input pkt in bytes } i: Integer; limit: Integer; Raw_Ptr: PTR; tempsum: unsigned; { temp variable for a checksum } Dummy: Boolean; connection: Ref_tcconn; ip_hd: Ref_IP; { used to extract fhost for checksum } start_offset: Integer; end_offset: Integer; notfound: Boolean; iphp: tcpph; { pseudo-header for checksum calcs } Fake: OSErr; BEGIN CheckTask; WITH global^ DO BEGIN { Process the received input packet } itp := POINTER(ORD4(in_data(in_head(inpkt)))); idp := POINTER(ORD4(itp) + (itp^.tc_thl*4)); { get incoming data length } idlen := len - (itp^.tc_thl * 4); tcpprcv := tcpprcv + 1; { another packet received } {$IFC DEBUG} WriteLn('TCP: Received packet, len = ',len:1,', idlen = ',idlen:1); (* Print_TCP(itp); *) {$ENDC} { compute incoming tcp checksum here... } if idlen >= 0 THEN BEGIN Raw_Ptr := POINTER(ORD4(idp) + idlen); Raw_Ptr^ := 0; END; iphp.tp_zero := 0; iphp.tp_pro := TCPPROT; iphp.tp_len := idlen + (itp^.tc_thl*4); ip_hd := in_head(inpkt); iphp.tp_dst := in_mymach(ip_hd^.ip_src); iphp.tp_src := ip_hd^.ip_src; tempsum := itp^.tc_cksum; itp^.tc_cksum := cksum(@iphp,sizeof(tcpph) div 2); itp^.tc_cksum := BitNOT(cksum(POINTER(ORD4(itp)), ((idlen+1) div 2)+(itp^.tc_thl*2))); if (itp^.tc_cksum <> tempsum) THEN BEGIN {$IFC DEBUG} WriteLn('TCP: Bad xsum was ',tempsum:1, '; should have been ',itp^.tc_cksum:1); {$ENDC} bd_chk := bd_chk + 1; in_free(inpkt); EXIT(tcp_rcv); END; { loop over each connection looking for match (demux) } connection := TCPconns^.qHead; notfound := true; while (connection <> NIL) and notfound do begin { If the user wishes to send data, give up the processor. } if (CALLB(connection^.tc_yield)) THEN BEGIN still_data := TRUE; { There's still data to process. } tk_yield; still_data := FALSE; END; if (itp^.tc_dstp = connection^.SourcePort) then begin if (connection^.conn_state = CLOSED) | ((connection^.conn_state <> LISTEN) & (itp^.tc_srcp <> connection^.DestPort)) THEN BEGIN tcpsock := tcpsock + 1; in_free(inpkt); EXIT(tcp_rcv); END; notfound := false; end else connection := connection^.tc_next; end; { of loop } if notfound then BEGIN {$IFC DEBUG} WriteLn('Received TCP packet not for me'); {$ENDC} tcpsock := tcpsock + 1; in_free(inpkt); exit(tcp_rcv); END; WITH connection^ DO BEGIN if itp^.tc_rst THEN BEGIN IF (conn_state <> LISTEN) AND (conn_state <> CLOSED) THEN { other guy's resetting me } BEGIN cleanup(StrCvt('foreign reset')); CALL(tc_cfcn); tcp_free(connection); END; in_free(inpkt); EXIT(tcp_rcv); END; { incoming packet processing is dependent on the state of the connection } if (conn_state = SYNSENT) THEN BEGIN { opening the connection } { ack must be for our initial sequence number - namely, 1 } if (NOT itp^.tc_fack) OR (itp^.tc_ack <> 1) THEN BEGIN in_free(inpkt); EXIT(tcp_rcv); END; if NOT itp^.tc_syn THEN BEGIN in_free(inpkt); EXIT(tcp_rcv); END; { Connection open } otp^.tc_syn := false; otp^.tc_rst := false; otp^.tc_fack := true; otp^.tc_psh := true; otp^.tc_seq := 1; itp^.tc_seq := itp^.tc_seq + 1; { syn's take sequence no.space } otp^.tc_ack := itp^.tc_seq; tcpbrcv := 1; frn_win := itp^.tc_win; conn_state := ESTAB; retry_time := CONNRT*TPS; { Our initial request was acknowledged and there is not yet new data so reset the timer } newsend := TRUE; Dummy := tm_clear(tcptm); CALL(tc_ofcn); send_wake(connection); END {$IFC LISTEN} else if conn_state = LISTEN then BEGIN { New code (for Mac version) to accept incoming connections } if (NOT itp^.tc_syn) OR itp^.tc_fack THEN BEGIN tc_clrs(connection,inpkt,fhost); in_free(inpkt); EXIT(tcp_rcv); END; conn_state := SYNRCVD; ForeignHost := fhost; DestPort := itp^.tc_srcp; { set up pseudoheader for outgoing packet } ophp^.tp_src := in_mymach(ForeignHost); ophp^.tp_dst := ForeignHost; { set up outgoing packet } otp^.tc_srcp := SourcePort; otp^.tc_dstp := DestPort; otp^.tc_ack := itp^.tc_seq + 1; otp^.tc_fack := true; newsend := TRUE; send_wake(connection); in_free(inpkt); EXIT(tcp_rcv); END else if (conn_state = SYNRCVD) then BEGIN { New (Mac) code to accept acknowledgement of opening } if itp^.tc_seq <> otp^.tc_ack then begin {$IFC DEBUG} WriteLn('TCP_RCV: bad seq number (',itp^.tc_seq:1, ') should be (',otp^.tc_seq:1,')'); {$ENDC} tc_clrs(connection,inpkt,fhost); in_free(inpkt); EXIT(tcp_rcv); end; if NOT otp^.tc_fack then begin in_free(inpkt); EXIT(tcp_rcv); end; if itp^.tc_ack <> (otp^.tc_seq + 1) then begin {$IFC DEBUG} WriteLn('TCP_RCV: bad ack number (',itp^.tc_ack:1, '); should be ',otp^.tc_ack:1); {$ENDC} tc_clrs(connection,inpkt,fhost); in_free(inpkt); EXIT(tcp_rcv); end; { Connection open } otp^.tc_syn := false; otp^.tc_rst := false; otp^.tc_fack := true; otp^.tc_psh := true; otp^.tc_seq := itp^.tc_seq; otp^.tc_ack := itp^.tc_ack; tcpbrcv := 1; frn_win := itp^.tc_win; conn_state := ESTAB; retry_time := CONNRT*TPS; Dummy := tm_clear(tcptm); CALL(tc_ofcn); END {$ENDC} ; { This code updates things based on incoming ack value } if itp^.tc_fack THEN BEGIN acked := ORD(itp^.tc_ack - otp^.tc_seq); needed_acking := odlen + ORD(otp^.tc_fin); IF acked = needed_acking THEN data_acked := acked - ORD(otp^.tc_fin) ELSE data_acked := acked; {$IFC DEBUG} WriteLn('tcp_rcv: ACK processing (acked = ',acked:1, ', needed_acking = ',needed_acking:1,')'); {$ENDC} { Ack for unsent data prompts us to reset. } if acked > needed_acking THEN BEGIN otp^.tc_rst := true; Error( StrCvt('TCP_RCV: unsent data acknowledged (resetting)')); otp^.tc_seq := itp^.tc_ack; send_wake(connection); in_free(inpkt); EXIT(tcp_rcv); END; if acked > 0 THEN BEGIN { So now we have some free output buffer space. Upcall the client and tell him so. } if (odlen >= MAXBUF) THEN CALL(tc_buff); { Account for ack of our urgent data by updating the urgent pointer } if otp^.tc_furg THEN BEGIN otp^.tc_urg := otp^.tc_urg - acked; if (otp^.tc_urg <= 0) THEN BEGIN otp^.tc_urg := 0; otp^.tc_furg := false; END; END; { See if all our outgoing data is now acked. If so, turn off resend timer } if acked = needed_acking THEN BEGIN Dummy := tm_clear(tcptm); newsend := FALSE; resend := 0; odlen := 0; odp^ := 0; if otp^.tc_fin THEN BEGIN otp^.tc_fin := false; CASE (conn_state) OF FINSENT: conn_state := FINACKED; SIMUL: conn_state := TIMEWAIT; R_AND_S: BEGIN CALL(tc_cfcn); tcp_free(connection); in_free(inpkt); EXIT(tcp_rcv); END; {$IFC DEBUG} OTHERWISE BEGIN WriteLn('tcp_rcv: unexpected state: ',conn_state:1); END; {$ENDC} END; { end of case } END; { end of fin test } END { if acked = needed_acking } ELSE { acked is less than needed_acking } BEGIN { Otherwise, shift the output data in the packet to account for ack } odlen := odlen - data_acked; shift(POINTER(ORD4(odp)+data_acked), odp, odlen); Raw_Ptr := POINTER(ORD4(odp) + odlen); Raw_Ptr^ := 0; END; { update the sequence number } frn_win := itp^.tc_win; otp^.tc_seq := otp^.tc_seq + acked; END; END; { Now process the received data } {$IFC DEBUG} WriteLn('tcp_rcv: About to process data'); {$ENDC} { Check if the incoming data is over the top of our window } if ((itp^.tc_seq + idlen) > (otp^.tc_ack + otp^.tc_win)) THEN BEGIN { ignore excess data } ign_win := ign_win + 1; idlen := idlen - (itp^.tc_seq + idlen - (otp^.tc_ack + otp^.tc_win)); END; { Calculate the number of previously received sequence numbers in the current packet } prev_rcvd := otp^.tc_ack - itp^.tc_seq; start_offset := 0 - prev_rcvd; end_offset := (start_offset + idlen) - 1; if end_offset >= BUFSIZE THEN end_offset := BUFSIZE - 1; {$IFC DEBUG} WriteLn('tcp_rcv: prev_rcvd = ',prev_rcvd:1,', end_offset = ',end_offset:1, ', idlen = ',idlen:1,', avail = ',avail:1); {$ENDC} if itp^.tc_fin THEN BEGIN fin_rcvd := TRUE; fin_offset := end_offset; END; if end_offset < avail THEN { If all incoming info is old.} BEGIN {$IFC DEBUG} WriteLn('tcp_rcv: no new data in packet'); {$ENDC} tcprercv := tcprercv + 1; in_free(inpkt); EXIT(tcp_rcv); END; limit := prev_rcvd; if limit < 0 then limit := 0; FOR i := limit TO idlen - 1 DO BEGIN Raw_Ptr := POINTER(ORD4(idp)+i); circbuf[(taken+1+start_offset+i) mod BUFSIZE] := chr(Raw_Ptr^); END; if (numchunks = 0) THEN BEGIN if start_offset <= avail + 1 THEN { DDC CHANGE } avail := end_offset else BEGIN first[0] := start_offset; last[0] := end_offset; free[0] := FALSE; numchunks := 1; maxchunks := 0; lastchunk := 0; END; END else BEGIN if (lastchunk > -1) & ((last[lastchunk] + 1) = start_offset) THEN last[lastchunk] := end_offset else BEGIN FOR i := 0 TO maxchunks DO BEGIN if free[i] THEN cycle; if (last[i]+1) < start_offset THEN cycle; if first[i] > (end_offset+1) THEN cycle; if end_offset < last[i] then end_offset := last[i]; if start_offset > first[i] then start_offset := first[i]; free[i] := TRUE; numchunks := numchunks - 1; if (numchunks = 0) THEN BEGIN maxchunks := -1; leave; END; END; if (lastchunk > -1) & free[lastchunk] THEN lastchunk := -1; if (start_offset <= 0) THEN avail := end_offset else BEGIN limit := maxchunks + 1; if limit > TOTALCHUNKS-1 then limit := TOTALCHUNKS - 1; FOR i := 0 TO limit DO BEGIN if not free[i] THEN cycle; free [i] := FALSE; first[i] := start_offset; last [i] := end_offset; numchunks := numchunks + 1 ; if i = (maxchunks + 1) THEN maxchunks := i; if i > ceilchunk THEN ceilchunk := i; if lastchunk = -1 THEN lastchunk := i; leave; END; END; END; END; {$IFC DEBUG} WriteLn('tcp_rcv: avail = ',avail:1,', fin_offset = ',fin_offset:1); {$ENDC} { If there's data to process, wake up the data processing task. } if (avail >= 0) OR (fin_offset = -1) THEN BEGIN {$IFC DEBUG} WriteLn('tcp_rcv: awakening tcp_process'); {$ENDC} hasrecvd := true; tk_wake(TCPprocess); END; in_free(inpkt); end; { WITH connection^ } end; { WITH global^ } END; {tcp_rcv} { Process received data from tcp. This is the task counterpart of tcp_rcv. } PROCEDURE tcp_process(NoArg:PTR); LABEL 666; VAR i: Integer; { used for looping over data } Fake: Boolean; connection: Ref_tcconn; ticks: LONGINT; { used to regulate SystemTask calling } BEGIN tk_block; { Forget the initial wakeup } 666: while true do begin connection := global^.tcpconns^.qHead; while connection <> NIL do if connection^.hasrecvd then leave else connection := connection^.tc_next; if connection = NIL then tk_block else leave; END; {$IFC DEBUG} WriteLn('tcp_process: running....'); {$ENDC} WITH connection^,global^ do begin {$IFC DEBUG} WriteLn('tcp_process: avail: ',avail:1,', fin_offset: ',fin_offset:1); {$ENDC} if (avail >= 0) OR (fin_offset = -1) THEN BEGIN while in_more do begin {$IFC DEBUG} WriteLn('tcp_process: yielding due to in_more'); {$ENDC} tk_yield; end; hasrecvd := false; {$IFC DEBUG} WriteLn('tcp_process: about to dispose, avail = ',avail:1); {$ENDC} ticks := tickcount; FOR i := 0 TO avail DO BEGIN CALL1C(circbuf[(taken+1+i) mod BUFSIZE],tc_dispose); if tickcount > ticks then begin SystemTask; ticks := tickcount; end; END; FOR i := maxchunks DOWNTO 0 DO BEGIN if free[i] THEN BEGIN if i = maxchunks then maxchunks := maxchunks - 1; cycle; END; first[i] := first[i] - (avail + 1); last[i] := last[i] - (avail + 1); END; if fin_rcvd THEN fin_offset := (fin_offset - avail) - 1; taken := (taken + avail + 1) mod BUFSIZE; if fin_rcvd THEN BEGIN if fin_offset = -1 THEN BEGIN { foreign close request prompts connection state change } CASE (conn_state) OF ESTAB: BEGIN conn_state := FINRCVD; otp^.tc_fin := true; conn_state := R_AND_S; END; FINSENT: conn_state := SIMUL; FINACKED: conn_state := TIMEWAIT; {$IFC DEBUG} OTHERWISE BEGIN WriteLn('TCP_RCV: bad state: ', conn_state:1); END; {$ENDC} END; { end of CASE } { ack foreign close request } { fin's take up seq. no. space } otp^.tc_ack := otp^.tc_ack + 1; tcpbrcv := tcpbrcv + 1; send_wake(connection); END; END; otp^.tc_ack := otp^.tc_ack + avail + 1; tcpbrcv := tcpbrcv + avail + 1; otp^.tc_win := otp^.tc_win - (avail + 1); if (otp^.tc_win < TelLowWin) | (NOT in_more) THEN send_wake(connection); avail := -1; { make sure to avoid silly window syndrome } { this used to be done in tcp_send, but if there was nothing to send, TCP would stop accepting data until there was something to send! } if { (NOT still_data) & } (otp^.tc_win < TelLowWin) THEN otp^.tc_win := TelWin; Fake := tm_clear(tmack); dally_time := ack_time - TickCount; if dally_time > 0 THEN tm_tset(dally_time, @tcp_ack, POINTER(ORD4(connection)), tmack) ELSE send_wake(connection); END; end; { WITH } goto 666; END; { tcp_process } PROCEDURE send_wake(connection:Ref_tcconn); BEGIN connection^.mustsend := TRUE; tk_wake(global^.TCPSend); END; { This routine forms the main body of the TCP data sending task. This task is awakened for one of two reasons: someone has data to send, or a resend timeout has occurred and a retransmission is called for. This routine in either case finishes filling in the header of the output packet, and calls in_write to send it to the net. } PROCEDURE tcp_send(NoArg:PTR); LABEL 666; VAR sndlen: Integer; { bytes to send } Fake:Boolean; err:Integer; connection:Ref_tcconn; BEGIN tk_block; { Forget the initial wakeup } { track through the connection queue looking for data to send } 666: while true do begin connection := global^.tcpconns^.qHead; while connection <> NIL do if connection^.mustsend then leave else connection := connection^.tc_next; if connection = NIL then tk_block else leave; END; WITH connection^,global^ do begin connection^.mustsend := FALSE; if (conn_state = CLOSED) THEN GOTO 666; { If this is not a timeout, clear the resend timer } if newsend THEN { if there is new data to send } BEGIN Fake := tm_clear(tcptm); tm_tset(retry_time, @tmhnd, POINTER(ORD4(connection)), tcptm); END; if resend <> 0 THEN { if resend timer has gone off } BEGIN tcpresend := tcpresend + 1; if resend >= 12 THEN BEGIN if (NOT_YET(conn_state, ESTAB)) THEN begin CALL(tc_tfcn); { should be Boolean function } CALL(tc_cfcn); tcp_free(connection); goto 666; end else BEGIN NotResponding(StrCvt('TCP')); CALL(tc_cfcn); tcp_free(connection); goto 666; END; END else if (NOT_YET(conn_state, ESTAB)) THEN CALL(tc_rfcn); Fake := tm_clear(tcptm); tm_tset(retry_time,@tmhnd, POINTER(ORD4(connection)), tcptm) END; { if resend <> 0 then } { Calculate the number of bytes to send, keeping in mind the foreign host's window. If the connection isn't yet open, send no data at all. } IF NOT_YET(conn_state,ESTAB) THEN sndlen := 0 ELSE sndlen := odlen; { Finish filling in the TCP header } otp^.tc_thl := sizeof(tcp) div 4; ophp^.tp_len := sndlen + sizeof(tcp); otp^.tc_cksum := cksum(POINTER(ORD4(ophp)),sizeof(tcpph) div 2); otp^.tc_cksum := BitNOT(cksum(POINTER(ORD4(otp)), (sndlen+sizeof(tcp)+1) div 2)); {$IFC DEBUG} Write('Sending '); Print_TCP(otp); {$ENDC} { Send it } err := in_write(tcpfd, opbi, sndlen + sizeof(tcp), ForeignHost); if err <= 0 then begin Error(StrCvt('TELNET: Destination unreachable')); CALL(tc_cfcn); tcp_free(connection); goto 666; end; ack_time := TickCount + ACKDALLY; tcppsnt := tcppsnt + 1; tcpbsnt := otp^.tc_seq + sndlen; tcprack := tcpbrcv; if otp^.tc_rst THEN BEGIN { were we resetting? } cleanup(StrCvt('aborted')); CALL(tc_cfcn); tcp_free(connection); END else if conn_state = TIMEWAIT THEN { if we sent the last ack } BEGIN CALL(tc_cfcn); tcp_free(connection); END; end; { with } GOTO 666; END; { tcp_send } { Stuff a character into the send buffer for transmission, but do not wake up the TCP sending task. This assumes that more data will immediately follow. } FUNCTION tc_put(connection:Ref_tcconn; c: char): Boolean; VAR RawPtr: PTR; tmp: byte; BEGIN WITH connection^ do begin if NOT blk_inpt THEN BEGIN if (odlen >= MAXBUF) THEN BEGIN tc_put := TRUE; EXIT(tc_put); END else BEGIN RawPtr := POINTER(ORD4(odp)+odlen); RawPtr^ := byte(c); odlen := odlen + 1; RawPtr := POINTER(ORD4(odp)+odlen); RawPtr^ := 0; tc_put := FALSE; EXIT(tc_put); END END else BEGIN tc_put := TRUE; EXIT(tc_put); END; end;{with} END; { tc_put } { Stuff a character into the send buffer for transmission, and wake up the TCP sender task to send it. } FUNCTION tc_fput(connection:Ref_tcconn; c: char): Boolean; VAR RawPtr: PTR; BEGIN WITH connection^ do begin if(odlen >= MAXBUF) THEN BEGIN tc_fput := TRUE; EXIT(tc_fput); END; if NOT blk_inpt THEN BEGIN RawPtr := POINTER(ORD4(odp)+odlen); RawPtr^ := byte(c); odlen := odlen + 1; RawPtr := POINTER(ORD4(odp)+odlen); RawPtr^ := 0; END else BEGIN tc_fput := TRUE; EXIT(tc_fput); END; newsend := TRUE; send_wake(connection); tc_fput := FALSE; end;{with} END; { tc_fput } { Indicate the presence of urgent data. Just sets the urgent pointer to the current data length and wakes up the sender. } PROCEDURE tcpurgent(connection:Ref_tcconn); BEGIN WITH connection^ do begin otp^.tc_urg := odlen; otp^.tc_furg := true; send_wake(connection); end;{with} END; { tcpurgent } PROCEDURE TCP_Win(connection:Ref_tcconn; win:Integer; lowwin: Integer); BEGIN WITH connection^ do begin if (win<1) OR (win>TCPMAXWIND) OR (lowwin<1) OR (lowwin>TCPMAXWIND) THEN BEGIN TelWin := TCPWINDOW; TelLowWin := TCPLOWIND; END else BEGIN TelWin := win; TelLowWin := lowwin; END; end;{with} END; { Actively open a tcp connection to foreign host fh on foreign socket fs. Get a unique local socket to open the connection on. Returns FALSE if unable to open an internet connection with the specified hosts and sockets, or TRUE otherwise. Note that this routine does not wait until the connection is actually opened before returning. Instead, the user open function specified as ofcn in the call to tcp_init (which must precede this call) will be called when the connection is successfully opened. } FUNCTION tcp_open(fh: Ref_in_name; fs: unsigned; ls: unsigned; win:Integer; lowwin: Integer; ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr):Ref_tcconn; { *fh: in_name;} { foreign host address } { fs:unsigned;} { foreign socket } { ls:unsigned;} { local socket } { int win;} { window to advertise } { int lowwin;} { low water mark for window } { int (*ofcn)();} { user fcn to call on open done } { int (*infcn)();} { user fcn to process input data } { int (*yldfcn)();} { predicate set when user needs to run} { int (*cfcn)();} { user fcn to call on close done } { int (*tmofcn)();} { user fcn to call on icp timeout } { int (*rsdfcn)();} { user icp resend function } { int (*buff)();} { user output buffer room upcall } VAR Dummy:Boolean; connection:Ref_tcconn; BEGIN connection := tcp_alloc; if connection = NIL then begin CantAlloc(StrCvt('TCP'),StrCvt('connection')); tcp_open := NIL; exit(tcp_open); end; WITH connection^ do begin tc_ofcn := ofcn; { save user fcn addresses } tc_dispose := infcn; tc_yield := yldfcn; tc_cfcn := cfcn; tc_tfcn := tmofcn; tc_rfcn := rsdfcn; tc_buff := buff; ForeignHost := fh^; DestPort := fs; SourcePort := ls; if ls = 0 THEN BEGIN SourcePort := TickCount; if(SourcePort < 1000) THEN SourcePort := SourcePort + 1000; END; TCP_Win(connection,win,lowwin); conn_state := SYNSENT; { syn-sent } ophp^.tp_src := in_mymach(fh^); ophp^.tp_dst := fh^; otp^.tc_srcp := ls; otp^.tc_dstp := fs; otp^.tc_win := TelWin; { set resend timer } tm_tset(retry_time, @tmhnd, POINTER(ORD4(connection)), tcptm); newsend := TRUE; send_wake(connection); end;{with} tcp_open := connection; END; { tcp_open } { Passive open; new in Mac version } {$IFC LISTEN} FUNCTION tcp_listen(ls: unsigned; win:Integer; lowwin: Integer; ofcn,infcn,yldfcn,cfcn,tmofcn,rsdfcn,buff:ProcPtr):Ref_tcconn; { ls:unsigned;} { local socket } { int win;} { window to advertise } { int lowwin;} { low water mark for window } { int (*ofcn)();} { user fcn to call on open done } { int (*infcn)();} { user fcn to process input data } { int (*yldfcn)();} { predicate set when user needs to run} { int (*cfcn)();} { user fcn to call on close done } { int (*tmofcn)();} { user fcn to call on icp timeout } { int (*rsdfcn)();} { user icp resend function } { int (*buff)();} { user output buffer room upcall } VAR connection:Ref_tcconn; BEGIN connection := tcp_alloc; if connection = NIL then begin CantAlloc(StrCvt('TCP'),StrCvt('connection')); tcp_listen := NIL; exit(tcp_listen); end; WITH connection^ do begin tc_ofcn := ofcn; { save user fcn addresses } tc_dispose := infcn; tc_yield := yldfcn; tc_cfcn := cfcn; tc_tfcn := tmofcn; tc_rfcn := rsdfcn; tc_buff := buff; DestPort := 0; SourcePort := ls; TCP_Win(connection,win,lowwin); otp^.tc_win := TelWin; conn_state := LISTEN; end;{with} tcp_listen := connection; END; { tcp_listen } {$ENDC} { Display some tcp statistics and a few lines of unacked data. Should be revised and integrated in with the normal logging system. } { Changed this to go through the terminal emulator } PROCEDURE tc_status(connection:Ref_tcconn); LABEL 777; VAR i:Integer; { loop index } crseen:Integer; RawPtr:PTR; BEGIN if connection = NIL then exit(tc_status); WITH connection^,global^ do begin crseen := 0; EmStr('Connection State: '); CASE (conn_state) OF CLOSED: EmLn('Closed'); SYNSENT: EmLn('Trying to Open'); SYNRCVD: EmLn('Being Opened'); ESTAB: EmLn('Established'); {$IFC LISTEN} LISTEN: EmLn('Listening for connections'); {$ENDC} OTHERWISE BEGIN EmStr('('); NumToStr(conn_state,Msg); EmStr(Msg); EmLn(') Closing'); END; END; EmStr('Packets Sent: '); NumToStr(tcppsnt,Msg);EmStr(Msg); EmStr('; Packets Received: '); NumToStr(tcpprcv,Msg);EmLn(Msg); EmStr('Bytes Sent: '); NumToStr(tcpbsnt,Msg);EmStr(Msg); EmStr(' (Acked: '); NumToStr(otp^.tc_seq,Msg);EmStr(Msg); EmLn(')'); EmStr('Bytes Received: '); NumToStr(tcpbrcv,Msg);EmStr(Msg); EmStr(' (Acked: '); NumToStr(tcprack,Msg);EmStr(Msg); EmLn(')'); EmStr('Bad TCP xsums: '); NumToStr(bd_chk,Msg);EmStr(Msg); EmStr('; Window ignored: '); NumToStr(ign_win,Msg);EmLn(Msg); EmStr('Packets not for me: '); NumToStr(tcpsock,Msg);EmStr(Msg); EmStr('; Resends: '); NumToStr(tcpresend,Msg);EmStr(Msg); EmStr('; Rereceived: '); NumToStr(tcprercv,Msg);EmLn(Msg); EmStr('Local Win: '); NumToStr(otp^.tc_win,Msg);EmStr(Msg); EmStr('; Local Low Win: '); NumToStr(TelLowWin,Msg);EmStr(Msg); EmStr('; Foreign Win: '); NumToStr(frn_win,Msg);EmLn(Msg); EmStr('Ack #: '); NumToStr(otp^.tc_ack,Msg);EmStr(Msg); EmStr(', Seq #: '); NumToStr(otp^.tc_seq,Msg);EmLn(Msg); EmStr('Output Flags:'); if(otp^.tc_syn) THEN EmStr(' SYN'); if(otp^.tc_fack) THEN EmStr(' ACK'); if(otp^.tc_psh) THEN EmStr(' PSH'); if(otp^.tc_furg) THEN EmStr(' URG'); if(otp^.tc_fin) THEN EmStr(' FIN'); if(otp^.tc_rst) THEN EmStr(' RST'); EmLn('.'); if(odlen <> 0) THEN EmLn('Output data:') else EXIT(tc_status); i:=0; while(true) DO BEGIN RawPtr := POINTER(ORD4(odp)+i); if RawPtr^ = $d THEN crseen := crseen + 1; if (RawPtr^ >= 32) AND (RawPtr^ < 127) then Em(chr(RawPtr^)) else if RawPtr^ = $d then EmLn('') else EmStr('.'); if crseen > 3 THEN GOTO 777 ELSE BEGIN i := i + 1; if (i > 100) OR (i > odlen) THEN GOTO 777 END; END; 777: EmLn(''); if i <= odlen THEN BEGIN EmLn('***MORE DATA***'); END; end;{with} em_flush; END; { tcp_status } { expedite (resend and push) a packet } PROCEDURE tcp_ex(connection:Ref_tcconn); BEGIN send_wake(connection); connection^.otp^.tc_psh := true; END; PROCEDURE tcp_ack(connection:Ref_tcconn); BEGIN send_wake(connection); END; { tcp_ack} { Handle a timer upcall. } PROCEDURE tmhnd(connection:Ref_tcconn); BEGIN connection^.resend := connection^.resend + 1; send_wake(connection); END; { tmhnd } FUNCTION tcp_sock: unsigned; VAR temp: LongInt; BEGIN temp := TickCount; temp := BitAnd(temp,$0000ffff); if (temp < 1000) THEN temp := temp + 1000; tcp_sock := temp; END; { tcp_sock} {$IFC SAVEREST} VAR SeqNum:Integer; AckNum:Integer; { restore a suspended tcp connection } PROCEDURE tcp_restore; BEGIN conn_state := ESTAB; tcpfd := in_open(TCPPROT, @tcp_rcv); if (tcpfd = NIL) THEN BEGIN CantConnect('tcp_restore','Internet'); CALL(tc_cfcn); tcp_free(connection); EXIT(tcp_restore); END; ophp^.tp_src := in_mymach(ForeignHost); ophp^.tp_dst := ForeignHost; otp^.tc_srcp := SourcePort; otp^.tc_dstp := DestPort; otp^.tc_seq := SeqNum; otp^.tc_ack := AckNum-1; otp^.tc_win := TelWin; otp^.tc_bits := 0; newsend := TRUE; send_wake(connection); Write('foreign host '); out_inaddr(ForeignHost); WriteLn(', foreign port ',DestPort:1,', local port ',SourcePort:1); WriteLn('ack # is ',AckNum:1,' and seq # is ',SeqNum:1); CALL(tc_ofcn); END; { tcp_restore } FUNCTION tcp_save { : Boolean } ; VAR OSStatus: OSErr; FRN: Integer; RLength: LongInt; CR:CustRecord; BEGIN OSStatus := FSOpen(CFileName, {Current Vol} 0, FRN); if( OSStatus <> noErr ) THEN tcp_save := FALSE else BEGIN RLength := sizeof(CustRecord); CR.SeqNum := otp^.tc_seq; CR.AckNum := otp^.tc_ack; CR.LocalIPAddr := LocalIPAddress; CR.GateWIPAddr := GWIPAddress; CR.NameServer := NSIPAddress[0]; CR.TimeServer := TSIPAddress[0]; CR.UseAB := Use_AB; CR.UserName := User_Name; CR.SourcePort := SourcePort; CR.DestPort := DestPort; CR.TelLowWin := TelLowWin; CR.ForeignHost := ForeignHost; { Get to the start of the file } OSStatus := SetFPos(FRN,fsFromStart,0); IF OSStatus = noErr THEN OSStatus := FSWrite(FRN, RLength, @CR); OSStatus := FSClose(FRN); tcp_save := TRUE END END; { tcp_save } {$ENDC} END. !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting net/tftp_lib.text... cat >net/tftp_lib.text <<'!E!O!F!' {$X-} {$M+} {$R-} {$0V-} {$D-} {$DECL DEBUG} {$SETC DEBUG := false} {$DECL ALLOCT} {$SETC ALLOCT := false} {$DECL BUNDLE} {$SETC BUNDLE := true} UNIT TFTP_Lib; { Please note the copyright notice in the file "copyright/notice" } { TFTP_LIB module using UDP over the Applebus } { by Mark Sherman (Dartmouth) and Tim Maroney (C-MU) } INTERFACE {$L-} USES {$U-} {$U Obj-Memtypes } Memtypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-ABPasIntf } ABPasIntf, {$U net-Task_Lib } Task_Lib, {$U net-Timer_Lib } Timer_Lib, {$U net-err_lib } Err_Lib, {$U net-IP_Lib } IP_Lib, {$U net-UDP_Lib } UDP_Lib, {$U net-calls } Call_Lib, {$U net-tftp_defs } TFTP_Defs, {$U net-tftp_file } TFTP_File; {$L+} CONST TFTKSZ = 2048; PROCEDURE tftpinit; PROCEDURE tfsinit(alert: ProcPtr; done:ProcPtr); FUNCTION tftpuse(fhost: in_name; fname: StringPtr; volume:Integer; rmfile: StringPtr; dir: integer; mode: integer; userDone: ProcPtr): LongInt; PROCEDURE tfs_on; PROCEDURE tfs_off; PROCEDURE tfabort; IMPLEMENTATION CONST MINTICKS = 10*60; { read or write request packet } CONST MaxFileName = 512; tf_name_offset = 2; TYPE tfreq = PACKED RECORD {+0} tf_op: integer; { would be 1 (read) or 2 (write) } {+2} tf_name: packed array[0..MaxFileName] of byte; END; Ref_tfreq = ^ tfreq; { data packet } tfdata = PACKED RECORD { +0 } tf_op: integer; { would be 3 } { +2 } tf_block: integer; { +4 } tf_data: packed array [0..511] of byte; END; Ref_tfdata = ^ tfdata; { finder information packet } tffinder = PACKED RECORD { +0 } tf_op: Integer; { would be 3 } { +2 } tf_block: Integer; { would be 1 } { +4 } tf_finder: FInfo; END; Ref_tffinder = ^ tffinder; { structure of an ack packet } tfack = PACKED RECORD tf_op: integer; { would be 4 } tf_block: integer; END; Ref_tfack = ^ tfack; Ref_LongInt = ^ LongInt; CONST { TFTP states } DATAWAIT = 1; ACKWAIT = 2; DEAD = 3; TIMEOUT = 4; RCVERR = 5; RCVACK = 6; RCVDATA = 7; RCVLASTDATA = 8; TERMINATED = 9; TFTPSOCK = 69 { TFTP's well known port }; TFTPTRIES = 10 { # of retries on packet transmission }; REQTRIES = 4 { # of retries on initial request }; NORMLEN = 512 { normal length of received packet }; { Constants for round trip time estimation and retry timeout. All calculation is done in clock ticks (at a rate of 18/second) but only the initial estimate and the upper limit are specified in ticks; the rest of the algorithm uses dimensionless multipliers. } { Added multiplier of 4 for Macintosh (60 ticks/second) } Kinit = 3*4; { Old = 3; Initial divisor for (1+1/K) estimate multiplier. } Kinc = 1; { Reduce K by this if previous packet lost. } T0 = 36; { Initial value for round trip time estimate. } MAXTMO = 4*216; { Old=216; upper limit on retry timeout timer, in ticks.} TMMULT = 3; { multiplier to get retry timeout from round trip estimate. } OFF = 0; ON = 1; MAXTFTPS = 1; VAR tfs_alert,server_done: ProcPtr; tftp: UDPCONN; tfstate: Byte; tfconnq:QHdr; { TFTP connection queue } PROCEDURE tfrpyerr(udpc:UDPCONN; p: PACKET; code:Integer; text: StringPtr); FORWARD; FUNCTION tfcleanup(cn: Ref_tfconn): LongInt; FORWARD; PROCEDURE tfshnd(p:PACKET; len:Integer; host: in_name; Foo_Value:PTR); FORWARD; PROCEDURE tftpwrit(cn: Ref_tfconn); FORWARD; PROCEDURE tftpread(cn: Ref_tfconn); FORWARD; PROCEDURE tftprcv(p:PACKET; len: Integer; fhost: in_name; cn: Ref_tfconn); FORWARD; FUNCTION tfmkcn(dir:Integer; mode:Integer): Ref_tfconn; FORWARD; PROCEDURE tfsndack(cn:Ref_tfconn; number: integer); FORWARD; FUNCTION tfsndata(cn: Ref_tfconn; len: integer): Integer; FORWARD; FUNCTION tfsndreq(cn: Ref_tfconn; fname: StringPtr): Integer; FORWARD; FUNCTION tf_write(cn: Ref_tfconn; len: integer): Integer; FORWARD; PROCEDURE tftptmo(cn: Ref_tfconn); forward; PROCEDURE tfdoerr(cn: Ref_tfconn; p:PACKET; len:Byte); forward; {$IFC DEBUG} PROCEDURE tfcndump(cn: Ref_tfconn); FORWARD; PROCEDURE out_inaddr(addr:in_name); VAR tmp:STR255; BEGIN cvt_inaddr(addr,tmp); Write(tmp); END; {$ENDC} {$IFC ALLOCT} PROCEDURE NoteAlloc(p:PACKET; s:STR255); BEGIN WriteLn(s,': ',ORD4(p)); END; {$ENDC} {$S InitSeg} PROCEDURE tftpinit; BEGIN ntftps := 0; tfconnq.qFlags := 0; tfconnq.qHead := NIL; tfconnq.qTail := NIL; END; {$S TFTPSeg } PROCEDURE tfkill(cn: Ref_tfconn); VAR Dummy: Boolean; BEGIN Dummy := tm_clear(cn^.tf_tm); cn^.tf_state := DEAD; tk_wake(cn^.tf_task); END; { tfkill } { abort all TFTP connections } PROCEDURE tfabort; VAR cn:Ref_tfconn; Dummy: Boolean; BEGIN cn := POINTER(ORD4(tfconnq.qHead)); while (cn <> NIL) do begin if cn^.tf_rcv >= 1 then begin {$IFC DEBUG} WriteLn('tfabort: aborting connection in progress'); {$ENDC} tfudperr(cn^.tf_udp, cn^.tf_outp, ERRTXT, StrCvt('Connection aborted')); tfkill(cn); end else begin { I couldn't get this to work, so it's disallowed } Error(StrCvt( 'Sorry, you can''t abort a connection request.')); end; cn := cn^.tf_next; end; END; FUNCTION tftp_data(p:Packet): PTR; VAR Temp: Ref_tfdata; BEGIN Temp := POINTER(ORD4(tftp_head(p))); tftp_data := POINTER( ORD4(temp) + { offset for tf_data } 4 ); END; { tftp_data } { turn the tftp server on} PROCEDURE tfs_on; BEGIN tfstate := ON; END; { End of tfs_on } { turn the tftp server off } PROCEDURE tfs_off; BEGIN tfstate := OFF; END; { end of tfs_off } { tfsinit(alert, done) - initialize the tftp server. This opens a UDP connection but does not turn on the server. That needs to done by an explicit call to tfs_on(). alert() is a function which the server will call whenever it receives request for a transfer. This function will be called in the following way: alert(ip_addr, file_name, direction) alert() should return TRUE if it wishes to allow the transfer and FALSE otherwise. done() is a function that the server will call to inform the invoker that this file transfer is complete or aborted. } PROCEDURE tfsinit(alert: ProcPtr; done:ProcPtr); BEGIN tfstate := OFF; refusedt := 0; {time of most recent transfer refusal} tftp := udp_open(0, 0, TFTPSOCK, @tfshnd, NIL); IF (tftp=NIL) THEN Fatal(StrCvt('Can''t open UDP socket'),false); server_done := done; tfs_alert := alert; END; { End of tfsinit } { handle an incoming tftp packet. This involves opening a udp connection (immediately so that we can report errors). If the server is OFF then the tftp will be refused; otherwise more checking will be done. Call the alert function and verify that the 'user' wishes to allow the tftp. Report an error if not. Finally, spawn a task to oversee the tftp and cleanup when it's done. } PROCEDURE tfshnd(p:PACKET; len:Integer; host: in_name; Foo_Value:PTR); CONST Ignore_Case = FALSE; No_Diacrit = TRUE; VAR nport: integer; ptreq: Ref_tfreq; cn: Ref_tfconn; mode: integer; FPart: tf_FilePart; smode,fname: STR255; FPartName: STRING[15]; pup: Ref_udp; tmpudp: UDPCONN; TransDir: Integer; LongDummy: LongInt; OSStatus: OSErr; {$IFC DEBUG} TempPtr: Ref_TFTP; TP2: Ref_Udp; {$ENDC} BEGIN CheckTask; {$IFC DEBUG} WriteLn('tfshnd: entering tf server handler'); WriteLn('Packet pointer: ',ORD4(p)); WriteLn('Length : ',len); { Write('Host : ');out_inaddr(host);WriteLn; } WriteLn('Useless data : ',ORD4(Foo_Value)); Write('TFTP Header at: '); TempPtr := tftp_head(p); IF TempPtr <> NIL THEN Write(ORD4(TempPtr)) ELSE Write(' NIL '); WriteLn(''); IF TempPtr <> NIL THEN BEGIN WriteLn('OPCde :',TempPtr^.tf_op); WriteLn('Block :',TempPtr^.tf_block); END; TP2 := udp_head(in_head(p)); Write('UDP Header at : '); IF TP2 <> NIL THEN Write(ORD4(TP2)) ELSE Write(' NIL '); WriteLn(''); IF TP2 <> NIL THEN BEGIN WriteLn(' ud_srcp ',TP2^.ud_srcp); WriteLn(' ud_dstp ',TP2^.ud_dstp); WriteLn(' ud_len ',TP2^.ud_len); WriteLn(' ud_cksum',TP2^.ud_cksum); END; {$ENDC} pup := udp_head(in_head(p)); { If there is already a connection like this one, ignore duplicate request. } if (udp_ckcon(host, pup^.ud_srcp) <> NIL) THEN BEGIN {$IFC DEBUG} WriteLn('tfshnd: udp_ckcon <> NIL'); {$ENDC} {$IFC ALLOCT} Write('tfshnd 1: '); {$ENDC} udp_free(p); exit(tfshnd); END; { If we refused a connection since this request got enqueued, assume this is a duplicate and discard it, so we don't bother the user with a duplicate question. (If we are unlucky, this might be a request from somewhere else that arrived while the user was thinking over the previous request. Tough; somewhere else will just have to try again.) } if (refusedt > p^.nb_tstamp) THEN BEGIN {$IFC DEBUG} WriteLn('tfshnd: refusedt > p^.nb_tstamp'); {$ENDC} {$IFC ALLOCT} Write('tfshnd 2: '); {$ENDC} udp_free(p); exit(tfshnd); END; { The next question: Do we have room to do this transfer? } if (ntftps >= MAXTFTPS) THEN BEGIN tk_yield; { maybe the connection is waiting to die } if (ntftps >= MAXTFTPS) THEN BEGIN tfrpyerr(tftp, p, ERRTXT, StrCvt('Too many connections.')); {$IFC ALLOCT} Write('tfshnd 3: '); {$ENDC} udp_free(p); exit(tfshnd); END; END; ntftps := ntftps + 1; { OK, let's check over the request more carefully. } ptreq := POINTER(ORD4(tftp_head(p))); ptreq^.tf_op := { bswap } (ptreq^.tf_op); { Swapping unnecssary on 68000 } IF (ptreq^.tf_op > WRQ) THEN BEGIN {$IFC DEBUG} WriteLn('TFTP SERVER: bad tftp opcode ',ptreq^.tf_op); {$ENDC} {$IFC ALLOCT} Write('tfshnd 4: '); {$ENDC} udp_free(p); ntftps := ntftps - 1; exit(tfshnd); END; { Extract the file name from the request } CStr2PStr({ @ptreq^.tf_name} POINTER(ORD4(ptreq) + tf_name_offset ) ,@fname); { Extract the transfer mode from the request } CStr2PStr(POINTER({Start of buffer } ORD4(ptreq) + tf_name_offset + {Characters of file name} Length(fname) + {Null byte terminating fname} 1 ), @smode); FPart := tf_DataPart; { We assume unless Mac mode } if EqualString(smode,'octet',Ignore_Case,No_Diacrit) then begin mode := IMAGE; FPart := tf_RsrcPart; end else if EqualString(smode,'image',Ignore_Case,No_Diacrit) THEN begin mode := IMAGE; FPart := tf_RsrcPart; end else if EqualString(smode,'netascii',Ignore_Case,No_Diacrit) THEN mode := ASCII else if EqualString(smode,'macintosh',Ignore_Case,No_Diacrit) THEN BEGIN mode := MACINTOSH; FPart := tf_FindPart; END else BEGIN {$IFC DEBUG} Write('TFTP SERVER: Bad mode ',smode,' in req '); {$ENDC} tfrpyerr(tftp, p, ERRTXT, StrCvt('Bad mode')); {$IFC ALLOCT} Write('tfshnd 6: '); {$ENDC} udp_free(p); ntftps := ntftps - 1; exit(tfshnd); END; IF (tfstate=OFF) THEN BEGIN tfrpyerr(tftp, p, ERRTXT, StrCvt('Transfers are not being accepted.')); {$IFC ALLOCT} Write('tfshnd 7: '); {$ENDC} udp_free(p); ntftps := ntftps - 1; exit(tfshnd); END; IF ptreq^.tf_op = RRQ THEN TransDir := PUT ELSE TransDir := GET; if CALL3A(host, @fname, TransDir, tfs_alert)=0 THEN BEGIN tfrpyerr(tftp, p, ERRTXT,StrCvt('Transfer refused.')); refusedt := TickCount; {$IFC ALLOCT} Write('tfshnd 8: '); {$ENDC} udp_free(p); ntftps := ntftps - 1; exit(tfshnd); END; { It looks safe to try to open a connection. } cn := tfmkcn(PUT, ASCII); { Direction is a dummy for now } if (cn=NIL) THEN BEGIN CantAlloc(StrCvt('TFTP'),StrCvt('connection')); {$IFC ALLOCT} Write('tfshnd 9: '); {$ENDC} udp_free(p); ntftps := ntftps - 1; exit(tfshnd); END; cn^.tf_done := server_done; cn^.tf_fn := fname; cn^.tf_fp := FPart; cn^.tf_volume := 0; cn^.tf_mode := mode; cn^.tf_udp := udp_open(host, pup^.ud_srcp, udp_socket, @tftprcv, POINTER(ORD4(cn))); if (cn^.tf_udp=NIL) THEN BEGIN CantConnect(StrCvt('TFTP'),StrCvt('UDP')); LongDummy := tfcleanup(cn); {$IFC ALLOCT} Write('tfshnd 10: '); {$ENDC} udp_free(p); ntftps := ntftps - 1; exit(tfshnd); END; IF (ptreq^.tf_op=RRQ) THEN BEGIN cn^.tf_dir := PUT; cn^.tf_fport := 1; cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioPermssn := fsRdPerm; { Only need to read it } cn^.tf_PB^.ioMisc := NIL; CASE cn^.tf_fp OF tf_DataPart: BEGIN OSStatus := PBOpen(cn^.tf_PB, FALSE); IF OSStatus = noErr THEN begin if ForkZero(cn) then OSStatus := EOFErr else begin cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB,FALSE); end; end; END; tf_RsrcPart: BEGIN OSStatus := PBOpenRF(cn^.tf_PB, FALSE); IF OSStatus = noErr THEN begin if ForkZero(cn) then OSStatus := EOFErr else begin cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB,FALSE); end; end; END; tf_FindPart: BEGIN { Just see if file exists, don't really need to open it } OSStatus := PBOpen(cn^.tf_PB, FALSE); IF OSStatus = noErr THEN OSStatus := PBClose(cn^.tf_PB,FALSE); END; END; { end of case } if NOT OpenOK(cn,OSStatus,p) THEN BEGIN tfudperr(cn^.tf_udp, cn^.tf_outp, FNOTFOUND, StrCvt('Could not open the file')); CALL1B(OFF, cn^.tf_done); LongDummy := tfcleanup(cn); exit(tfshnd); end; cn^.tf_task := tk_fork(tk_cur, @tftpread, TFTKSZ, 'tfrd', POINTER(ORD4(cn))); END else BEGIN cn^.tf_dir := GET; cn^.tf_fport := 1; cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioPermssn := fsRdWrPerm; cn^.tf_PB^.ioMisc := NIL; { See if file already there; yes, truncate it, else create it } CASE cn^.tf_fp OF tf_DataPart: BEGIN OSStatus := PBOpen(cn^.tf_PB,FALSE); ScratchIt(cn,OSStatus); END; tf_RsrcPart: BEGIN OSStatus := PBOpenRF(cn^.tf_PB, FALSE); ScratchIt(cn,OSStatus); END; tf_FindPart: BEGIN { Do not need to open it, but if not available, it must be created } OSStatus := PBOpen(cn^.tf_PB,FALSE); IF OSStatus = noErr THEN OSStatus := PBClose(cn^.tf_PB,FALSE) ELSE IF OSStatus = fnfErr THEN BEGIN cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; OSStatus := PBCreate(cn^.tf_PB,FALSE); { Finder info will be filled in later } END; END; { end of finder part } END; { end of case statement } if NOT OpenOK(cn,OSStatus,p) then BEGIN tfudperr(cn^.tf_udp, cn^.tf_outp, ACCESS, StrCvt('Could not open the file')); CALL1B(OFF, cn^.tf_done); LongDummy := tfcleanup(cn); exit(tfshnd); end; cn^.tf_task := tk_fork(tk_cur, @tftpwrit, TFTKSZ, 'tfwr', POINTER(ORD4(cn))); tk_yield; { let it get started } cn^.tf_expected := 1; tfsndack(cn, 0); END; {$IFC ALLOCT} Write('tfshnd 13: '); {$ENDC} udp_free(p); END; { end of tfshnd } { User TFTP: attempt to transmit or receive a file in the specified mode. } FUNCTION tftpuse(fhost: in_name; fname: StringPtr; volume:Integer; rmfile: StringPtr; dir: integer; mode: integer; userDone: ProcPtr): LongInt; VAR cn: Ref_tfconn; mysock: integer; FIPtr: ^ FInfo; len: LongInt; Status: Integer; LongDummy: LongInt; OSStatus: OSErr; BEGIN {$IFC DEBUG} Write('TFTP_USER called on '); out_inaddr(fhost); WriteLn(fname^,',',rmfile^,',',dir,',',mode,'.'); {$ENDC} cn := tfmkcn(dir, mode); if(cn=NIL) THEN BEGIN CantAlloc(StrCvt('TFTP'),StrCvt('connection')); tftpuse := 0; EXIT(tftpuse); END; ntftps := ntftps + 1; cn^.tf_done := userDone; cn^.tf_fn := fname^; cn^.tf_volume := volume; cn^.tf_mode := mode; if mode = MACINTOSH then cn^.tf_fp := tf_FindPart else if mode in [IMAGE, OCTET] then cn^.tf_fp := tf_RsrcPart else cn^.tf_fp := tf_DataPart; if (dir=GET) THEN BEGIN if mode IN [IMAGE, ASCII, OCTET, MACINTOSH] THEN BEGIN cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioPermssn := fsRdWrPerm; cn^.tf_PB^.ioMisc := NIL; { Try to open the file for writing, since we will try to get this file } CASE cn^.tf_fp OF tf_DataPart: BEGIN OSStatus := PBOpen(cn^.tf_PB,FALSE); ScratchIt(cn,OSStatus); END; tf_RsrcPart: BEGIN OSStatus := PBOpenRF(cn^.tf_PB, FALSE); ScratchIt(cn,OSStatus); END; tf_FindPart: BEGIN OSStatus := PBOpen(cn^.tf_PB,false); IF OSStatus = noErr THEN OSStatus := PBClose(cn^.tf_PB,FALSE) ELSE IF OSStatus = fnfErr THEN BEGIN cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; OSStatus := PBCreate(cn^.tf_PB,FALSE); { Finder info filled in later } END; END; { end of finder part } END; { end of CASE on file part } END { end of ASCII, OCTET and IMAGE modes } {$IFC DEBUG} else if (mode=TEST) THEN BEGIN WriteLn('tftpuse: TEST mode not supported '); LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END {$ENDC} else BEGIN {$IFC DEBUG} WriteLn('TFTP_USER: Bad mode ',mode,'.'); {$ENDC} LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END; if (OSStatus <> noErr) THEN BEGIN FOpenErr(fname,OSStatus); LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END; mysock := udp_socket; cn^.tf_udp := udp_open(fhost, TFTPSOCK, mysock, @tftprcv, POINTER(ORD4(cn))); if (cn^.tf_udp=NIL) THEN BEGIN CantConnect(StrCvt('TFTP'),StrCvt('UDP')); LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END; cn^.tf_task := tk_cur; cn^.tf_fport := 0; cn^.tf_expected := 1; cn^.tf_task := tk_fork(tk_cur, @tftpwrit, TFTKSZ, 'tfwr', POINTER(ORD4(cn))); Status := tfsndreq(cn, rmfile); if Status < 0 then begin Error(StrCvt('TFTP: Destination unreachable')); LongDummy := tfcleanup(cn); tftpuse := 0; end else tftpuse := 1; exit(tftpuse); END { end of GET test } else if (dir=PUT) THEN BEGIN if mode in [IMAGE, ASCII, OCTET, MACINTOSH] THEN BEGIN cn^.tf_PB^.ioNamePtr := @cn^.tf_fn; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; cn^.tf_PB^.ioVersNum := 0; cn^.tf_PB^.ioPermssn := fsRdPerm; cn^.tf_PB^.ioMisc := NIL; CASE cn^.tf_fp OF tf_DataPart: { Open a file for reading } begin OSStatus := PBOpen(cn^.tf_PB,FALSE); IF OSStatus = noErr THEN begin IF ForkZero(cn) THEN OSStatus := EOFErr else BEGIN cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB, FALSE); END; end; end; tf_RsrcPart: BEGIN OSStatus := PBOpenRF(cn^.tf_PB, FALSE); IF OSStatus = noErr THEN begin IF ForkZero(cn) THEN OSStatus := EOFErr else BEGIN cn^.tf_PB^.ioPosMode := fsFromStart; cn^.tf_PB^.ioPosOffset := 0; OSStatus := PBSetFPos(cn^.tf_PB, FALSE); END; end; END; tf_FindPart: BEGIN { Just see if file exists, don't really need to open it } OSStatus := PBOpen(cn^.tf_PB,FALSE); IF OSStatus = noErr THEN OSStatus := PBClose(cn^.tf_PB,FALSE); END; { end of finder part} END; { end of case } if NOT OpenOK(cn,OSStatus,NIL) then begin LongDummy := tfcleanup(cn); tftpuse := 0; exit(tftpuse); end; END { end of mode test } else BEGIN {$IFC DEBUG} WriteLn('Invalid mode for put.'); {$ENDC} LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END; if(OSStatus <> noErr) THEN BEGIN FOpenErr(fname,OSStatus); LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END; mysock := udp_socket; cn^.tf_udp := udp_open(fhost, TFTPSOCK, mysock, @tftprcv, POINTER(ORD4(cn))); if (cn^.tf_udp=NIL) THEN BEGIN CantConnect(StrCvt('TFTP'),StrCvt('UDP')); OSStatus := PBClose(cn^.tf_PB,FALSE); LongDummy := tfcleanup(cn); tftpuse := 0; EXIT(tftpuse); END; cn^.tf_task := tk_cur; cn^.tf_expected := 0; cn^.tf_fport := 0; while cn^.tf_tries <> 0 do begin Status := tfsndreq(cn, rmfile); if Status < 0 then begin Error(StrCvt('TFTP: Destination unreachable')); LongDummy := tfcleanup(cn); tftpuse := 0; exit(tftpuse); end else tftpuse := 1; cn^.tf_state := ACKWAIT; tk_block; if cn^.tf_state <> TIMEOUT then leave; end; if cn^.tf_tries = 0 then begin Error(StrCvt('TFTP: Request to send not acknowledged')); LongDummy := tfcleanup(cn); tftpuse := 0; exit(tftpuse); end else if cn^.tf_state = DEAD then begin LongDummy := tfcleanup(cn); tftpuse := 0; exit(tftpuse); end; cn^.tf_task := tk_fork(tk_cur, @tftpread, TFTKSZ, 'tfrd', POINTER(ORD4(cn))); tftpuse := 1; exit(tftpuse); END; END; { end of tftpuse } { Task that receives a file from the remote system } PROCEDURE tftpwrit(cn: Ref_tfconn); VAR Len: LongInt; Status: Integer; Dummy: Boolean; BEGIN while(True) DO BEGIN cn^.tf_state := DATAWAIT; while (cn^.tf_state=DATAWAIT) DO tk_block; {$IFC DEBUG} WriteLn('tftpwrit: awakened'); {$ENDC} if (cn^.tf_state=TIMEOUT) THEN BEGIN if (cn^.tf_tries > 0) THEN BEGIN Status := udp_write(cn^.tf_udp, cn^.tf_outp, cn^.tf_lastlen); Dummy := tm_clear(cn^.tf_tm); tm_tset(cn^.tf_rt, @tftptmo, POINTER(ORD4(cn)), cn^.tf_tm); cycle; END ELSE BEGIN NotResponding(StrCvt('TFTP')); if cn^.tf_rcv >= NORMLEN then { in progress } tfudperr(cn^.tf_udp, cn^.tf_outp, ERRTXT, StrCvt('Too many retries, giving up')); CALL1B(OFF, cn^.tf_done); len := tfcleanup(cn); ntftps := ntftps - 1; leave; END; END; if (cn^.tf_state = RCVLASTDATA) THEN BEGIN { unless in Mac mode, have sent last data } if (cn^.tf_mode = MACINTOSH) AND (cn^.tf_fp <> tf_DataPart) THEN BEGIN cn^.tf_state := RCVDATA; if NOT MacPart(cn,1) then begin CALL1B(OFF, cn^.tf_done); Len := tfcleanup(cn); leave; end; END ELSE BEGIN Dummy := tm_clear(cn^.tf_tm); CALL1B(ON, cn^.tf_done); Len := tfcleanup(cn); if (len=0) THEN Error(StrCvt('Tried to transfer zero-length file')); ntftps := ntftps - 1; leave; END END; if (cn^.tf_state <> RCVDATA) THEN BEGIN Dummy := tm_clear(cn^.tf_tm); CALL1B(OFF, cn^.tf_done); len := tfcleanup(cn); ntftps := ntftps - 1; leave; END; END; { end of the WHILE loop } tk_exit; END; { end of tfswrit } { Task that sends a file to the remote system } { This procedure was incorrect. It leaked storage and timed out improperly. It has been extensively rewritten. -- Tim } PROCEDURE tftpread(cn: Ref_tfconn); LABEL 666; { timeout retry } VAR data: PTR; flen: LongInt; FIPtr: ^ FInfo; done: Boolean; pfill,psnt,tmp: PACKET; Status: Integer; LongDummy: LongInt; FinderInfo: FInfo; { For macintosh mode transfers } OSStatus : OSErr; Dummy: Boolean; NEEDLF: Boolean; BEGIN NEEDLF := FALSE; flen := NORMLEN; done := FALSE; cn^.tf_expected := 0; psnt := cn^.tf_outp; pfill := udp_alloc(512,0); {$IFC ALLOCT} NoteAlloc('tftpread',pfill); {$ENDC} if pfill = NIL then BEGIN CantAlloc(StrCvt('TFTP'),StrCvt('fill packet')); ntftps := ntftps - 1; tk_exit; end; { Here's how the main loop for putting data blocks works: Fill the next packet to send Wait for ACK (of initial request or previous block) On timeout, resend and restart ACK wait, unless too many, in which case abort On ACK receipt, send the filled packet, reset the filled packet to be the old packet (which is no longer in use, having been ACKED), and go back up to Fill (unless done, in which case, wrap up [or restart if in Mac mode]) } while(true) DO BEGIN cn^.tf_state := ACKWAIT; CASE cn^.tf_fp of tf_DataPart, tf_RsrcPart: if cn^.tf_mode <> ASCII then begin cn^.tf_PB^.ioBuffer := tftp_data(pfill); cn^.tf_PB^.ioReqCount := NORMLEN; cn^.tf_PB^.ioPosMode := fsAtMark; OSStatus := PBRead(cn^.tf_PB,FALSE); flen := cn^.tf_PB^.ioActCount; end else begin { ASCII mode; have to translate CR to CRLF } flen := NORMLEN; data := tftp_data(pfill); if NEEDLF then begin NEEDLF := false; data^ := $0a; { LF } flen := flen - 1; data := POINTER(ORD4(data)+1); end; cn^.tf_PB^.ioPosMode := $0d80; while flen > 0 do begin { Read one line at a time } cn^.tf_PB^.ioBuffer := data; cn^.tf_PB^.ioReqCount := flen; OSStatus := PBRead(cn^.tf_PB,FALSE); flen := flen - cn^.tf_PB^.ioActCount; if OSStatus <> noErr then leave; data := POINTER(ORD4(data) + (cn^.tf_PB^.ioActCount-1)); if data^ = $0d { CR } then begin if flen = 0 then NEEDLF := true else begin data := POINTER(ORD4(data) + 1); data^ := $0a; { LF } flen := flen - 1; data := POINTER(ORD4(data) + 1); end; end; end; { while flen > 0 } flen := NORMLEN - flen; end; { ASCII mode } tf_FindPart: BEGIN IF (cn^.tf_expected = 0) THEN BEGIN FIPtr := POINTER(ORD4(tftp_data(pfill))); OSStatus := GetFInfo(cn^.tf_fn,cn^.tf_volume,FIPtr^); flen := sizeof(FInfo); END ELSE BEGIN flen := 0; OSStatus := eofErr; END; END; END; { end of the case } IF (OSStatus <> noErr) AND (OSStatus <> eofErr) THEN BEGIN { For some reason our read died! } { Just say that nothing is left and that EOF reached } FReadErr(StrCvt(cn^.tf_fn),OSStatus); OSStatus := eofErr; flen := 0; END; cn^.tf_size := cn^.tf_size + flen; if (cn^.tf_expected = 0) THEN cn^.tf_state := RCVACK; 666: while (cn^.tf_state=ACKWAIT) DO tk_block; if (cn^.tf_state=TIMEOUT) THEN BEGIN if (cn^.tf_tries > 0) THEN BEGIN Status := udp_write(cn^.tf_udp,psnt, cn^.tf_lastlen); Dummy := tm_clear(cn^.tf_tm); tm_tset(cn^.tf_rt, @tftptmo, POINTER(ORD4(cn)), cn^.tf_tm); cn^.tf_state := ACKWAIT; goto 666; END ELSE BEGIN NotResponding(StrCvt('TFTP')); tfudperr(cn^.tf_udp,cn^.tf_outp,ERRTXT, StrCvt('Retry limit exceeded, giving up')); cn^.tf_state := DEAD; CALL1B(OFF, cn^.tf_done); LongDummy := tfcleanup(cn); {$IFC ALLOCT} WriteLn('tftpread 1: '); {$ENDC} udp_free(pfill); ntftps := ntftps - 1; leave; END; END; if (cn^.tf_state=RCVACK) THEN BEGIN cn^.tf_expected := cn^.tf_expected + 1; if (done) THEN BEGIN if (cn^.tf_mode = MACINTOSH) AND (cn^.tf_fp <> tf_DataPart) THEN BEGIN if NOT MacPart(cn,0) then begin CALL1B(ON, cn^.tf_done); LongDummy := tfcleanup(cn); udp_free(pfill); leave; end; done := false; cycle; END ELSE BEGIN Dummy := tm_clear(cn^.tf_tm); CALL1B(ON, cn^.tf_done); LongDummy := tfcleanup(cn); {$IFC ALLOCT} WriteLn('tftpread 2: '); {$ENDC} udp_free(pfill); ntftps := ntftps - 1; leave; END; END; { Switch packets. psnt is the last packet we sent; it can now be safely written into (whereas before it might have been waiting for an async net write). So make it the fill packet. } tmp := psnt; cn^.tf_outp := pfill; psnt := pfill; pfill := tmp; Status := tfsndata(cn, flen); if (flen < NORMLEN) OR (OSStatus = eofErr) THEN done := TRUE; cycle; END; { Anomalous state. Die } Dummy := tm_clear(cn^.tf_tm); CALL1B(OFF, cn^.tf_done); LongDummy := tfcleanup(cn); {$IFC ALLOCT} WriteLn('tftpread 3: '); {$ENDC} udp_free(pfill); ntftps := ntftps - 1; leave; END; { of tftpread main loop } tk_exit; END { tftpread }; { Utility routines } { Note: this formerly called udp_write, a no-no at timer level. The code that used it has been corrected. } PROCEDURE tftptmo(cn: Ref_tfconn); BEGIN cn^.tf_tmo := cn^.tf_tmo + 1; cn^.tf_tries := cn^.tf_tries - 1; cn^.tf_rsnd := cn^.tf_rsnd + 1; cn^.tf_NR := cn^.tf_NR + 1; cn^.tf_state := TIMEOUT; tk_wake(cn^.tf_task); END; { end of tftptmo } PROCEDURE tf_good(cn: Ref_tfconn); VAR trtM: LongInt; BEGIN trtM := TickCount - cn^.tf_sent; { Measured round trip time } if(cn^.tf_NR_last=1) THEN cn^.tf_K := Kinit; if(cn^.tf_NR=1) THEN cn^.tf_trt := (trtM+cn^.tf_trt) DIV 2 else BEGIN if ((cn^.tf_NR_last > 1) AND (cn^.tf_K >1) ) THEN cn^.tf_K := cn^.tf_K - Kinc; cn^.tf_trt := cn^.tf_trt + (cn^.tf_trt DIV cn^.tf_K); END; { cn^.tf_rt := max(min( cn^.tf_trt*TMMULT , MAXTMO), MINTICKS); } cn^.tf_rt := cn^.tf_trt*TMMULT; if cn^.tf_rt > MAXTMO then cn^.tf_rt := MAXTMO; if cn^.tf_rt < MINTICKS then cn^.tf_rt := MINTICKS; cn^.tf_NR_last := cn^.tf_NR; END; { end of tfgood } { Format up and send out an initial request for a tftp connection. } FUNCTION tfsndreq(cn: Ref_tfconn; fname: StringPtr): Integer; VAR ptreq : Ref_tfreq; NxtFieldPtr: PTR; modelen: Integer; BEGIN ptreq := { (struct tfreq *)} POINTER(ORD4(tftp_head(cn^.tf_outp))); if (cn^.tf_dir=GET) THEN ptreq^.tf_op := RRQ else if (cn^.tf_dir=PUT) THEN ptreq^.tf_op := WRQ else BEGIN {$IFC DEBUG} WriteLn('TFSNDREQ: Bad direction ',cn^.tf_dir,'.'); tfcndump(cn); { if BitAnd(NDEBUG,BUGHALT)<>0 THEN HALT; } {$ENDC} tfsndreq := -1; exit(tfsndreq); END; PStr2CStr(fname,POINTER(ORD4(ptreq) + tf_name_offset)); NxtFieldPtr := POINTER(ORD4(ptreq) + tf_name_offset + length(fname^) + 1); if (cn^.tf_mode=IMAGE) OR (cn^.tf_mode=TEST) THEN BEGIN PStr2CStr(StrCvt('image'),NxtFieldPtr); modelen := length('image'); END else if (cn^.tf_mode=OCTET) THEN BEGIN PStr2CStr(StrCvt('octet'),NxtFieldPtr); modelen := length('octet'); END else if (cn^.tf_mode=ASCII) THEN BEGIN PStr2CStr(StrCvt('netascii'),NxtFieldPtr); modelen := length('netascii'); END else if (cn^.tf_mode=MACINTOSH) THEN BEGIN PStr2CStr(StrCvt('macintosh'),NxtFieldPtr); modelen := length('macintosh'); END else BEGIN {$IFC DEBUG} WriteLn('TFSNDREQ: Bad mode ',cn^.tf_mode,'.'); tfcndump(cn); { if BitAnd(NDEBUG,BUGHALT)<>0 THEN HALT; } {$ENDC} tfsndreq := -1; exit(tfsndreq); END; {$IFC DEBUG} WriteLn('TFTP: sending file request'); {$ENDC} tfsndreq := tf_write(cn, { Overhead of tfreq for opcode } 2 + { File name } length(fname^) + 1 + { Mode size } modelen + 1); END; { end of tfsndreq } { Process a data packet received for TFTP connection cn, according to the type specified in the connection block. Also handle out of sequence blocks and check on block length. If a block is way too short (len < tftp header size) send back an error message and abort the transfer; we have CSR disease. If the block is less than 512 bytes, shut down the transfer; we're done. Otherwise, just write it to disk (if necessary). } PROCEDURE tfdodata(cn: Ref_tfconn; p:PACKET; len: integer); VAR data: PTR; ptfdat: Ref_tfdata; start_off, end_off: integer; RawPtr: PTR; Dummy: Boolean; OSStatus: OSErr; FIPtr: ^ FInfo; CurFInfo: FInfo; { Finder information of file } BEGIN CheckTask; if(len < 4) THEN BEGIN { log(tftplog, 'TFDODATA: CSR disease; len := %u', len); } tfrpyerr(cn^.tf_udp, p, ERRTXT,StrCvt('You have CSR disease.')); {$IFC DEBUG} WriteLn('TFDODATA: Died of CSR disease (gurgle).'); {$ENDC} tfkill(cn); exit(tfdodata); END; if (cn^.tf_state = TIMEOUT) THEN BEGIN { Got it just in time! } cn^.tf_state := DATAWAIT; END else if (cn^.tf_state <> DATAWAIT) THEN BEGIN {$IFC DEBUG} WriteLn('TFTP: Received unexpected data block (state =', ORD(cn^.tf_state),')'); {$ENDC} tfrpyerr(cn^.tf_udp, p, ERRTXT, StrCvt('Received unexpected data block')); exit(tfdodata); END; ptfdat := {(struct tfdata *)} POINTER(ORD4(tftp_head(p))); len := len - 4; { BAD. } if (ptfdat^.tf_block <> cn^.tf_expected) THEN BEGIN {$IFC DEBUG} WriteLn('TFTP: Got block ', ptfdat^.tf_block, ', expecting ', cn^.tf_expected); {$ENDC} { We got a retransmission of a packet we have already tried to ACK. If we retransmit the ACK, and the old ACK finally gets through also, our correspondent will resend the next data block, and we will do the same thing for it, on through to the end of the file. So we shouldn't retransmit the ACK until we are convinced that the first one was actually lost. We will become convinced if our own timeout waiting for the next data packet expires. } { Here is what you shouldn't do. . . if(ptfdat^.tf_block=cn^.tf_expected-1) tfsndack(cn, ptfdat^.tf_block); And now we return to correct procedures. . . } cn^.tf_ous := cn^.tf_ous + 1; exit(tfdodata); END; { Send the ack before writing the data } Dummy := tm_clear(cn^.tf_tm); tf_good(cn); tfsndack(cn, ptfdat^.tf_block); cn^.tf_size := cn^.tf_size + len; data := tftp_data(p); {$IFC DEBUG} WriteLn('tfdodata: about to write packet, len = ',len:1); {$ENDC} IF cn^.tf_mode in [IMAGE, MACINTOSH, OCTET, ASCII] THEN BEGIN CASE cn^.tf_fp OF tf_DataPart, tf_RsrcPart: IF cn^.tf_mode <> ASCII THEN BEGIN cn^.tf_PB^.ioBuffer := data; cn^.tf_PB^.ioReqCount := len; cn^.tf_PB^.ioPosMode := fsAtMark; OSStatus := PBWrite(cn^.tf_PB,FALSE); END ELSE BEGIN { ASCII mode, have to translate CRLF to just CR and CR NUL to just CR as well; bare CR's shouldn't happen, so we can just strip any character after a CR } if cn^.tf_SAWCR then begin { last packet ended in CR, strip this character } cn^.tf_SAWCR := false; start_off := 1; end_off := 1; {$IFC DEBUG} WriteLn('Last packet ended in CR'); {$ENDC} end else begin start_off := 0; end_off := 0; end; cn^.tf_PB^.ioPosMode := fsAtMark; OSStatus := noErr; { maybe no file write will be done } {$IFC DEBUG} WriteLn('tfdodata: end_off = ',end_off:1,', start_off = ', start_off:1,', len = ',len:1); {$ENDC} while end_off < len do begin RawPtr := POINTER(ORD4(data) + end_off); while (RawPtr^ <> $0d) & (end_off < len) do begin end_off := end_off + 1; RawPtr := POINTER(ORD4(RawPtr) + 1); end; if end_off = len then begin end_off := len - 1; RawPtr := POINTER(ORD4(RawPtr) - 1); end; cn^.tf_PB^.ioReqCount := (end_off - start_off) + 1; cn^.tf_PB^.ioBuffer := POINTER(ORD4(data) + start_off); {$IFC DEBUG} WriteLn('writing, ioReqCount = ', cn^.tf_PB^.ioReqCount:1); {$ENDC} OSStatus := PBWrite(cn^.tf_PB, FALSE); {$IFC DEBUG} WriteLn('written, OSStatus = ',OSStatus:1); {$ENDC} if OSStatus <> noErr then leave; if (end_off = len-1) & (RawPtr^ = $0d) then begin { last char in pkt was CR } cn^.tf_SAWCR := true; leave; end; start_off := end_off + 2; end_off := start_off; end; { while end_off < len do } END; { ASCII mode } tf_FindPart: BEGIN FIPtr := POINTER(ORD4(TFTP_Data(p))); OSStatus := GetFInfo(cn^.tf_fn,cn^.tf_volume,CurFInfo); CurFInfo.fdType := FIPtr^.fdType; CurFInfo.fdCreator := FIPtr^.fdCreator; { The ninth bit of the finder flags word means "the location of this file's icon is known". However, the location is NOT known for a new file received by TFTP. That bit has to be cleared, so that the Finder will figure out where the icon should go. This is undocumented. } CurFInfo.fdFlags := BitAND($feff,FIPtr^.fdFlags); OSStatus := SetFInfo(cn^.tf_fn,cn^.tf_volume, CurFInfo); END; { end of finder part } END; { end of case statement } IF OSStatus <> noErr THEN BEGIN tfrpyerr(cn^.tf_udp, p, DISKFULL,StrCvt('Disk Full')); FWriteErr(StrCvt(cn^.tf_fn),OSStatus); tfkill(cn); exit(tfdodata); END; END { end of mode test } {$IFC DEBUG} else if cn^.tf_mode = TEST THEN BEGIN WriteLn('TFDOData: Cannot process TEST mode'); END {$ENDC} else BEGIN tfrpyerr(cn^.tf_udp, p, ERRTXT,StrCvt('Internal Error.')); tfkill(cn); exit(tfdodata); END; if (len=NORMLEN) THEN cn^.tf_state := RCVDATA else cn^.tf_state := RCVLASTDATA; cn^.tf_expected := cn^.tf_expected + 1; tk_wake(cn^.tf_task); END; { End of tfdodata } { ack a certain block number } PROCEDURE tfsndack(cn:Ref_tfconn; number: integer); VAR pack: Ref_tfack; Status: Integer; BEGIN pack := { (struct tfack *)} POINTER(ORD4(tftp_head(cn^.tf_outp))); cn^.tf_lastlen := sizeof(tfack); pack^.tf_op := ACK; pack^.tf_block := number; {$IFC DEBUG} WriteLn('TFTP: ACKing block ',number); {$ENDC} Status := tf_write(cn, sizeof(tfack)); END; { End of tfsndack } { write a tftp packet } FUNCTION tf_write(cn: Ref_tfconn; len: integer):Integer; VAR mypacket: Ref_tfack; BEGIN mypacket := { (struct tfack *) } POINTER(ORD4(tftp_head(cn^.tf_outp))); IF (mypacket^.tf_op <> RRQ) AND (mypacket^.tf_op <> WRQ) THEN BEGIN { Byte swapping unnecessary on 68000 } { mypacket^.tf_block := bswap (mypacket^.tf_block); } cn^.tf_tries := TFTPTRIES; END ELSE cn^.tf_tries := REQTRIES; mypacket^.tf_op := { bswap } (mypacket^.tf_op); cn^.tf_lastlen := len; cn^.tf_snt := cn^.tf_snt + 1; IF udp_write(cn^.tf_udp, cn^.tf_outp, len) <= 0 THEN begin tf_write := -1; cn^.tf_state := RCVERR; exit(tf_write); end; tm_tset(cn^.tf_rt, @tftptmo, POINTER(ORD4(cn)), cn^.tf_tm); cn^.tf_sent := TickCount; cn^.tf_NR := 1; tf_write := noErr; END; { End of tf_write} {$IFC DEBUG} { Dump a connection block for debugging purposes. } PROCEDURE tfcndump(cn: Ref_tfconn); BEGIN WriteLn('Connection addr = ',ORD4(cn)); WriteLn('lastlen = ',cn^.tf_lastlen); WriteLn('expected =',cn^.tf_expected); WriteLn('state = ',cn^.tf_state); WriteLn(' dir = ',cn^.tf_dir); WriteLn(' mode = ',cn^.tf_mode); WriteLn('sent = ',cn^.tf_snt); WriteLn(' rcvd = ',cn^.tf_rcv); WriteLn(' tous = ',cn^.tf_ous); WriteLn(' tmo = ',cn^.tf_tmo); WriteLn(' rsnd = ',cn^.tf_rsnd); WriteLn('round trip delay = ',cn^.tf_trt); WriteLn(' K = ',cn^.tf_K); WriteLn(' curnt tmo = ',cn^.tf_rt); END; { End of tfcndump } {$ENDC} { Setup a TFTP connection block. } FUNCTION tfmkcn(dir:Integer; mode:Integer): Ref_tfconn; VAR cn: Ref_tfconn; Dummy: Boolean; BEGIN cn := { (struct tfconn *)} POINTER(ORD4(NewPtr(sizeof(tfconn)))); if (cn=NIL) THEN BEGIN CantAlloc(StrCvt('TFTP'),StrCvt('connection block')); { log(tftplog, 'Couldn''t allocate connection block.'); } tfmkcn := NIL; exit(tfmkcn); END; cn^.tf_udp := NIL; cn^.tf_rcv := 0; cn^.tf_snt := 0; cn^.tf_ous := 0; cn^.tf_tmo := 0; cn^.tf_rsnd := 0; cn^.tf_dir := dir; cn^.tf_mode := mode; cn^.tf_size := 0; cn^.tf_K := Kinit; cn^.tf_trt := T0; cn^.tf_volume := 0; cn^.tf_SAWCR := false; {$IFC DEBUG} cn^.tf_rt := 30*60; { 30 seconds } {$ELSEC} { cn^.tf_rt := max(min( cn^.tf_trt*TMMULT , MAXTMO), MINTICKS); } cn^.tf_rt := cn^.tf_trt*TMMULT; if cn^.tf_rt > MAXTMO then cn^.tf_rt := MAXTMO; if cn^.tf_rt < MINTICKS then cn^.tf_rt := MINTICKS; {$ENDC} cn^.tf_NR := 0; cn^.tf_NR_last := 1; cn^.tf_tries := REQTRIES; cn^.tf_tm := tm_alloc; if(cn^.tf_tm=NIL) THEN BEGIN CantAlloc(StrCvt('TFTP'),StrCvt('timer')); { log(tftplog, 'Couldn''t allocate timer.'); } DisposPtr(POINTER(ORD4(cn))); tfmkcn := NIL; exit(tfmkcn); END; cn^.tf_outp := udp_alloc(512, 0); {$IFC ALLOCT} NoteAlloc(cn^.tf_outp, 'tfmkcn'); {$ENDC} if(cn^.tf_outp=NIL) THEN BEGIN CantAlloc(StrCvt('TFTP'),StrCvt('output packet')); { log(tftplog, 'Couldn''t allocate output packet.'); } Dummy := tm_free(cn^.tf_tm); DisposPtr(POINTER(ORD4(cn))); tfmkcn := NIL; exit(tfmkcn); END; cn^.tf_PB := POINTER(ORD4(NewPtr(sizeof(ParamBlockRec)))); if cn^.tf_PB = NIL then begin CantAlloc(StrCvt('TFTP'),StrCvt('parameter block')); { log(tftplog, 'Couldn''t allocate parameter block.'); } Dummy := tm_free(cn^.tf_tm); udp_free(cn^.tf_outp); DisposPtr(POINTER(ORD4(cn))); tfmkcn := NIL; exit(tfmkcn); END; cn^.tf_PB^.ioRefNum := -1; cn^.tf_task := tk_cur; cn^.tf_next := NIL; cn^.tf_qtype := dummyType; enqueue(POINTER(ORD4(cn)),@tfconnq); tfmkcn := cn; END; { Cleanup routine called when done } FUNCTION tfcleanup(cn: Ref_tfconn): LongInt; VAR size: LongInt; Dummy: Boolean; OSStatus: OSErr; BEGIN { Give us one last chance to flush out a packet that hasn't been processed yet. } tk_yield; {$IFC DEBUG} WriteLn('TFCLEANUP called'); {$ENDC} if(cn^.tf_mode <> TEST) THEN BEGIN OSStatus := PBClose(cn^.tf_PB,FALSE); cn^.tf_PB^.ioNamePtr := NIL; cn^.tf_PB^.ioVRefNum := cn^.tf_volume; OSStatus := PBFlushVol(cn^.tf_PB,FALSE); END; {$IFC BUNDLE} if (cn^.tf_dir = GET) AND (cn^.tf_mode in [IMAGE, OCTET]) then setbundle(StrCvt(cn^.tf_fn),cn^.tf_volume); {$ENDC} udp_close(cn^.tf_udp); Dummy := tm_clear(cn^.tf_tm); Dummy := tm_free(cn^.tf_tm); DisposPtr(POINTER(ORD4(cn^.tf_PB))); {$IFC ALLOCT} Write('tfcleanup: '); {$ENDC} udp_free(cn^.tf_outp); cn^.tf_state := DEAD; {$IFC DEBUG} tfcndump(cn); {$ENDC} { tfcnlog(cn); } size := cn^.tf_size; OSStatus := dequeue(POINTER(ORD4(cn)),@tfconnq); DisposPtr(POINTER(ORD4(cn))); tfcleanup := size; END; { end of tfcleanup } { Send a TFTP data block. } FUNCTION tfsndata(cn: Ref_tfconn; len: integer): Integer; VAR tfdata_var: Ref_tfdata; BEGIN tfdata_var := {(struct tfdata *)} POINTER(ORD4(tftp_head(cn^.tf_outp))); tfdata_var^.tf_op := DATA; tfdata_var^.tf_block := cn^.tf_expected; {$IFC DEBUG} WriteLn('TFTP: sending block ',tfdata_var^.tf_block:1,'.'); {$ENDC} tfsndata := tf_write(cn, sizeof(tfdata)-512+len); END; { End of tfsndata } { Handle an incoming ack. } PROCEDURE tfdoack(cn: Ref_tfconn; p: PACKET; len: Integer); VAR ack: Ref_tfack; Dummy: Boolean; BEGIN ack := { (tfack *) } POINTER(ORD4(tftp_head(p))); if(ack^.tf_block <> cn^.tf_expected) THEN BEGIN { We have received an ACK, but not for the data block we sent. It must be for a duplicate, since we wouldn't have sent the current data block if we hadn't gotten an ACK for the previous one. This duplicate ACK means either that the network resent a packet that it wasn't sure got through, or else the other end resent the ACK because our current data block is lost or late. In either case, we can safely ignore this extra ACK, and if the ACK we want doesn't come our own timer will get us started again. It isn't safe to resend the current data block now unless we are absolutely certain that the other end won't reack it if the earlier send was just delayed. } cn^.tf_ous := cn^.tf_ous + 1; {$IFC DEBUG} WriteLn('TFTP: ACK for block ',ack^.tf_block,' received again.'); {$ENDC} END else BEGIN tf_good(cn); Dummy := tm_clear(cn^.tf_tm); cn^.tf_state := RCVACK; tk_wake(cn^.tf_task); END; END; { Handle an incoming packet. } FUNCTION tfckport(cn: Ref_tfconn; p: PACKET): Boolean; FORWARD; PROCEDURE tftprcv(p:PACKET; len: Integer; fhost: in_name; cn: Ref_tfconn); VAR pdata: Ref_tfdata; op: integer; OSStatus:OSErr; BEGIN CheckTask; cn^.tf_rcv := cn^.tf_rcv + 1; pdata := { (struct tfdata *)} POINTER(ORD4(tftp_head(p))); op := { bswap } (pdata^.tf_op); { swapping unnecessary for 68000 } pdata^.tf_op := op; CASE (op) OF DATA: if (tfckport(cn,p)) THEN tfdodata(cn, p, len); ACK: if (tfckport(cn,p)) THEN tfdoack(cn, p, len); ERRPCK: BEGIN if ((cn^.tf_fport=0) OR (cn^.tf_udp^.u_fport=udp_head(in_head(p))^.ud_srcp)) THEN BEGIN tfdoerr(cn, p, len); tfkill(cn); END; {$IFC DEBUG} WriteLn('TFTP: ignoring error packet.'); {$ENDC} END; OTHERWISE BEGIN {$IFC DEBUG} WriteLn('TFTPRCV: Got bad opcode ',op,'.'); {$ENDC} tfrpyerr(cn^.tf_udp, p, ILLTFTP,StrCvt(' ')); END; END; { End of case } {$IFC ALLOCT} Write('tftprcv: '); {$ENDC} udp_free(p); { So much for that packet! } END; { end tftprcv } { Check over the port in the incoming packet. } FUNCTION tfckport(cn: Ref_tfconn; p: PACKET): Boolean; VAR pdata: Ref_tfdata; svoutp: PACKET; pfport, svport: integer; BEGIN pdata := {(struct tfdata *)} POINTER(ORD4(tftp_head(p))); pdata^.tf_block := { bswap } (pdata^.tf_block); { no byte swapping on 68000 } pfport := udp_head(in_head(p))^.ud_srcp; if(cn^.tf_fport=0) THEN BEGIN { Foreign port not yet identified, save it. } if (cn^.tf_expected=pdata^.tf_block) THEN BEGIN{ but only if this is } cn^.tf_fport := 1; { a response to our } cn^.tf_udp^.u_fport := pfport; { request. } END else BEGIN {$IFC DEBUG} WriteLn('TFTP: Received packet from old connection.'); WriteLn(' Expected block ',cn^.tf_expected, ', got block ',pdata^.tf_block); {$ENDC} tfrpyerr(cn^.tf_udp, p, ERRTXT,StrCvt('old connection')); tfckport := FALSE; EXIT(tfckport); END; END { end of tf_fport = 0 test } else if( cn^.tf_udp^.u_fport <> pfport) THEN BEGIN {$IFC DEBUG} WriteLn('TFTP: Rcvd pkt from port ',pfport,', expect port ', cn^.tf_udp^.u_fport); {$ENDC} tfrpyerr(cn^.tf_udp, p, BADTID,StrCvt(' ')); tfckport := FALSE; EXIT(tfckport); END; tfckport := TRUE; END; { end tfckport } { Send error packet back where this packet came from. } PROCEDURE tfrpyerr(udpc:UDPCONN; p: PACKET; code:Integer; text: StringPtr); VAR svport: integer; svhost: in_name; svoutp: PACKET; BEGIN svport := udpc^.u_fport; { Save correct port no. } svhost := udpc^.u_fhost; { and host id } udpc^.u_fport := udp_head(in_head(p))^.ud_srcp; { improper } udpc^.u_fhost := in_head(p)^.ip_src; { layer violation } tfudperr(udpc, p, code, text); udpc^.u_fport := svport; {N.B. udperr must not yield } udpc^.u_fhost := svhost; { or these saves will fail } END; { end tfrpyerr() } { Process an incoming error packet } PROCEDURE tfdoerr(cn: Ref_tfconn; p:PACKET; len:Byte); CONST tf_err_offset = 4; VAR perr: PTR; LocalErr: STR255; i:INTEGER; BEGIN perr := POINTER(ORD4(tftp_head(p))); CStr2PStr(POINTER(ORD4(perr) + tf_err_offset),@LocalErr); for i := 1 to length(LocalErr) do begin if (LocalErr[i] = chr($d)) or (LocalErr[i] = chr($a)) then LocalErr[i] := ' '; end; Error2(StrCvt('TFTP: Error from foreign host: '),@LocalErr); cn^.tf_state := RCVERR; END; { end of tfdoerr } END. !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
I have posted the sources and binaries for the Macintosh Internet Protocols to net.sources.mac . The sources run about 12,000 lines of code, and another few thousand lines of binhex'd binaries. The source and binaries are stored in twelve shell archive files; note that "sh" and not "csh" should be used to unpack the archives, and that you should do the unpacking in a new, clean directory. The first archive file absolutely must be unpacked first; after that, the order doesn't matter. The Internet Protocols are network communication protocols defined by the Department of Defense, in wide use around the world. The Macintosh versions run on Appletalk, the low-cost local area network for the Macintosh computer (and, using a Centram board, for the IBM PC). They are meant not so much for Macintosh to Macintosh communication as for high-speed, error-checked communication with minicomputers and mainframes, such as VAXen and TOPS-20 systems. Some form of Seagate-compatible router will be needed in order to use these programs as they are intended to be used. (Seagate, the Stanford Ethernet Appletalk Gateway, is a family of routers defined by compatible software; the Seagate source code may be retrieved by FTP from SUMEX, in the INFO-MAC archives. Those of you who are not able to FTP should send mail to Bill Croft, su-safe!croft, for information on the tape distribution.) The programs included are TFTP (simple file transfer), TELNET (remote terminal emulation), CUST (to customize your Macintosh's IP address and so on), and SFMTEST, a test program provided as an illustration of the multiple file "Get File" dialog. Both sources and binaries are provided for all these programs. The sources may be of interest even to people who have no use for Appletalk, because they contain software for emulating DEC vt100 terminal graphics, error reporting to the user, running multiple tasks simultaneously (actually synchronously) within an application, setting timers at a higher level than direct manipulation of the vertical retrace queue, and an extended "SFGetFile" (standard file package "Get File" dialog) that can be interfaced to from any language without translating the source code, and which allows multiple files to be selected. The sources are in Lisa Pascal, but Lisa Pascal can readily be transliterated into any of the Mac C compilers. The code originated with the MIT PCIP network package for the IBM PC, written in C. Initial translation was done by Mark Sherman of Dartmouth; I continued this work. The final product is far more than a translation, being thoroughly integrated into the weird and wonderful Macintosh environment, and incorporating a set of, oh, roughly aleph-null bug fixes and major extensions. Second draft user-level documentation may be found in the file "user" in the first file of the twelve files in the posting. Better documentation is currently being written; by someone else, since I have moved on to other projects, such as LaserWriter print spooling on a UNIX machine. There is currently no external documentation of the source files, though they are reasonably well commented. -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting sfmget/make.text... cat >sfmget/make.text <<'!E!O!F!' $EXEC $SUBMIT t-assemble(sfmget-sfmget_asm) $SUBMIT t-depend(sfmget-sfmgetfile,sfmget-sfmget_asm) $SUBMIT t-assemble(sfmget-sfmintf_asm) $SUBMIT t-depend(sfmget-sfmintf) $SUBMIT t-depend(sfmget-sfmtest,sfmget-sfmintf,sfmget-sfmintf_asm) $IF NEWER("sfmget-sfmtestr.text","sfmget-sfmtestr.rsrc") THEN r{un}bin-RMaker sfmget-sfmtestr $ENDIF $IF NEWER("sfmget-sfmgetr.text","sfmget-sfmgetr.rsrc") THEN r{un}bin-RMaker sfmget-sfmgetr $ENDIF $IF NEWER("sfmget-sfmgetfile.obj","sfmget-sfmgetfileL.obj") THEN L{ink}? +X +R sfmhdr {partial link} { no more options} sfmget-sfmget_asm sfmget-sfmgetfile obj-quickdraw obj-tooltraps obj-ostraps obj-prlink obj-packtraps obj-rtlib obj-pasinit obj-paslibasm obj-paslib {no more object files} sfmget-sfmgetfileL.OBJ $ENDIF $IF NEWER("sfmget-sfmtest.obj","sfmget-sfmtestL.obj") THEN L{ink}? +X { no more options} sfmget-sfmtest obj-quickdraw obj-tooltraps obj-ostraps obj-prlink obj-packtraps obj-rtlib obj-pasinit obj-paslibasm obj-paslib obj-writelnwindow sfmget-sfmintf sfmget-sfmintf_asm {no more object files} {list on console} sfmget-sfmtestL.OBJ { output file is progL.OBJ } $ENDIF R{un}bin-rmaker { convert linked file to CODE resource file } sfmget-sfmtesto $ R{un}RFB { Resource file builder } sfmget-sfmtest.rsrc { output file } sfmget-sfmtestr.rsrc { compiled resource file } * sfmget-sfmgetr.rsrc * sfmget-sfmtesto.rsrc { CODE resource file } * { no more input files } { quit } R{un}bin-MacCom { write the disk } RYFYLsfmget-sfmtest.RSRC sfmtest APPL { set type to APPL } { set creator to ???? } Y{es, bundle bit}E{ject}Q{uit} $DOIT $ENDEXEC !E!O!F! # # echo extracting sfmget/sfmget_asm.tex... cat >sfmget/sfmget_asm.tex <<'!E!O!F!' ; header for Multiple File Get SOFT resource .PROC sfmhdr .REF GetFList,DelFList,SFMGetFile,CompFList,SortFList jmp GetFList ; 0 jmp DelFList ; 4 jmp SFMGetFile ; 8 jmp CompFList ; 12 jmp SortFList ; 16 .PROC CallBool MOVE.L 4(SP),A0 ; Copy the procedure pointer MOVE.L (SP)+,(SP) ; Move the return address down JMP (A0) ; Jump to the real routine .END !E!O!F! # # echo extracting sfmget/sfmgetfile.tex... cat >sfmget/sfmgetfile.tex <<'!E!O!F!' {$X-} {$U-} {$R-} {$D-} Unit SFMGet; INTERFACE {$L-} USES {$U Obj-Memtypes } MemTypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf; {$L+} Type StrCell = Record str: StringHandle; selected: Boolean; end; StrList = Record howmany: Integer; volume: Integer; vname: String[27]; { change vn_offset if this changes place } table: Array [0..0] of StrCell; end; StrLPtr = ^StrList; StrLHandle = ^StrLPtr; FUNCTION GetFList(vref:Integer; numTypes:Integer; typeList:SFTypeList; fileFilter: ProcPtr):StrLHandle; PROCEDURE DelFList(h:StrLHandle); FUNCTION SFMGetFile(where: Point; fileFilter: ProcPtr; numTypes:Integer; typeList:SFTypeList; dlgHook:ProcPtr; flags:Integer): StrLHandle; PROCEDURE CompFList(h:StrLHandle); PROCEDURE SortFList(h:StrLHandle); CONST { constants to be OR'ed into flags word } SFMmulti = 1; SFMenable = 2; SFMstart = 4; IMPLEMENTATION CONST GrayLine = 9; VolName = 4; getHScroll = 11; getMChk = 12; shiftList = 255; IncHoriz = 8; DlogResID = -4001; {resource for the dialog} DefVCBPtr = $352; {address of default volume control block pointer} SFSaveDisk = $214; {SF package global: last colume seen} vn_offset = 4; Type DrvElPtr = ^DrvQEl; VCBPtr = ^VCB; VCBHdl = ^VCBPtr; IntPtr = ^Integer; DlogRef = RECORD VBar: ControlHandle; HBar: ControlHandle; List: StrLHandle; DrvPtr: DrvElPtr; filter: ProcPtr; oldVert:Integer; oldHoriz:Integer; Rows: Integer; tnum: Integer; tlist: SFTypeList; tlist2: SFTypeList; tlist3: SFTypeList; tlist4: SFTypeList; { if ya want more'n 16 types, yer nuts } end; DlogRPtr = ^DlogRef; PROCEDURE DrawBoxInerds(DlogPtr:DialogPtr); VAR fi: FontInfo; i,v: Integer; invert: Rect; VBar,HBar: ControlHandle; h: StrLHandle; val: Integer; ItemType: INTEGER; ItemHdl: Handle; BoxRect: Rect; Clear: Rect; Begin VBar := DlogRPtr(GetWRefCon(DlogPtr))^.VBar; HBar := DlogRPtr(GetWRefCon(DlogPtr))^.HBar; GetDItem(DlogPtr, getNmList, ItemType, ItemHdl, BoxRect); Clear := BoxRect; InsetRect(Clear,1,1); EraseRect(Clear); h := DlogRPtr(GetWRefCon(DlogPtr))^.List; if h = NIL then exit(DrawBoxInerds); GetFontInfo(fi); v := fi.ascent + boxRect.top + 1; val := GetCtlValue(VBar); for i := 0 to DlogRPtr(GetWRefCon(DlogPtr))^.rows do begin if i+val >= h^^.howmany then leave; MoveTo(boxRect.left+2 - (GetCtlValue(HBar)*IncHoriz),v); DrawString(h^^.table[i+val].str^^); if h^^.table[i+val].selected then begin SetRect(invert, boxRect.left+1, v - fi.ascent, boxRect.right - 1, v + fi.descent + fi.leading); InvertRect(invert); end; v := v + fi.ascent + fi.descent + fi.leading; end; End; PROCEDURE DrawGrayLine(theDialog: DialogPtr; ItemNo: integer); {this is the UserItem procedure that draws the gray divider line} VAR ItemType: integer; theItem: Handle; itsRect: Rect; penLoc: Point; Begin GetDItem(theDialog, ItemNo, ItemType, theItem, itsRect); penLoc.v := itsRect.top; penLoc.h := itsRect.left; PenNormal; moveto(penLoc.h, penLoc.v); while (penLoc.v < itsRect.bottom) do begin move(0,2); penLoc.v := penLoc.v + 2; Line(0,0); end; End; PROCEDURE PrVName(theDialog: DialogPtr; ItemNo: integer); {this is the UserItem procedure that draws the volume name} VAR ItemType: integer; theItem: Handle; itsRect: Rect; TempClip: RgnHandle; fi: FontInfo; width: Integer; lastSpace: Integer; i,line2: Integer; h: StrLHandle; BEGIN GetDItem(theDialog, ItemNo, ItemType, theItem, itsRect); EraseRect(itsRect); h := DlogRPtr(GetWRefCon(theDialog))^.list; if h = NIL then exit(PrVName); GetFontInfo(fi); TempClip := NewRgn; GetClip(TempClip); ClipRect(itsRect); width := StringWidth(h^^.vname); if width < itsRect.right-itsRect.left then begin MoveTo(itsRect.left+(((itsRect.right-itsRect.left)-width) div 2), itsRect.top + 3 + ((itsRect.bottom - itsRect.top) div 2)); DrawString(h^^.vname); end else begin { have to justify the sucker within the box } TextBox(Ptr(ORD4(h^)+vn_offset+1),length(h^^.vname),itsRect,teJustCenter); end; SetClip(TempClip); DisposeRgn(tempClip); END; PROCEDURE ScrollVert(DlogPtr: DialogPtr); Var OldOrig: integer; NewOrig: integer; delta: integer; theBox: Rect; UpDateRgn: RgnHandle; TempClip: RgnHandle; fi: FontInfo; SBar: ControlHandle; ItemType: INTEGER; ItemHdl: Handle; BoxRect: Rect; dp: DlogRPtr; Begin {get some new region space} UpDateRgn := NewRgn; {Rgn for use in updateing} TempClip := NewRgn; {Rgn for use in restoring the clip} dp := DlogRPtr(GetWRefCon(DlogPtr)); SBar := dp^.VBar; GetDItem(DlogPtr, getNmList, ItemType, ItemHdl, BoxRect); {calculate the amount scrolled} OldOrig := dp^.oldVert; NewOrig := GetCtlValue(SBar); GetFontInfo(fi); {get V diff. between old & new origin} delta := (OldOrig - NewOrig) * (fi.ascent+fi.descent+fi.leading); {get the area to scroll} SetRect(theBox, boxRect.left, boxRect.top, boxRect.right, boxRect.bottom); InSetRect(theBox, 1,1); {make scroll box smaller by one pixel} {do the scrolling} ScrollRect(theBox, 0, delta, UpDateRgn); {move pixels up by Vert. diff.} {clip to the update region} GetClip(TempClip); ClipRect(UpDateRgn^^.rgnBBox); {draw whatever is in the box} DrawBoxInerds(DlogPtr); {restore the clip & remember the new origin for next scroll} SetClip(TempClip); dp^.oldVert := NewOrig; {throw away unneeded things} DisposeRgn(tempClip); DisposeRgn(UpDateRgn); End; PROCEDURE ItemScroll(ScrollBarHdl:ControlHandle; CntlLoc: integer); Begin If (CntlLoc = inDownButton) & (GetCtlValue(ScrollBarHdl) < GetCtlMax(ScrollBarHdl)) then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + 1) else If (CntlLoc = inUpButton) & (GetCtlValue(ScrollBarHdl) > GetCtlMin(ScrollBarHdl)) then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - 1); ScrollVert(ScrollBarHdl^^.contrlOwner); End; PROCEDURE PageVert(ScrollBarHdl:ControlHandle; CntlLoc: integer); Begin If CntlLoc = inPageUp then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - DlogRPtr(GetWRefCon(ScrollBarHdl^^.contrlOwner))^.rows) Else If CntlLoc = inPageDown then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + DlogRPtr(GetWRefCon(ScrollBarHdl^^.contrlOwner))^.rows); If GetCtlValue(ScrollBarHdl) > GetCtlMax(ScrollBarHdl) then SetCtlValue(ScrollBarHdl,GetCtlMax(ScrollBarHdl)) Else If GetCtlValue(ScrollBarHdl) < GetCtlMin(ScrollBarHdl) then SetCtlValue(ScrollBarHdl,GetCtlMin(ScrollBarHdl)); ScrollVert(ScrollBarHdl^^.contrlOwner); End; { Procedure to handle mouse-downs in the vertical scroll bar } PROCEDURE VScroller(theDialog: DialogPtr); Var BoxScrollBar: ControlHandle; mouseLoc: Point; ControlLoc: integer; dummy: integer; Begin {get the mouse location- its local to the current Grafport} GetMouse(mouseLoc); {find the control part} ControlLoc := FindControl(mouseLoc, theDialog, BoxScrollBar); If ControlLoc <> 0 then begin Case ControlLoc of inUpButtom: dummy:=TrackControl(BoxScrollBar, mouseLoc, @ItemScroll); inDownButton: dummy:=TrackControl(BoxScrollBar, mouseLoc, @ItemScroll); inPageUp: PageVert(BoxScrollBar, ControlLoc); inPageDown: PageVert(BoxScrollBar, ControlLoc); inThumb: If TrackControl(BoxScrollBar, mouseLoc, Nil) <> 0 then ScrollVert(theDialog); End; End else sysbeep(3); End; PROCEDURE ScrollHoriz(DlogPtr: DialogPtr); Var OldOrig: integer; NewOrig: integer; delta: integer; theBox: Rect; UpDateRgn: RgnHandle; TempClip: RgnHandle; SBar: ControlHandle; ItemType: INTEGER; ItemHdl: Handle; BoxRect: Rect; dp: DlogRPtr; Begin {get some new region space} UpDateRgn := NewRgn; {Rgn for use in updateing} TempClip := NewRgn; {Rgn for use in restoring the clip} dp := DlogRPtr(GetWRefCon(DlogPtr)); SBar := dp^.HBar; GetDItem(DlogPtr, getNmList, ItemType, ItemHdl, BoxRect); {calculate the amount scrolled} OldOrig := dp^.oldHoriz; NewOrig := GetCtlValue(SBar); {get H diff. between old & new origin} delta := (OldOrig - NewOrig) * IncHoriz; {get the area and scroll} SetRect(theBox, boxRect.left, boxRect.top, boxRect.right, boxRect.bottom); InSetRect(theBox, 1,1); {make scroll box smaller by one pixel} ScrollRect(theBox, delta, 0, UpDateRgn); {clip to the update region} GetClip(TempClip); ClipRect(UpDateRgn^^.rgnBBox); {draw whatever is in the box} DrawBoxInerds(DlogPtr); {restore the clip & remember the new origin for next scroll} SetClip(TempClip); dp^.oldHoriz := NewOrig; {throw away unneeded things} DisposeRgn(tempClip); DisposeRgn(UpDateRgn); END; {tracking routine for horizontal scoll bar} PROCEDURE NameScroll(ScrollBarHdl:ControlHandle; CntlLoc: integer); Begin If (CntlLoc = inDownButton) & (GetCtlValue(ScrollBarHdl) < GetCtlMax(ScrollBarHdl)) then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + 1) else If (CntlLoc = inUpButton) & (GetCtlValue(ScrollBarHdl) > GetCtlMin(ScrollBarHdl)) then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - 1); ScrollHoriz(ScrollBarHdl^^.contrlOwner); End; PROCEDURE PageHoriz(ScrollBarHdl:ControlHandle; CntlLoc: integer); Begin If CntlLoc = inPageUp then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) - 5) Else If CntlLoc = inPageDown then SetCtlValue(ScrollBarHdl, GetCtlValue(ScrollBarHdl) + 5); If GetCtlValue(ScrollBarHdl) > GetCtlMax(ScrollBarHdl) then SetCtlValue(ScrollBarHdl,GetCtlMax(ScrollBarHdl)) Else If GetCtlValue(ScrollBarHdl) < GetCtlMin(ScrollBarHdl) then SetCtlValue(ScrollBarHdl,GetCtlMin(ScrollBarHdl)); ScrollHoriz(ScrollBarHdl^^.contrlOwner); End; { Procedure to handle mouse down events in the horizontal scroll bar } PROCEDURE HScroller(theDialog: DialogPtr); Var HScrollBar: ControlHandle; mouseLoc: Point; ControlLoc: integer; dummy: integer; Begin {get the mouse location- its local to the current Grafport} GetMouse(mouseLoc); {find the control part} ControlLoc := FindControl(mouseLoc, theDialog, HScrollBar); If ControlLoc <> 0 then begin Case ControlLoc of inUpButtom,inDownButton: dummy := TrackControl(HScrollBar, mouseLoc, @NameScroll); inPageUp,inPageDown: PageHoriz(HScrollBar, ControlLoc); inThumb: If TrackControl(HScrollBar, mouseLoc, Nil) <> 0 then ScrollHoriz(theDialog); End; End else sysbeep(3); End; PROCEDURE BoxContents(theDialog: DialogPtr; ItemNo: integer); Var ItemType: integer; ItemHdl: Handle; ItemRect: Rect; LongHdl: LongInt; TempClip: RgnHandle; Begin {first get the Box size} GetDItem(theDialog, ItemNo, ItemType, ItemHdl, ItemRect); {set the clip for drawing} TempClip := NewRgn; GetClip(TempClip); ClipRect(ItemRect); {do the drawing} FrameRect(ItemRect); DrawBoxInerds(theDialog); {restore the clip & clean up} SetClip(TempClip); DisposeRgn(tempClip); End; PROCEDURE ListAdjust(dlog: DialogPtr); VAR i: Integer; anySelected: Boolean; h:StrLHandle; itemType: Integer; itemHandle: Handle; itemBox: Rect; BEGIN h := DlogRPtr(GetWRefCon(dlog))^.List; anySelected := false; for i := 0 to h^^.howmany-1 do if h^^.table[i].selected then anySelected := true; GetDItem(dlog,getOpen,itemType,itemHandle,itemBox); if anySelected then HiLiteControl(ControlHandle(itemHandle), 0) else HiLiteControl(ControlHandle(itemHandle), 255); END; PROCEDURE MultClick(dlog: DialogPtr; bar: ControlHandle; rows: Integer); { this procedure handles mouse-down events in the name box (with multi-selection)} CONST pauseTime = 4; { 4 60th's of a second := 1/15 second } VAR mouseLoc: Point; fontHeight: Integer; fi: FontInfo; invert: Rect; name_no: Integer; h: StrLHandle; ItemType: Integer; ItemHdl: Handle; BoxRect: Rect; time: LongInt; selecting: (yep, nope, dunno); val,max: Integer; lastDone: Integer; BEGIN h := DlogRPtr(GetWRefCon(dlog))^.List; if h = NIL then begin sysbeep(3); exit(MultClick); end; GetFontInfo(fi); fontHeight := fi.ascent + fi.descent + fi.leading; GetDItem(Dlog, getNmList, ItemType, ItemHdl, BoxRect); selecting := dunno; max := GetCtlMax(bar); val := GetCtlValue(bar); while StillDown do begin { loop tracks mouse } SystemTask; GetMouse(mouseLoc); if mouseLoc.v < boxRect.top+1 then begin { past the top of the first row, scroll down } If val > 0 then SetCtlValue(bar, val - 1) else cycle; val := val - 1; case selecting of dunno: begin if h^^.table[val].selected then selecting := nope else selecting := yep; h^^.table[val].selected := NOT h^^.table[val].selected; end; yep: h^^.table[val].selected := true; nope: h^^.table[val].selected := false; end; lastDone := val; ScrollVert(dlog); Delay(pauseTime,time); cycle; end else name_no := (mouseLoc.v - (boxRect.top + 1)) div fontHeight; if name_no > (rows - 1) then begin { past the bottom of the last row, scroll upwards } If val < max then SetCtlValue(bar, val + 1) else cycle; val := val + 1; case selecting of dunno: begin if h^^.table[val+(rows-1)].selected then selecting := nope else selecting := yep; h^^.table[val+(rows-1)].selected := NOT h^^.table[val+(rows-1)].selected; end; yep: h^^.table[val+(rows-1)].selected := true; nope: h^^.table[val+(rows-1)].selected := false; end; lastDone := val+(rows-1); ScrollVert(dlog); Delay(pauseTime,time); cycle; end; if name_no + val >= h^^.howmany then cycle; case selecting of dunno: begin if h^^.table[name_no + val].selected then selecting := nope else selecting := yep; h^^.table[name_no + val].selected := NOT h^^.table[name_no + val].selected; invert.left := boxRect.left + 1; invert.top := boxRect.top + 1 + (name_no * fontHeight); invert.right := boxRect.right - 1; invert.bottom := boxRect.top+1+((name_no+1)*fontHeight); InvertRect(invert); end; yep: begin if NOT h^^.table[name_no + val].selected then begin h^^.table[name_no + val].selected := true; invert.left := boxRect.left + 1; invert.top := boxRect.top+1+(name_no * fontHeight); invert.right := boxRect.right - 1; invert.bottom := boxRect.top+1+ ((name_no+1)*fontHeight); InvertRect(invert); end; end; nope: begin if h^^.table[name_no + val].selected then begin h^^.table[name_no + val].selected := false; invert.left := boxRect.left + 1; invert.top := boxRect.top+1+(name_no*fontHeight); invert.right := boxRect.right - 1; invert.bottom := boxRect.top + 1 + ((name_no+1)*fontHeight); InvertRect(invert); end; end; end; { case } lastDone := name_no + val; end; { loop } ListAdjust(dlog); END; PROCEDURE UnSelect(h:StrLHandle; val, rows, fontHeight:Integer; boxRect: Rect); VAR i:INTEGER; invert:Rect; BEGIN for i := 0 to h^^.howmany - 1 do begin if h^^.table[i].selected then begin h^^.table[i].selected := false; if (i >= val) & (i-val < rows) then begin SetRect(invert, boxRect.left + 1, boxRect.top + 1 + ((i - val) * fontHeight), boxRect.right - 1, boxRect.top+1+ (((i-val)+1)*fontHeight)); InvertRect(invert); end; end; end; END; {UnSelect} PROCEDURE SingClick(dlog: DialogPtr; bar: ControlHandle; rows: Integer); { this procedure handles mouse-down events in the name box (no multi-selection)} CONST pauseTime = 4; { 4 60th's of a second := 1/15 second } VAR mouseLoc: Point; fontHeight: Integer; fi: FontInfo; invert: Rect; name_no: Integer; h: StrLHandle; ItemType: Integer; ItemHdl: Handle; BoxRect: Rect; time: LongInt; val,max: Integer; j: Integer; BEGIN h := DlogRPtr(GetWRefCon(dlog))^.List; if h = NIL then begin sysbeep(3); exit(SingClick); end; GetFontInfo(fi); fontHeight := fi.ascent + fi.descent + fi.leading; GetDItem(Dlog, getNmList, ItemType, ItemHdl, BoxRect); max := GetCtlMax(bar); val := GetCtlValue(bar); { single click unselects any existing selections } Unselect(h,val,rows,fontHeight,boxRect); while StillDown do begin { loop tracks mouse } SystemTask; GetMouse(mouseLoc); if mouseLoc.v < boxRect.top+1 then begin { past the top of the first row, scroll down } val := GetCtlValue(bar); If val > 0 then begin if not h^^.table[val - 1].selected then Unselect(h,val,rows,fontHeight,boxRect); SetCtlValue(bar, val - 1); end else cycle; h^^.table[val - 1].selected := true; ScrollVert(dlog); Delay(pauseTime,time); cycle; end else name_no := (mouseLoc.v - (boxRect.top + 1)) div fontHeight; if name_no > (rows - 1) then begin { past the bottom of the last row, scroll upwards } val := GetCtlValue(bar); If val < max then begin if not h^^.table[val + 1 + (rows-1)].selected then Unselect(h,val,rows,fontHeight,boxRect); SetCtlValue(bar, val + 1); end else cycle; h^^.table[val + 1 + (rows-1)].selected := true; ScrollVert(dlog); Delay(pauseTime,time); cycle; end; val := GetCtlValue(bar); if name_no + val >= h^^.howmany then cycle; if NOT h^^.table[name_no + val].selected then begin Unselect(h,val,rows,fontHeight,boxRect); h^^.table[name_no + val].selected := true; SetRect(invert, boxRect.left + 1, boxRect.top + 1 + (name_no * fontHeight), boxRect.right - 1, boxRect.top+1+((name_no+1)*fontHeight)); InvertRect(invert); end; end; { loop } { disable or enable Open button as needed } GetDItem(dlog,getOpen,ItemType,ItemHdl,BoxRect); for j := 0 to h^^.howmany-1 do if h^^.table[j].selected then begin HiLiteControl(ControlHandle(ItemHdl),0); exit(SingClick); end; HiLiteControl(ControlHandle(ItemHdl),255); END; FUNCTION CallBool(PB:ParmBlkPtr; func:ProcPtr):Boolean; external; FUNCTION GetFList(vref:Integer; numTypes:Integer; typeList:SFTypeList; fileFilter: ProcPtr):StrLHandle; VAR i, j: Integer; h: StrLHandle; PB: ParamBlockRec; s: STR255; sh: StringHandle; err: OSErr; OK: Boolean; numFiles, badfiles, t: Integer; v: VCBPtr; BEGIN if vref = 0 then begin { default volume, find its real reference number } v := VCBHdl(DefVCBPtr)^; vref := v^.vcbVRefNum; end else if vref > 0 then begin { drive number, find volume ref } v := VCBPtr(GetVCBQHdr^.qHead); while (v <> NIL) & (v^.vcbDrvNum <> vref) do v := VCBPtr(v^.qLink); if v = NIL then SysError(-56); { no such drive } vref := v^.vcbVRefNum; end else begin { volume reference number } v := VCBPtr(GetVCBQHdr^.qHead); while (v <> NIL) & (v^.vcbVRefNum <> vref) do v := VCBPtr(v^.qLink); if v = NIL then SysError(-35); {no such volume} end; numFiles := v^.vcbNmFls; h := StrLHandle(NewHandle(sizeof(StrList) + (numFiles*sizeof(StrCell)))); if (memError <> noErr) | (h = NIL) then SysError(25); h^^.volume := vref; h^^.vname := v^.vcbVN; badfiles := 0; t := 0; for i := 1 to numFiles do begin PB.ioVersNum := 0; PB.ioVRefNum := vref; s := ''; PB.ioNamePtr := @s; PB.ioFDirIndex := i; err := PBGetFInfo(@PB, FALSE); if err <> noErr then SysError(err); if numTypes <> -1 then begin OK := false; for j := 0 to numTypes-1 do if typeList[j] = PB.ioFlFndrInfo.fdType then begin OK := true; leave; end; end else OK := true; if OK & (fileFilter <> NIL) then OK := NOT CallBool(@PB,fileFilter); if OK then begin sh := POINTER(ORD4(NewHandle(length(s)+1))); if (memError <> noErr) | (sh = NIL) then SysError(25); BlockMove(@s, Ptr(sh^), length(s) + 1); h^^.table[t].str := sh; h^^.table[t].selected := FALSE; t := t + 1; end else badfiles := badfiles + 1; end; h^^.howmany := numFiles - badfiles; SetHandleSize(Handle(h),sizeof(StrList) + (h^^.howmany*sizeof(StrCell))); err := MemError; if err <> noErr then SysError(err); GetFList := h; END; PROCEDURE DelFList(h:StrLHandle); VAR i:INTEGER; err:OSErr; BEGIN if h = NIL then exit(DelFList); for i := 0 to h^^.howmany-1 do begin if h^^.table[i].str <> NIL then begin DisposHandle(Handle(h^^.table[i].str)); err := memError; if err <> noErr then SysError(err); end; end; DisposHandle(Handle(h)); err := memError; if err <> noErr then SysError(err); END; PROCEDURE CompFList(h:StrLHandle); VAR i,j:INTEGER; err:OSErr; selNum:Integer; BEGIN if h = NIL then exit(CompFList); selNum := 0; for i := 0 to h^^.howmany-1 do begin if not h^^.table[i].selected then begin DisposHandle(Handle(h^^.table[i].str)); err := memError; if err <> noErr then SysError(err); h^^.table[i].str := NIL; end else begin selNum := selNum + 1; { move it back to fill up any holes } if (i > 0) & (h^^.table[i-1].str = NIL) then begin for j := i - 1 downto 0 do if h^^.table[j].str <> NIL then leave; if (j = 0) & (h^^.table[0].str = NIL) then j := -1; h^^.table[j+1].str := h^^.table[i].str; h^^.table[j+1].selected := true; h^^.table[i].str := NIL; h^^.table[i].selected := false; end; end; end; h^^.howmany := selNum; SetHandleSize(Handle(h),sizeof(StrList) + (h^^.howmany*sizeof(StrCell))); END; {simple selection sort is fast enough} PROCEDURE SortFList(h:StrLHandle); VAR i:Integer; flag,tmp:Boolean; s:StringHandle; BEGIN flag := true; while flag do begin flag := false; for i := 0 to h^^.howmany - 2 do begin if IUMagString(POINTER(ORD4(h^^.table[i].str^)+1), POINTER(ORD4(h^^.table[i+1].str^)+1), length(h^^.table[i].str^^), length(h^^.table[i+1].str^^)) = 1 then begin flag := true; s := h^^.table[i].str; tmp := h^^.table[i].selected; h^^.table[i].str := h^^.table[i+1].str; h^^.table[i].selected := h^^.table[i+1].selected; h^^.table[i+1].str := s; h^^.table[i+1].selected := tmp; end; end; end; END; FUNCTION DrvHasDisk(Drive: DrvElPtr): Boolean; VAR RawPtr:PTR; BEGIN RawPtr := POINTER(ORD4(Drive)-3); if RawPtr^ in [1,2,8] then DrvHasDisk := true else DrvHasDisk := false; END; PROCEDURE DlogSetup(SFFileDlg:DialogPtr); VAR ItemType: integer; ItemHdl: Handle; ItemRect: Rect; VScrollBar: ControlHandle; HScrollBar: ControlHandle; List: StrLHandle; rows: INTEGER; dp: DlogRPtr; max,i,w: Integer; BEGIN dp := DlogRPtr(GetWRefCon(SFFileDlg)); VScrollBar := dp^.VBar; HScrollBar := dp^.HBar; List := dp^.List; rows := dp^.Rows; {set scroll bar values} if List = NIL then SetCtlMax(VScrollBar, 0) else begin if List^^.howmany >= rows then SetCtlMax(VScrollBar,List^^.howmany-rows) else SetCtlMax(VScrollBar,0); end; SetCtlValue(VScrollBar, 0); if GetCtlMax(VScrollBar) < 1 then HiLiteControl(VScrollBar,255) else HiLiteControl(VScrollBar,0); { the maximum value of the horizontal scroll bar is the maximum width of the strings in the file list minus the width of the name list box } SetCtlValue(HScrollBar,0); if List = NIL then HiLiteControl(HScrollBar,255) else begin max := 0; for i := 0 to List^^.howmany - 1 do begin w := StringWidth(List^^.table[i].str^^); if w > max then max := w; end; GetDItem(SFFileDlg,getNmList,itemType,itemHdl,itemRect); w := (itemRect.right - itemRect.left) + 4; { 4 for frame + offset } if max <= w then HiLiteControl(HScrollBar,255) else begin SetCtlMax(HScrollBar,((max - w) div IncHoriz) + 2); HiLiteControl(HScrollBar,0); end; end; {make open button inactive initially} GetDItem(SFFileDlg,getOpen,itemType,itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl), 255); {Set scrollbar old settings = to zero initially} dp^.oldVert := 0; dp^.oldHoriz := 0; END; FUNCTION SFFilter(theDialog: DialogPtr; VAR theEvent: EventRecord; VAR theItem: INTEGER): Boolean; VAR DIEvent: EventRecord; DIpt: Point; DIOK: Boolean; ItemType: integer; ItemHdl: Handle; ItemRect: Rect; ItemPt: Point; dp: DlogRPtr; tmpDrvPtr:DrvElPtr; i: Integer; BEGIN if GetNextEvent(diskMask,DIEvent) then begin dp := DlogRPtr(GetWRefCon(theDialog)); if HiWord(DIEvent.message) <> noErr then begin DIPt.v := 120; DIPt.h := 150; if DIBadMount(DIpt, DIEvent.message) = noErr then DIOK := true else DIOK := false; end else DIOK := true; if DIOK then begin { set up from new drive } DelFList(dp^.list); dp^.list := GetFList(LoWord(DIEvent.message),dp^.tnum, dp^.tlist,dp^.filter); SortFList(dp^.list); DlogSetUp(theDialog); { if 2 or more disks in, activate Drive button } i := 0; tmpDrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while tmpDrvPtr <> NIL do begin if DrvHasDisk(tmpDrvPtr) then i := i + 1; tmpDrvPtr := DrvElPtr(tmpDrvPtr^.qLink); end; if i > 1 then begin GetDItem(theDialog,getDrive,ItemType,ItemHdl, ItemRect); HiliteControl(ControlHandle(ItemHdl),0); end; { activate Eject button } GetDItem(theDialog,getEject,ItemType,ItemHdl,ItemRect); HiliteControl(ControlHandle(ItemHdl),0); { set drive pointer for later use } dp^.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while dp^.DrvPtr^.dQDrive <> LoWord(DIEvent.message) do dp^.DrvPtr := DrvElPtr(dp^.DrvPtr^.qLink); { cause the dialog to be redrawn } InvalRect(theDialog^.portRect); end; end; if (theEvent.what = mouseDown) then begin GetDItem(theDialog,getNmList,ItemType,ItemHdl,ItemRect); ItemPt := theEvent.where; GlobalToLocal(ItemPt); if PtInRect(ItemPt,ItemRect) then begin if BitAnd(theEvent.modifiers,shiftKey) <> 0 then theItem := shiftList else theItem := getNmList; SFFilter := true; exit(SFFilter); end; end; if (theEvent.what in [keyDown,autoKey]) & (BitAnd(theEvent.modifiers,BitOr(optionKey,cmdKey)) = 0) & (chr(BitAnd(theEvent.message,$ff)) = chr($0d) { cr }) then begin GetDItem(theDialog,getOpen,ItemType,ItemHdl,ItemRect); ItemPt.h := (ItemRect.left + ItemRect.right) div 2; ItemPt.v := (ItemRect.top + ItemRect.bottom) div 2; if TestControl(ControlHandle(ItemHdl),ItemPt) in [254,0] then SFFilter := false else begin theItem := 1; SFFilter := true; end; end else SFFilter := false; END; FUNCTION SFMGetFile(where: Point; fileFilter: ProcPtr; numTypes:Integer; typeList:SFTypeList; dlgHook:ProcPtr; flags:Integer): StrLHandle; Var SFFileDlg: DialogPtr; ItemHit: integer; ItemType: integer; ItemHdl: Handle; ItemRect: Rect; tmpRect: Rect; DRec: DlogRef; tmpDrvPtr: DrvElPtr; tmp: Integer; fi: FontInfo; PB: ParamBlockRec; err: OSErr; savePort: GrafPtr; v: VCBPtr; multichecked:Boolean; lastclick: LongInt; lastpoint: Point; tmppoint: Point; ip: IntPtr; Begin DRec.filter := fileFilter; DRec.tnum := numTypes; if numTypes > -1 then begin for tmp := 0 to numTypes - 1 do DRec.tlist[tmp] := typeList[tmp]; end; lastclick := 0; {create the dialog} GetPort(savePort); SFFileDlg := GetNewDialog(DlogResID, Nil, Pointer(-1)); MoveWindow(SFFileDlg, where.h, where.v, FALSE); SetPort(SFFileDlg); SetWRefCon(SFFileDlg,ORD4(@DRec)); GetDItem(SFFileDlg,getScroll,ItemType,ItemHdl,ItemRect); DRec.VBar := NewControl(SFFileDlg, ItemRect, '', TRUE, 0, 0, 0, scrollBarProc,0); GetDItem(SFFileDlg,getHScroll,ItemType,ItemHdl,ItemRect); GetDItem(SFFileDlg,getNmList,ItemType,ItemHdl,tmpRect); DRec.HBar := NewControl(SFFileDlg, ItemRect, '', TRUE, 0, 0, tmpRect.right div IncHoriz, scrollBarProc,0); {set up the multiple file check box using flags} GetDItem(SFFileDlg,getMChk,ItemType,ItemHdl,ItemRect); if BitAnd(flags,SFMmulti) = 0 then HideControl(ControlHandle(ItemHdl)) else if BitAnd(flags,SFMenable) = 0 then HiLiteControl(ControlHandle(ItemHdl),255); if BitAnd(flags,SFMstart) = 0 then multichecked := false else multichecked := true; SetCtlValue(ControlHandle(ItemHdl),ord(multichecked)); {set the font information} TextFont(0); TextFace([]); TextMode(srcOr); TextSize(12); GetFontInfo(fi); GetDItem(SFFileDlg, getNmList, ItemType, ItemHdl, ItemRect); Drec.rows := 1 + ((ItemRect.bottom-ItemRect.top) - 3) div (fi.ascent+fi.descent+fi.leading); {set up the drive button (oh, God, this is complicated...)} DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); if DRec.DrvPtr^.qLink = NIL then begin { only one drive attached } GetDItem(SFFileDlg,getDrive,itemType,itemHdl,itemRect); HideControl(ControlHandle(itemHdl)); { hide Drive button } if DrvHasDisk(DRec.DrvPtr) then { if it has a disk, set up from that } DRec.List := GetFList(DRec.DrvPtr^.dQDrive,numTypes,typeList, fileFilter) else begin { drive has no disk, disable Eject and set list to NIL } DRec.List := NIL; GetDItem(SFFileDlg,getEject,itemType,itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl), 255); end; end else begin {multiple drives in queue} tmp := 0; tmpDrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while tmpDrvPtr <> NIL do begin { count number of drives with disks } if DrvHasDisk(tmpDrvPtr) then tmp := tmp + 1; tmpDrvPtr := DrvElPtr(tmpDrvPtr^.qLink); end; if tmp = 0 then begin { no drives have disks, disable Eject and Drive } DRec.List := NIL; GetDItem(SFFileDlg,getEject,itemType,itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl), 255); GetDItem(SFFileDlg,getDrive,itemType,itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl), 255); end else if tmp = 1 then begin { only one drive has a disk, disable Drive } { find which drive it is that has a disk, set up from it } DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while NOT DrvHasDisk(DRec.DrvPtr) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); Drec.List := GetFList(DRec.DrvPtr^.dQDrive,numTypes,typeList, fileFilter); GetDItem(SFFileDlg,getDrive,itemType,itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl), 255); end else begin { multiple drives have disks } { try to use last volume seen by SFGetFile } v := VCBPtr(GetVCBQHdr^.qHead); tmp := IntPtr(SFSaveDisk)^; tmp := -tmp; while (v <> NIL) & (v^.vcbVRefNum <> tmp) do v := VCBPtr(v^.qLink); if v <> NIL then begin DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while (DRec.DrvPtr <> NIL) & (DRec.DrvPtr^.dQDrive <> v^.vcbDrvNum) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); end else begin { try to use default volume } v := VCBHdl(DefVCBPtr)^; DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while (DRec.DrvPtr <> NIL) & (DRec.DrvPtr^.dQDrive <> v^.vcbDrvNum) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); end; { if those volumes don't have disks, then use the first drive that does have a disk } if (DRec.DrvPtr = NIL) | NOT DrvHasDisk(DRec.DrvPtr) then begin DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while NOT DrvHasDisk(DRec.DrvPtr) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); end; { set up from whatever disk was chosen } Drec.List := GetFList(DRec.DrvPtr^.dQDrive, numTypes, typeList, fileFilter); end; end; if Drec.List <> NIL then SortFList(Drec.List); {set the gray line userItem} GetDItem(SFFileDlg, GrayLine, ItemType, ItemHdl, ItemRect); ItemHdl := Handle(ORD(@DrawGrayLine)); SetDItem(SFFileDlg, GrayLine, ItemType, ItemHdl, ItemRect); {set the Box UserItem} GetDItem(SFFileDlg, getNmList, ItemType, ItemHdl, ItemRect); ItemHdl := Handle(ORD(@BoxContents)); SetDItem(SFFileDlg, getNmList, ItemType, ItemHdl, ItemRect); {set the volume name UserItem} GetDItem(SFFileDlg, VolName, ItemType, ItemHdl, ItemRect); ItemHdl := Handle(ORD(@PrVName)); SetDItem(SFFileDlg, VolName, ItemType, ItemHdl, ItemRect); {set up control values and such} DlogSetUp(SFFileDlg); {everything has been setup, show the dialog window} ShowWindow(SFFileDlg); {draw the fake grow box} GetDItem(SFFileDlg, getScroll, ItemType, ItemHdl, ItemRect); tmpRect.top := ItemRect.bottom - 1; tmpRect.right := ItemRect.right; GetDItem(SFFileDlg, getHScroll, ItemType, ItemHdl, ItemRect); tmpRect.left := ItemRect.right - 1; tmpRect.bottom := ItemRect.bottom; FrameRect(tmpRect); InsetRect(tmpRect,2,2); while (tmpRect.right-tmpRect.left) >= 2 do begin FrameRect(tmpRect); InsetRect(tmpRect,2,2); end; {now start processing some user inputs} Repeat ModalDialog(@SFFilter, ItemHit); Case ItemHit of getEject: begin PB.ioNamePtr := NIL; PB.ioVRefNum := DRec.List^^.volume; err := PBEject(@PB); if err = noErr then begin DelFList(DRec.List); { try to move on to next drive. However, all the drives may be empty. } DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); while (DRec.DrvPtr <> NIL) & NOT DrvHasDisk(DRec.DrvPtr) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); if DRec.DrvPtr = NIL then begin DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while (DRec.DrvPtr <> NIL) & NOT DrvHasDisk(DRec.DrvPtr) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); end; if DRec.DrvPtr = NIL then begin {all empty} DRec.List := NIL; GetDItem(SFFileDlg,getEject,itemType, itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl),255); GetDItem(SFFileDlg,getDrive,itemType, itemHdl,itemRect); HiLiteControl(ControlHandle(itemHdl),255); end else begin Drec.List := GetFList(DRec.DrvPtr^.dQDrive, numTypes,typeList,fileFilter); SortFList(Drec.List); { if fewer than two drives have disks, then deactivate the Drive button } tmp := 1; tmpDrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); while tmpDrvPtr <> NIL do begin if DrvHasDisk(tmpDrvPtr) then tmp := tmp + 1; tmpDrvPtr := DrvElPtr(tmpDrvPtr^.qLink); end; if tmp < 2 then begin GetDItem(SFFileDlg,getDrive, itemType,itemHdl, itemRect); HiLiteControl(ControlHandle( itemHdl),255); end; end; DlogSetUp(SFFileDlg); InvalRect(SFFileDlg^.portRect); end; end; getDrive: begin DelFList(DRec.List); DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); while (DRec.DrvPtr <> NIL) & NOT DrvHasDisk(DRec.DrvPtr) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); if DRec.DrvPtr = NIL then begin DRec.DrvPtr := DrvElPtr(GetDrvQHdr^.qHead); while NOT DrvHasDisk(DRec.DrvPtr) do DRec.DrvPtr := DrvElPtr(DRec.DrvPtr^.qLink); end; Drec.List := GetFList(DRec.DrvPtr^.dQDrive,numTypes, typeList,fileFilter); SortFList(Drec.list); DlogSetUp(SFFileDlg); InvalRect(SFFileDlg^.portRect); end; shiftList: begin if multichecked then MultClick(SFFileDlg, DRec.VBar, Drec.rows) else begin GetDItem(SFFileDlg,getMChk,ItemType,ItemHdl, ItemRect); SetCtlValue(ControlHandle(ItemHdl),1); MultClick(SFFileDlg, DRec.VBar, Drec.rows); SetCtlValue(ControlHandle(ItemHdl),0); end; end; getNmList: begin if multichecked then MultClick(SFFileDlg, DRec.VBar, Drec.rows) else begin { is this a double click? Note that I do not quite do this by the book; there are extra delays in the times involved, since I don't use the times stored in event records. It should all average out, though. } if TickCount - lastClick < GetDblTime then begin { time OK for double click, check motion } GetMouse(tmpPoint); if (abs(tmpPoint.h - lastPoint.h) < 3) & (abs(tmpPoint.v - lastPoint.v) < 3) then itemHit := getOpen else begin lastpoint := tmppoint; SingClick(SFFileDlg, DRec.VBar, Drec.rows); lastclick := TickCount; end end else begin GetMouse(lastPoint); SingClick(SFFileDlg, DRec.VBar, Drec.rows); lastclick := TickCount; end; end; end; getScroll: VScroller(SFFileDlg); getHScroll: HScroller(SFFileDlg); getMChk: begin GetDItem(SFFileDlg,getMChk,ItemType,ItemHdl,ItemRect); if multichecked then SetCtlValue(ControlHandle(ItemHdl),0) else SetCtlValue(ControlHandle(ItemHdl),1); multichecked := NOT multichecked; end; End; Until ItemHit in [getOpen,getCancel]; { set last volume seen by standard file package } if DRec.DrvPtr <> NIL then begin v := VCBPtr(GetVCBQHdr^.qHead); while (v <> NIL) & (v^.vcbDrvNum <> DRec.DrvPtr^.dQDrive) do v := VCBPtr(v^.qLink); if v <> NIL then begin ip := IntPtr(SFSaveDisk); ip^ := -v^.vcbVRefNum; end; end; if DRec.List <> NIL then begin CompFList(DRec.List); if itemHit <> getOpen then begin DelFList(DRec.List); DRec.List := NIL; end; end; DisposeControl(DRec.VBar); DisposeControl(DRec.HBar); DisposDialog(SFFileDlg); SetPort(savePort); SFMGetFile := DRec.List; End; END. !E!O!F! # # echo extracting sfmget/sfmgetr.text... cat >sfmget/sfmgetr.text <<'!E!O!F!' * resource compiler input file for SFMGetFile resources * sfmget-sfmgetr.rsrc * this is the dialog referenced as SFDialog in program Type DLOG ,-4001 0 0 152 358 InVisible 1 NoGoAway 0 -4001 * Item list for the SFdialog Type DITL ,-4001 12 BtnItem Enabled 28 162 46 242 Open BtnItem Disabled 59 1552 77 1232 Invisible BtnItem Enabled 90 162 108 242 Cancel *the volume name UserItem Disabled 22 258 54 354 BtnItem Enabled 59 266 77 346 Eject BtnItem Enabled 90 266 108 346 Drive *the window in the dialog UserItem Enabled 11 12 125 135 *the scroll bar for the window UserItem Enabled 11 134 125 150 *the gray line UserItem Disabled 20 254 116 255 StatText Disabled 20 1044 116 1145 Invisible Text *the horizontal scroll bar UserItem Enabled 124 12 140 135 ChkItem Enabled 125 172 140 346 Multiple File Selection !E!O!F! # # echo extracting sfmget/sfmtest.text... cat >sfmget/sfmtest.text <<'!E!O!F!' {$X-} {$U-} {$R-} Program SFMTest; USES {$U Obj-Memtypes } MemTypes, {$U Obj-QuickDraw } QuickDraw, {$U Obj-OSIntf } OSIntf, {$U Obj-ToolIntf } ToolIntf, {$U Obj-PackIntf } PackIntf, {$U Obj-WritelnWindow } WritelnWindow, {$U sfmget-SFMIntf } SFMIntf; CONST AppleMenu = 256; FileMenu = 257; FilterDlog = 256; TypeDlog = 257; TYPE myTypeList = ARRAY [0..7] OF OSType; VAR Finished: Boolean; {used to terminate the program} myFilter: ProcPtr; bounds: Rect; numTypes: INTEGER; typeList: myTypeList; FUNCTION theFilter(PB:ParmBlkPtr):Boolean; VAR theDialog: DialogPtr; itemHit: INTEGER; BEGIN ParamText(PB^.ioNamePtr^,'','',''); theDialog := GetNewDialog(FilterDlog,NIL,POINTER(-1)); ModalDialog(NIL,itemHit); DisposDialog(theDialog); if itemHit = 1 then theFilter := FALSE else theFilter := TRUE; END; PROCEDURE SetTypes; VAR theDialog: DialogPtr; itemHit: INTEGER; ItemType: INTEGER; ItemHdl: Handle; ItemBox: Rect; s: STR255; i: INTEGER; tmp: LongInt; tp: OSType; BEGIN theDialog := GetNewDialog(TypeDlog,NIL,POINTER(-1)); ModalDialog(NIL,ItemHit); if ItemHit = 2 then begin DisposDialog(theDialog); exit(SetTypes); end; GetDItem(theDialog,3,ItemType,ItemHdl,ItemBox); GetIText(ItemHdl,s); StringToNum(s,tmp); numTypes := tmp; for i := 0 to 7 do begin GetDItem(theDialog,4+i,ItemType,ItemHdl,ItemBox); GetIText(ItemHdl,s); BlockMove(POINTER(ORD4(@s)+1),@tp,4); typeList[i] := tp; end; DisposDialog(theDialog); END; PROCEDURE Break; inline $FACE; PROCEDURE ProcessMenu_in(CodeWord:longint); Var Menu_No: integer; {menu number that was selected} Item_No: integer; {item in menu that was selected} NameHolder: Str255; {name holder for desk accessory or font} DNA: integer; {OpenDA will never return 0, so don't care} pt: Point; h: StrLHandle; err: OSErr; i: Integer; reply: SFReply; Begin If CodeWord <> 0 then begin Menu_No := HiWord(CodeWord); {get the Hi word of...} Item_no := LoWord(CodeWord); {get the Lo word of...} Case Menu_No of AppleMenu:Begin GetItem(GetMHandle(AppleMenu), Item_No, NameHolder); DNA := OpenDeskAcc(NameHolder); End; FileMenu: case Item_No of 1: begin pt.v := 100; pt.h := 100; h := XMGetFile(pt,myFilter,numTypes,@typeList,NIL, BitOr(SFMmulti,SFMenable)); if h <> NIL then begin WriteLn('Volume ',h^^.volume:1,': ',h^^.vname, ' (',h^^.howmany:1,' files selected)'); for i := 0 to h^^.howmany-1 do WriteLn(' ',h^^.table[i].str^^); DelFList(h); end; SFMEnd; end; 2: err := Eject(NIL,1); 3: err := Eject(NIL,2); 4: if myFilter = NIL then begin CheckItem(GetMenu(Menu_No), Item_No, TRUE); myFilter := @theFilter; end else begin CheckItem(GetMenu(Menu_No), Item_No, FALSE); myFilter := NIL; end; 5: SetTypes; 6: begin pt.v := 100; pt.h := 100; SFPutFile(pt,'Having fun with your','Luau Frog Giblets?', NIL,reply); end; 7: Finished := True; end; End;{case of Menu_No} end; {the If codeword <> 0} HiliteMenu(0); {unhilite after processing menu} End; {of ProcessMenu_in procedure} PROCEDURE DealwthMouseDowns(Event:EventRecord); Var WindowPointedTo: WindowPtr; MouseLoc:Point; WindoLoc:integer; Begin MouseLoc := Event.Where; WindoLoc := FindWindow(MouseLoc, WindowPointedTo); Case WindoLoc of inMenuBar: ProcessMenu_in(MenuSelect(MouseLoc)); inSysWindow: SystemClick(Event,WindowPointedTo); inContent,inGrow: WWMouseDown(WindoLoc, Event.where, Event.modifiers); End{ of case}; End; PROCEDURE DealwthKeyDowns(Event:EventRecord); Var CharCode:char; Begin CharCode:= chr(Event.message MOD 128); If BitAnd(Event.modifier,CmdKey) = CmdKey then ProcessMenu_in(MenuKey(CharCode)); End; PROCEDURE MainEventLoop; Var Event:EventRecord; ProcessIt: Boolean; DIPt: Point; Fake: OSErr; Begin Repeat SystemTask; {so you can support Desk Accessories} ProcessIt := GetNextEvent(EveryEvent,Event); If ProcessIt{is true} then {we'll ProcessIt} Case Event.what of activateEvt: WWActivateEvent(Event.modifiers); updateEvt : WWUpdateEvent; mouseDown : DealwthMouseDowns(Event); KeyDown : DealwthKeyDowns (Event); diskEvt : if HiWord(Event.message) <> noErr then begin DIPt.v := 120; DIPt.h := 150; Fake := DIBadMount(DIpt, Event.message); end; End;{of Case} Until Finished; {terminate the program} End; BEGIN InitGraf(@thePort); MaxApplZone; MoreMasters; MoreMasters; MoreMasters; MoreMasters; {init everything in case the app is the Startup App} InitFonts; InitWindows; InitMenus; TEInit; InitDialogs(Nil); InitCursor; WWInit(200,80); bounds.top := 40; bounds.left := 10; bounds.bottom := 340; bounds.right := 500; WWNew(bounds,'Test Window',FALSE,TRUE,1,9); Finished := False; myFilter := NIL; numTypes := -1; FlushEvents(everyEvent,0); AddResMenu(GetMenu(AppleMenu),'DRVR'); InsertMenu(GetMenu(AppleMenu),0); InsertMenu(GetMenu(FileMenu),0); DrawMenuBar; {all done so show the menu bar} MainEventLoop; END. !E!O!F! # # echo extracting sfmget/sfmtesto.text... cat >sfmget/sfmtesto.text <<'!E!O!F!' sfmget-sfmtesto.rsrc Type CODE sfmget-sfmtestL,0 Type SOFT = DRVR sfmget-sfmgetfileL!SFMGetFile,1 (16) !E!O!F! # # echo extracting sfmget/sfmtestr.text... cat >sfmget/sfmtestr.text <<'!E!O!F!' * Input Resource File for Standard File example * sfmget-sfmtestr.Rsrc * apple menu, 14 is apple symbol in HEX Type MENU ,256 \14 * file menu ,257 File Open /O Eject Internal /I Eject External /E Filter Files /F Set Types /S Put File Test /P Quit /Q Type DLOG ,256 80 80 170 380 Visible 2 NoGoAway 0 256 Type DITL ,256 3 BtnItem Enabled 55 20 80 130 YES BtnItem Enabled 55 170 80 280 NO StatText Disabled 15 20 45 280 Should I allow the file "^0" to be used? Type DLOG ,257 178 163 328 463 Visible 3 NoGoAway 0 257 Type DITL ,257 12 BtnItem Enabled 105 40 137 120 OK BtnItem Enabled 105 185 137 265 CANCEL EditText Disabled 15 205 32 245 -1 EditText Disabled 40 25 56 75 TEXT EditText Disabled 40 95 56 145 MACA EditText Disabled 40 160 56 210 APPL EditText Disabled 40 225 56 275 MACS EditText Disabled 65 25 81 75 ZSYS EditText Disabled 65 95 81 145 PFIL EditText Disabled 65 160 81 210 FNDR EditText Disabled 65 225 81 275 DAMN StatText Disabled 15 25 31 185 Number of File Types: !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting cust.Hqx... cat >cust.Hqx <<'!E!O!F!' (This file must be converted with BinHex 4.0) :"'0eFh3!39"36%098e3J!*!('J#q0J#3"!%!N!-B!*!$&`#3"1)!N2-L)8098e3 J9Q9bFfP[EL!b,#!a-#"6CA"dC@eLCA)J-6Ni03#3!`G"8&"-!*!'(%098e3!N!- "5801)`#3"B"'8N9'!*!&J!!!!H!!$3#3"43!EJ!M!,i%!Np,!*!&&!%%!#-"9!3 '3f&ZBf9X!*!&-J$$!%%"AT!!&84PCQ&eE(4-Ef0KE%P33@4NFQ9cF`#3"NX!``" D!9k3!"*%C@CKG@ad4eG*8%&NC(*PFh-!N!9N!--!F`&HN!!54'9QBA9XG%j659" "C'4bCA0c!*!&I3$$!)`"AT!!$d4PCQ&eE(49Ff9b6Q&YC3#3"TB!``#P!9k3!!p %C@CKG@ad5'pcG%jKE@8!N!D[!!S![J&H"5e#BA0P)%a[Bf&X)%P3)%&NC(*PFh- JEfiJ3A"`E'9LGA-J6QpNC5"1G@eLCA)!N!Bb!!S!33#qL"&-Ef0KE#"*8#""C'4 bCA0c1J#3"NX!#J"D!,k)%dGKG'9hBANJ59!J3@4NFQ9cFcS!N!CN!!S!F`#qL"G 1B@eP)&0PFRCPFL"*8#""C'4bCA0c1J#3"Rd!#J#-!,k)#P9cCA)J6Q&YC6S!N!@ @!!S!T3#qL"9%C@CKG@ad)%C[FQ9TCfiJ5'pcG$S!N!E)!!S""!'3!)JX3h9cG'p YDATKG'P[EL"QD@aP)'j[G#"QEh9ZC#!Y,5"MFQ9KG'PZCb"TG#i!N!-9!"i!&!% X!HS!!3%!N!J$!*!%0!%!N!RrN!3%4QPXC3P$GA0dEfeTHQ8!N!3%9A0PFJ#3"!4 4G@Pd!*!(!3#3"32Mjr!%!J#!"!)!J!3#!)!$`i#!!#)!J!!L!)!!)J#!"m2JJ!# 3#83F!!"%)J!!4#)!!%3L!!"%2!!!4#!!!%3J!!"%)!!!1#!!N!Tm(!!!%#)!!"! L!!!3)J!!%$`!!"!J!!!3)!!!%#!!!(`J!!rrrrJ2rrri$rrrq!rrrrJ2rrri$rr rq!rrrrJ2rrri$rrrq!rrrrJ2rrri!2rr!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2r r!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2rr!!$rr`!!rrm!!2r r!!$rr`!!rrm!N!3`!*!$3!!!!J#3"#!!N!-J#1ir2!!"UI!"2Mmm!!1Tm!!!2c` !!kR`!Nir2!!$UI!!!!k)!*!$!8j@r[`[,Ir@2bi!#%KYrbT)E[rm5'hr)UQ0,bi !#NKZr[a1ZJbF,blrr%KZr[bTMdjH)&pF6dl36PEqr#mYrpBr,J!)5'hr+NKZrra )EImLUBd[,[rm5'lqr+Q3!%+R5'lqr%kk$53YA`!+6PiJAe426Y"19[R'51F2!%* R5'lkb%KZqXC1ZJZH5PpR"%*ZqXJ`,[V)5-$3[)!#!!!Y32hF,Ab!!J"!rH!YI)! #!%$pj#emJ!)!32hS3Hlpl%2k##BJf5$C-*&"l[lZ3rS)%#$C)0NGI!!"rZa#CdK k"l"#CdKZrqj1ZJKk2Kp+4fC),A`!!!)5rGK#CcmZrqir2!!"3UG1ZJPU2Kp+4fB @3QFr,[rZ5'lpf%(ZrG`[#%kk#*Jq(d(Zr0C$qJH#F!`Jf90!E[S`N@"H$%Ire@C B3QG)HJG-3QF[1JGJ,cS(@%kk#,3q(dT(CK*#CdKk"c*#CdKZrqj1ZJIm2KmYI!! !!K,pf%T(CK"#CcmZrqj)E[hB6VS)aMiI3HlmeN2k"X*`%#$C8d"ZqNT(Cc")abm (5'lmeNkk#24)HJCk5'lmeNKZqFBr2!!#6VS*'N(Zr0C$l[R'F%!Jf90!E[T#Tcm m!!&"lImX,`K`rbm!UA`VArr@,bhreMmm!!K)EImU5'lrp%KYrb+TM5mYrpBr2!! 15'hr+NKZrr")EImLUBd[,[rd%#lql%L!2`#TBbmZrG`r2!!$6VVphLmZrH!r2!! %6VVpdLmZrH3r2!!&6VVpaLmYrpBr2!!'5'hr+NKZrrK)EImLUBd[,[ri5'lpl+Q 2,bhreMmm!!G)EImU5'lrq%KYrb+TM5mZrrK)E[lZUBm[,[r`5'lmeUQ23UG)E[r mUC&#,[c@3UFr2!!$6VVpRL`I5SCQ%N(Zr0C$qJ9BF!JJf90!E[TJ"#e'rGa)E[c @5(S%N!"1ZJKk%"r!,[lXCbC#TbmZrG`[22q3!`#S@$!ZqXK)`0#I,8$ph#mZrG` r2!!$6VVp%%+R2c`!"%kkr6iU(dU&CK4"l[c@3rS%eR!))0P63'lk-*&J"#e&rH" #Tcmm!!91Z[d@+"p+K'B83HlmeN2k")K`#5$C8d"ZqM#4B!3Y42hN,bhreMmm!!C )EImU5'lrq%KYrb+TM5mZrrK)E[V+UC!!%#lkbJ*!!2m-3!!)Ea*"l[c@3rS%(R! *)0P63'lkB$!3,[V+!N!!rdT!CK4"l[c@3rS$hR!))0P63'lk-*&J%%(ZrHa$l[V +F%!Jf90!E[S[,Ir@2c`!"dKYrbT)E[ri5'hr)UQ0,blrq%KZqmUTN!!3,[[+!N! !rdT!CK4"l[c@3rS$E(!))0P63'lk-*&J%%(ZrZj$l[[+F%!Jf90!E[S`,[rm8d" R%&0!C`!"%Pe!C`!"B'!!!Ea)E[c@5(S$,Nkk"aJ3(fF!!1a"l[c@3rS#r(!))0P 63'lk,blrm%KZr0DTMd+R5'lrr+Q4$'i!!IrmCb3pI!!1rra"l[c@3rS#Y(!')0P 63'lk,blrm%KZr0DTMf!!!9`YI!!!!K,pf%*R2blrlMmm!!&#Tdkk"Giq(dT(CKC #CcmZrqj)E[hB3Hlph#m)6VS&%$iI5NGR)N(Zr0C$qJ)fF!SJf90!E[S[,[r`5'l meUQ23UG)E[rmUC&+4fB-3QFr,[rZ6VS%ZMiI5NGR)N(Zr0C$qJ(BF!SJf90!E[S [,[r`5'lmeUQ23UG)E[rmUC&+4fF'2A`!$[rmB!BpI!!1rraJ!!#f3HlmeN2k!A* `#b$C8d"ZqM#4,blrm%KZr0DTMd+R5'lrr+Q4$'i!!IrmCaSpI!!1rra"l[c@3rS ",R!%)0P63'lk-*&JEN*R2blrlNkk"#`q(f"J%#lql!S!!!%G32lX,blrp"!ZrZa )J$m!U@-3,[lXCbj"l[c@3rS!XR!1)0P63'lk-*&#TbmZrG`[22q3!`#S@$!ZqXK )`0#I,8$ph'!53HlmeN2k!$j`%5$C8d"ZqM#4-#lrr'XBX(`!"fi53IS!*$)!jNP %33%`%2m+2!!%CJ$m&LmYrpDTJNcI!2"1ANje!!C&6'pMB@`J59!J3@4NFQ9cFb" TFb"ZEb"XEfjRCA)JB5"QG@jMG'P[EL"[CL"dD'8J3A"`E'9LGA-JEQpNC5"ZG@e LCA)Z1%a[Bf&X)%P3)%&NC(*PFh-JEQph)'%JCR9ZBh4TEfiJEfBJ3A"`E'9LGA- JEQpNC5"ZG@eLCA)Z!"&$B@jMC@`J*f0KEQ0PE'9N*ba"FQ8JH@pe)(0eFQ8JH@p e)(GKER3JG'mJBf&ZBf9X2b!S8f9XC@0d)%p,+3!R9(*[G@*XC5"ME'pcD@jR)(4 SC5"MGA0dEfeTHQ&dD@pZ)'CTE'8Z*e4bEh9LE'8JGh*TG'PZCb"dD'8JBh9cG'p YDATKG'P[EL"QD@aP,KG6BACP)'KKFb"LC@9Z)'0KEQ0PE'9N,Kp3E'9KFf8JBfp ZCQPbE5"LH5"ME'PMDfPZCb!R6dXR!!!J6R9XE#"SEh0d)'jKE@8JDA-JEQpd)(" PFQeTG(4PC#i!)%jeE'`JGA0PFL"ZB@eP)'Pc)'j[G#"`CA*YDA4dC@3Z!#*9Ff9 b)'jKE@8JCh*PBA4PFL"dD'&Z)$JJBfKKFQ&MG'9b!#91B@eP)&0PFRCPFL"*8#" "C'4bCA0c)'Pc)'PXE#"QEh*YC@3Z)8GKG'9hBANJ59!J3@4NFQ9cFb"TFb"TE'` JCQpbE@9N,Kp-Ef0KE#"*8#""C'4bCA0c)'Pc)'PXE#"QEh*YC@3Z+d0[G@aN)'j [G#"[F'9Z)'0eFh4[E@PkBA4TEfiJCQPXC5!Y)(0dBA4eFb!r3h9cG'pYDATKG'P [EL"QD@aP)'j[G#"QEh9ZC#!Y)'0bC@&dD@jR)'Pd)(GTG'JJC'9QBA9XG#"fB@a eCA-Z&%0eFh4[E@PkBA4TEfiJ9Q&XG@9c!&4&@&3rN!3a3h9bFQ9ZG#"fB@aeCA- JCR*[E5"MGA0dEfeTHQ&dD@pZ)'CTE'8JBA*P)(0SEhGZ,JG9EQYZEhGZ#%&`F'a P68&$!%kk!EK19J!!,&p193!!Rqd!%%kk!ET"lIlq,`LSEUMq2ccrrd*R)"qJ-UN 5UFa#TkPlU&""lIr`3qhqLL$C)0P)EIri2c`!"$mm!"J`,Irf@8!r!$!Yrr4C3$m !U+G1Z[FN6VS"GNjG6VS"BNje6Pj1G8j@rmj"l[r1)@i!$J!5-@i!$!!@3LJ!'N) S!"Y#U!!FS!!LEJ!)-UJ!'$e!!"*1AL"Ihr`!N!-+6Y"19[r13HlrcM&Z!!J!'+! "28!!#NjH)&p8Mdl38F&J!P$"6PErcN(ZrmiKEJ!)!#!aEJ!3!"JLEJ!-)9%!*%* S!#a#U!!Z5J&Q"+!#B!+J!ce!!")LEJ!-)UJ!+%jH)PrIr!#3!`T1d8j@rl""l[q `)@i!%J!5-@i!%!!@3LJ!'U!)5N"Q&N*S!"bJ$#!)3qJ!)#,Z!!JLVJ!-S!dp3!! @6PiLAprm!*!$$Nl46PErcN(ZrmiLEJ!))9%!*$&Z!!`!'+!328!!$L+S!#K1AL* IA)p1d8j@rmj"l[r1-@i!$J!B-@i!$!!X)@i!#!!ZS%3p3!!36PiLAe#26Y%JE`! %)#m!#%*RUHiJAe"26Y"#V`!33IS!###[!!41G3#3"%je)&p1A5m)6VS!VNje)&p 193!!6Y"19J!!,&p)jq$J-#m!(#"[!"j$l`!L0!$P5Y,#3N&5L'!3*'K3DdN* J!K$D8FVrr&()rqiJE`!H%)&$l`!L-#m!(19)dX!M,`!B,dN!'%cI"`FZAdje6Pj 1G59I3d&8)*!$51I!`()"B!C)jm$!3N%JE`!B)Qm!&%*!%"L`'@B1B!5c#'B)8d" Uq!T"!!%I33!D,fm!%!!@60m$!eb26R919J!!3Uhr(N)YraK#,ImC3Uhr%N+Yr`j #VIm#6Pj1GD9*6NP85%9"!!"`!'!!!)T`!@!!!)4`!Q"qF!0JHR!%B(C`"@"bF!C JER!(B'T`#'"QF!PJBR!+B&j`#f"DF!aJ9R!0B&*`$Q"1F!pJ5R!3B%C`%@"#F"* J2R!6B$T`&'!fF"9J-R!@B#j`&f!UF"KJ*R!CB#*`'Q!HF"YJ'R!FB"C`(@!5F"j J$R!IB!T`)'!'F#&J!R!L(`!`H!$JX2J#VQ3!!!iJH!$J)&!J8%U3!'BN98m[2'& dF'bTR$!ICb*C6bmmBA4`E$m!UCdJ(fF-)%!I2!!M,a"#&dje-$J+B'B%-$cr3(3 !FJ!8(b*I3IS!'K)`)!$H`3a#!#*R#Ja#!!&Z!N*!2S"1d3#3"!B#"JB%#!))#!3 !!!J#"T!&"!D3"!i'"!!!#!!!6PEkr#eZ!!crr%+R%#lrr!*!!2p)`#m!,c`!N!2 rU&K)E[[m6VVpX%+R%#lrr3*!!2p)`#m!,c`!N!2rU&K)E[cm6VVpNN+R%#lrrJ* !!2p)`#m!,c`!N!2rU&K)E[hm6VVpG%+R%#lrr`*!!2p)`#m!,c`!N!2rU&K)E[l m6VVp9L"Z!!J[#%KZqra)HJ!i5'lmr%Kk!$")E[hm5(S!+%KZr[a)E[Vm2c`!"dk kr@)JAd2Zq[a`3#$C8d"ZqNjH)&p36dl3!5j19[rf51F2!#"Z!!J3%!*!!2mq!%T (CJK#VJ!-B!!!a#"Z!!K`!4)`!!!#33$rDaLbI!!rEK*"qJ$%-!(Q5%4!!c!!r`S m!!4Q@%)%$%F!#'`%1JGJ!RS)28ArpR`#B"JJEJ!)%$"J!!*!!2m-3!!ZCJ*i!9* 'D3DmE[rfEq)3"'F33UF[,J!)6Ud!1LeI!!aJ8%+R,bi!#%kY!#SYA`!-B%!JEJ! )F!%5-!!!!N%!r`a"!#0A`#"Z!!Kb!43`%!!#3J$r$%)!*&I"J!&R%%+R,bi!#%k Y!$)YA`!-B!4#VJ!-3Hd!-Lm)UI&-h`$`6PiZRdje!rm!N!N%!!J!N!3$C!!)!!0 19[ri51F$##KZ!!J3&!*!!2pV',"m!!pZ%N(k!5)b!1C*4%%"-"$r#M`!"'F)3Ui !$'!!!1K#Ka!8!N!!rce!rrKm!Q!!!-S30'!!!N!!rfXBX(`!2fi53IS!iM)!jNP %33%`%2m+2!!%CKSJ"qQ!%M4J!!*"!2q5I!!`5-(5J#i"B!!!L"!dB!!#3!$rDaL `I!"[EK*"qJ#F-J$Q584"!6!3r`Sm!!4Q(#!(kB!50'!!!N%!rj*m!'(5I!!+5-( 5J#i"B%B30'!!!N!!rfXBX(`!6fi53IS!@$)!jNP%33%`%2m+2!!%CK`J"qQ!%M4 J!!*"!2q5I!""dR`!#NM"dS!Z!@!')!ITJ#i!8NCT#,aZrrK[!2mb,8F!$%cI%-" 1ALkI6R8!IJ#3$!2r!*!'!ra19[rZ51F2##KZ!!K`!4fmri!!mA!#(E`!!J$aF!0 #0J$aF!4#0J$aIJ&m!@!!!)C#44!8!N!!rl"(A-!50(!!!N%!rfXBXR`!2fi53IS !`M3"jNT%3J-`)2m+2!!%9m(!!@FN)!AR3")dF!!#33$rdN#5I!!`1J%-43$rE`C #VJ!-B(T54f#U)!BGK3$l1!B3&!*!!2q`4fdL%$4`!!*!!2m-3!!XCJ454f!'3Ui !$'"-8NB-4J!%E`$rGK!8!N!!rl"(E3C#VJ!-B$)pI!!%rrTJ($!ZrrS50N$l!N% !raf"!2&64!a%!!&Y$&0ZrrS-EJ!"rrTXh#eZrr)!$%cI%2"1ALkI6R8!r`#3"Nj @rqj)j`m)+'i!#(!"(EcrJ!$aF!)G[!!#!2&`!d)f!2&`"%)f!2&q!A`"B!!!L%* &%"3#3!$rX%GF`")dF!!#33$rDaLbI!!rEK*"qJ$%0!(Q5N4#!c!Jr`Sm!!4A`F! "CbBJ"F(m!!S50(!!!N%!rp*!NR`!-$S"$%8!rfm'3Ui!$'"k8NGJU#!'(B8!qcJ '%"3#3!$rX%GY)K!dF!!#3!$r$%!!,QB%8NGJ"N+Z!!aJ6&*'$%B!"'m!rh33&!* !!2q`4fd'3Ui!$'!b2A`!"2rkB"``,[rk%MC!q`*"!2mGJ3$a8d3-4!!"E3a6E[r k$'i!!IrkE0`YE[rb!!a-ha$`6PiZRdje!rm!N2m!N'BF!1)!"d098e3!N!0#4P* &4J#3!dj#6N4-!*!$@N4*9%`!N!0Q4%a24`#3!h*048j9!*!$INP$6L-!N!1+3dp %43!$!*B!!2rr!*!*J2rr!*!$*J#3"B$rr`#3!c%!N!8$rrm!N!04!*!&!Irr!!! #03#3"!%!rrm!!!*1!*!&J2rr!!!#KJ#3"[rr)!!$LJ#3"3(rrc3!!li!N!8#rrm `!"*+!*!&!rrr-!!58J#3r`#3)j%V!: !E!O!F! # echo extracting sfmtest.Hqx... cat >sfmtest.Hqx <<'!E!O!F!' (This file must be converted with BinHex 4.0) :"h0QEA4PFh3!39"36$q3"#!!N!G#lF01!*!%!3#3!d)!N!0"!*!%l3#3meS!!J# 3"6F!&!"3!))%!eP&8`#3"MF!UJ"3!4J%!Nj2!*!&$`!8!#d"')JS8fK[G@aN)%N JB@aXEhFJG'KP)'CTE'8J)Pi`)L"dEb"LC5"eFf9N2`#3!qS!#`#3"@N!+!#*!(J %!Np,!*!&D3#j!)N"#33'3d&13d9-!*!&$`$0!#!!pC!!!Lda!*!&+!!C!$J!5j! !"&4&@&3!N!8S!&m!1!#4N!!%68&$33#3"5J!S!!i!0+3!!4"8&"-!*!&+!$K!$J "%j!!"%e"3e-!N!9"!"N!83",N!!%@P0C8`#3"8%!A`"4!*'3!!434NP-!*!&33# J!&%!dT!!"%C14&)!N!9"!1%!83%6N!!%4%&06J#3"3m!'3!I!,Q)&8jeE@*PFL" [CL"'D@aP)&4jF'9c1J#3""8!8!"3!+S"I!!#!3#3"`%!N!89!,)!S`&)!Fm!!`% !N!F"!3#3""%"!*!*rj!%!43!N!5%!3%!N!MrN!3%4QPXC392F'9Z)!"2!!!24@T PBh3J5@jdCA*ZB@`J!%N!!!p&DQ9MG#"&H(4PFQjKE#!!43!!$8CTE(4PFL"'D@a PFb!!4J!!#P0PG#"8HA"PFb!!8`!!$P"eG#"'D@aP)&4PFh3J!&!!!!94G@Pd)!" 4!*!'qJ!,!*!&(!#L!#i!mJ3')#"2F'9Z!*!&1`B3!%d%d)3,)#"*ERCTFfPLE'8 !N!CD!+)!E!$b"!JJ)%0KEQ0PE!#3"4B"!J!f!@+!!*!'1`%+!%d"@J3()#"&DQ9 MG!#3"PS"#J"X!9S%"b!J4(*TGQ8!N!B,!!`!I3#(!*!(#`#'!(d!PJ#3"a3!rJ" d!2q!!*!'&!38!(3%HBJ15@jfDA0TBQaP)&4PH(3!N!9m!!`!M!#(!*!(I3#X!)` "@J8A6A9XG'P`E'8J4QPXC5"6C@aPBh4TEfi!N!39!*!&Q!&Q!!%!N!M`A`#3!b+ f6[S2&%lk%AK1qKK`6[S4l%lk%a!JE`!%,Tp1d%j@rmC)j`mB*Qi!#%+R,`ZT&b" I,9$rj%+R,`ZT&b"I+#J!"#m,2c`!"dKZrpa)E[rB5'lrd+Q03Hlrb%2Zrp!Jf5$ C5'lrb$mm!!%r2!!"U+P)E[r)U+0#Tbm,U4FJAbKS!!JJ$'B%B!!!d%KZrrLSLc! Zrp$3E[ri8N!q!%*R,blrj+PJ1Kp#Tbm,U4FJAceS!"MraN*'B!!!Q#!'d%8J9," 3E34J!!#5-#lrdP4!,`"#Cbm%U@!b(b!Ijd'3!%%r!$m(U*-J9#!'d%A"r!!')(! !)#m3U)3J9#!'d%A"r!!'%M!!*'Fb5'lrl$!Zrp*53$m!)!H3!'lrq$m!-#lreP0 !2`!`,[rkd%Fb,[rqdN!r!DLR5'lrl+LN-#lrq0"(-LlrqY*!-#lrrY""2J"54QN )['lraQm!rf4-haM`6PiZRdje6PErlLmZ!!Sr,J!)5'lrrNKZrrT)E[rbUBdpE[r brqipE[rdrr#SRMmZrr!r,[rZU*-`,[rZX'lrpQ`D3QFr2!!#U*3`,[rZ9%!p32r Z3QG#CkL5B0a1AL"IA%p1d%j@rpT)j`%B,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8K Zrr+SSd+R,bi!#UNA)&mSD!!))!aQ"'!!!**)E[rQU)Y#TkMB*Pm[#kKk5'lrmUK l3QFJ9%KS!!5SM$iI-#lrq*!!E[rdX%G[1M!ZrrL3!'lrp*!!4dM!JI`!!Y"Zrr3 r!$!Zrr*@3$)ZrrD5E[rb5-'$r!!#dN!r!DL6)&4)D!!%U)4J)#!8@)"5J#m!)&3 3+!!%!N!!rdM!,`")E[rb2c`!!DR1,`ZSH5m,U0P-haL!6PiJAea26Y"19[r-51F 2'%+RU0JSAd+RU0JQAd+R,bi!#+NA,KmJ4bS3,bi!#$mm!!G)E[rF5'lrf%KZrp# TM5"(1#J!&%*R,`@TB$`I5'lriUL,)!53!%Bb,[rNdQlriM3ZrqM83F(#28$rqNK Zrr)r,[r52blrd$mZrpBr,[r8U+G)E[rb2c`!!6mm!!'SU8KZrr*#CcmZrrS[$+M [,`ZSHL"85'J!!UKl,bi!#%kkr1![#kKj)%Fa4J!8,`ZSf5m-U0P-haM`6PiZRdj e6PB!!#m-+'i!#JaZ!"8!#&I!CMB[!%*R,`bTB$)I)"p)jm!!3QF[$+PL0"p-h`! $Y%&H`F!"Ca)[$%*R,`bTB$!I8N!r!+PMB$i-EJ!8!!KA`'Bd,`"#Cbm-U@!b(b! I51I!!%*R,`bTB63I60m!!l4"AF(!!@F3,`a#Cbm-U@!`(e0!2`#TBb"8,bJ!"%k krUSSAdjH)&pF6dl36PB!!#m-+'i!#JaZ!"B!#'BJ,`a#Cbm-U@"#Tb"8,bJ!"+N A)&m`(j!!D!!B2`#TBf!Q$'i!&`!)CKi[$%*R,`bTB%+R)&3[+!!%U4FJAc!S!"M 3Acm!U@0#Cbm-U@"#Cbm-U@)`(l"IE!`[$%*R,`bTBUPMB"a#Cbm-U@"#Cbm-U@% `(l"IE`S[$%*R,`bTBDPM)&3[+!!%6VVq"#KI6PiJAea26Y"19[rd51F$!%KZrrL TFN*R,blrq#mZ!!K)E[rmU@`q(dT(Ch``"`4!!"4R&&0!CbC63'Fi8d"R3!4!!'T R4Q"Q3QF[,[rm,blrq%(krR)[#+PS2"pJ8%*R,blrr#mZrrK"q[jF,`LTD$`IB$S [,[rm2`G1Z[lQB#i[,[rm2`G1Z[lDB#*#CbmZrr`[,[ri3UHTD%TIC`J[,J!)6VV pA'!'2c`!!kR)60m!`%jH,Tp1G8j@rp4)j`mB3UHSf#KI3UHSf#CI3UF[,J!)U4F Z(b"(+LJ!"#mZ!!Jr2!!(5'lrj%KZrq")E[rBUBdJ4cJS!"C#Cbm&U@!m(b!%N!" 'jd!p32rk5'lrmMmZrpSr,[rB2blrhMmZrpbSTdKZrr)r2!!"2c`!!DLT5'lrmMm ZrrT#Cbm-U1m[#kKk)&4)D!!#U(X[,J!)6VVk1Lm,U(NJ4c&'!"B[#kMC,`bSf8c I'2"1ALkI6R919J!!,``SEJ!+$'i!&3!)9m"Q0Lm!3QF[$+PJ-KmJ(dMR`!"#Cbm -U@)d(dcI!!1d39l"`!&R%Lm-3QF[$+PJ-"p53$m!U@0J2JaZ!"3!#&I!CM3[!%* R,`bTB$)I)"p)jm!!3QF[$+PK0"p-h`!$Y%&G`F!"Ca![$%*R,`bTB$!I8d!r!+P M)&3[+!!%6VVq[#KI6PiJAea26Y"19J!!,``SEJ!+$'i!&J!)CK)[$%*R,`bTB$! I@d!r!+PMB"J-EJ!A!!KQ%#m-3QF[$+PJ-"pD3$m!U@0#Cbm-U@"#Cbm-U@)`(l" IE!`[$%*R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%`(l"IE`S[$%*R,`bTBDPM)&3[+!! %6VVq-LKI6PiJAea26Y"19[rd51F$!%KZrrLTFN*R,blrq#mZ!!K)E[rmU@`q(dT (CeS`"`4!!"4R&&0!Ca"63'FL8d"R(J4!!'TR*'"%3QF[,[rm,blrq%(krSi[#+P S2"pJ,LmZrr`r"dkkraKJ)N*R,blrr#mZrrK#TkPS5PpR##mZ!!K1Z[fXB!Br2!! $UFK-h`$!6PiZRdje6PErkLm-,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8+RU0JSAbm -U(T)E[rbU(Y)E[rbU+%[,J!+6VVi-#m-U(N[$+MC+&p1AL"IA%p1d%j@rqK)j`- )3UF[,J!)U4FJAbKS!!K#"L"8-""63$e!rqK#4f!8)&3J"m(m!!B5-!!NC`*m!9* (D3DqE[rSEqB[,J!)2c`!!8KZrrC)E[rb5'lrkUQ0%!CR#LmZrr*#CkPGB!S[,[r b2c`!rkPG60m3`%jH,Tp1G8j@rmT)j`m)3UF[,J!1U4FJAbKS!!JJ$'B+2c`!!kR )B!!$1NKZrr+SLc!Zrr63E[rb-Llrq0*!1!%[,J!12c`!"dKZrq*)E[rH5'lreUQ 0HJ*#CbmZ!!UTBMeIrmj#CbmZ!!UTB$iI3QHTFa!IC`!#j+Qd5'lrr+Pb-#lreP* !X'lrr'm!!*4+4fm1,bi!#L!(8d!r!+PMB!*Jc&0(5)8`"@Fm8d"R5&0!C`*J6L" 8)!I"r!!'%M!!*'F%HJ&J!N)&)&3J"m(m!!B5-!!N#J%!!5"8)!I"r!!'%B%!*'! F)&3J"m(m!!B4[!!"!#4J$#"8)!I"r!!'3M!!*$e(rmS[,J!16VVj&R!%,`")E[r 56VSBbQ!!re*J%M!ZrpC53$)Zrrb53%M"Jm3m!6!Z!!K63,"'E!!![,jZrmjX$Lm Z!!SJ"e*!2`#TBf!%B!$r'P*(5)8`"@G18d"RB&0!C`*JE#"8-#i!#&0!d%I"r!! '%M!!*'F%HJ&J!N)&)&3`,J!)8d$34m(m!!B5-!!N#J%!!5"8-#i!#&0!d%I"r!! '%B%!*'!S)&3`,J!)8d$34m(m!!B4[!!"!#4J%L"8-#i!#&0!d%I"r!!'3M!!*$! Z!!K630"(28$rbLmZ!!j1Z[JmF!3[!%KZrp*1ZKI`B!$qH#!'d%FJ9,"3E34J!2j U5)8`"@F!!)C63'F!!1*63'F%B!!"0#"8)!E34m(m!!B5-!!NC`4k!@!#3J8J9#! 'd%I"r!!'%M!!*!S"!!%J9#!'d%I"r!!'%B%!*$!ZrpK53$e!rq``,[r@8N!L"X2 %dN!p3IrU-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST'!!!,iJ9#! 'd%I"r!!'%M!!*!S"!!&R6#"8)!E34m(m!!B4[!!"!#3`,[rB8N!p32rX-#lreP* !)JE$a0*!28(rkM!Zrpa63$e!rr!`,[r@8N!L"P*"`m653$e"rqj)E[rUU+4J@L" 8)!E34m(m!!B5-!!NCdSJ9#!'d%I"r!!'3M!!*$!ZrpK53$e!rq``,[r@8N!L"X2 %dN!p3IrU-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST#!'d%Fp32r +B!$p&LmZ!!j1Z[`Z60m3m%jH)&rHr!!+6Y"19[rX51F$##"Z!!K$l[ri5K!Lf#, B+'i!%M`Z!"!J9$!38d!p32rX3NGJG#"8)!I"r!!'%M!!*'GL)&3J"m(m!!C#-!! N[%GI`'j3)JH54V*Z!!jG`F!"Cd*)E[rZ-#lrqP*!2`!`,[ri8N!L"j*'`qi!$0* !2`%`,[rq8d!r!$!ZrrK53#)(NNC53F2Z!!c53$m"U+G)E[rZU+454fN'[Qlrl'q '60m3`%jH)&rHr!!16Y"19[r+51F2'#CZ!!Si,J!)3UF[,J!1U4FJAbKS!!JJ$'B +2c`!!kR)B!!#5%KZrr+SLc!Zrr63E[rb-Llrq0*!2!%[,J!12c`!"dKZrq*)E[r H5'lreUQ03QF[#kPL29rrd%*R,`ZTB$iI,``r"cm%2`C)E[r@6VVqe%*RUA-3(fF !!CDTY%KZrrbTFM!ZrpC53,"Zrra[D%*R,`ZTB$iI5NG[-#"8)!G63-(m!!B5-!! N#J%!!@F3,``r"cm%2`C)E[r@6VVqKLm,)!G63$m!U@0J!Q#N)&3J"e0!`I`!"K' m!!%!*#mZ!!j1Z[85F!3[!%KZrp*1ZK6'B!$rIQ!5-#lreP*!-Llrr**!5-'$aMS ")!463,"&E(C#Cbm,U@!q(ljZrp"X0L"8)!G53#)%8d(53-2m!!B3-"!N#J!!!@F 3,``r"cm%2`C)E[r@6VVpr#m,)!G53$m!U@0J"'!!raSJ9#!(8N!L"&0"dN$$r!! '%E`!!4!N,bi!$Nkkp)"`"#m!5'lrdNkk&$4J!2lX3QF[#kPJ2KmJ"G"()&5`8'd %B!$qeL"8)!A34m(m!!B5-!!N#J%!!@GD,``r"cm%2`C)E[r@6VVpK#"8)!A34m( m!!B4[!!"!#4)E[rU-#lrf&*!2`!`,[r@8N!L"F2'dN!r!6!Zrpa63$m!-#lreP* !)J953F2'dN!r!DLR5'lrkULNB!$qC#mZ!!ir2!!"5'lriNKZrpj)E[r@UBdJ9$! 38d!p32r+3Qlrc'!J)&3`,[r-`I`!"K)`!#4R#LmZrpj#CkPGB"T5E[r-D3S`,[r -X'lrbQr@,blrhMmm!2qTA8cI'2"1AL"Ih[`!#Nl36PEqJNMR$aJJEJ!-3qlrm%S 3)YJLf#,B)YJq,J!55NGQ$L"m!!!$8LK32L`!6Q"L5NG[-N+R6VS6(L"I+'J!!L! -9X"R$VjX!%K@`F!"C`3S9'$X)!aQ#$mmrmJ`(kR*2L`!6Q!X3UG1ZK,X)&mSD!! #)!a@`'F1[Q`!6PE"`!&R"#K8B1`J$'B)2ccrh6!IUFNpE!!8rST#Th!'`HlqLY" m!#C)`#m!6VS5BLCI3QFqZ!)J5Pp@`'B))JYA`B!"C`Jr2!!C-"qTb5"6-8F!!L" 6@)K$l!!XF!FJf90!E[T#E[k-3N3pE[k+rS3pI!!"rqaJ!!%N3LlrXMe(rkj#,[k B3HlqQ#e)rkSpE[rXrl4#Cd(ZrjJ[#%*R6VS54$`I5NCR"Mm'-"qTb3aZrrm!%'G !3J8`,J!38d!p32k#3QlrlQ!L-#lrlZ9!3IB!m%2ZrlK`!l%*9XMrr'B%HJ&J&&* ZrqjT#M!Zrqk`E[k#Ep4J!RS"%!9R)%UZ!!K@`F!"CaC#Cd(ZrjJ[##mZ!!K1ZZp Z'Km+"3!"%!9RFN+R%#lqQ!*!!2p53%M!,`"1ZK&H,9rqP%*R2VJ#)%TI9X"Q#NU ZrT4A`B!"C`Jr2!!C-"qTb8(ZrTJ[##"ZrT3[%"!ZrTJ#3!$r8N")`#m!6VS43#" 6)!6"r!!')DlqP!!J)&-J"-(m!!C#-!!N8N4J"&*ZrSa5E[rXD3``,[rXX'lqK'm !rY3`,[k+N!"ZrS`J8c#!,`XJ8h!'`G$3I!!Q5-![!%kk%1"#Ccki!L!m(dT'C`B r"M!IUFNY5`!860mBm%jH)&rHr!!-6Y"19[rk51F$##KZ!!JJ$'B#B'!J9$!38d! p32rk3NCJ0#"8)!E"r!!'5V!!)'FL)&3J"X(m!!B[-!!J6VS3G%*R2VJ#)$iI5NG R"Mm(-"qTb9*'D3DmE[rkEmB[$%kk%&*#Ccki!L!q(dT(C`Br"c!IUFP-ha$!6Pi ZRdje6PErpNMR$`JSEJ!))!aQ"'!!!3a#4#"8-""63$e!rrC#4f!!!0SJ9#!(`I` !"K)`!#3+!3!"Cc)J9#!(`I`!"Lm`!#"1ZJrZ3QFqZ!)J1Kp+4@F'2`8`(kR*)&3 J"m(m!!C#X!!JB!!!NP*%5NGH`'m!!)JJ9#)(8d($r!!'5V!3)&I"`!&RFL!(8d! m!'!5)&3J"X(m!!C+X!!JC`*J"P0'5NCXkNT'9m"Q$L"85UJ!)&I"`!&R!Rcr)&3 J"m(m!!BL9#)'8N($r!!')l!!)"!J)&3J"P*!`I`!"K'm!!%!*#"8)!I"r!!'3V! !)#"8)!I"r!!'3M!!*&*(D3LqE[rfE`$r)L"8-)3[$#"8F!E"d0"m!#C)`#m!6VS 2)NcI%2"1ALkI6R919[rf51F('#KZ!!Km!4!'C`!!kN)')&3`%&9!28$rpN*(B!! !c%*R)&3J"m(m!!BJF!!J)""5J#m!)&3J"e*!`I`!"L"`!#!J%&+!,`!J9#!(`I` !"L"`!#!J8"!3!N!!rcm!)&3J"e*!`I`!"L"`!#!J8"!3!N!!rcm!6VS2!JaI!!& QCR`")&3J"m(m!!BQF!!J)&3J"m(m!!BD-!!N)&3J"e*!`I`!"L*8)JI$r!!')l! !)"!J)&3J"e*!`I`!"L*8)JI$r!!'%l!!*"!N)&3J"e*!`I`!"L',!#!J9#!(8N$ "r!!'%B8!*&*(D3LqE[rfE`$r-'!!ra4-haMJ6PiZRdje6PErr#!Z!!KAJ#e!rr` JE[rm%"")J'XBX(`!$fi53IS!*M)!jNP%33%`%2m+2!!%CJJGI!!"!!aJ"%)Z!!a 1ALkI6R8""Nj@rpK)j`mB3UF[,J!)U4FQAbK6,#X!"#iV!!JpD`!Brq4+KfB),`a #CkPPB#3J4b"3-"#`E[rNE4)[$#"()&!`%*!!E[rN2`#TC@!',`a#CkPP,`a#CkP M3QF[$+PL$&m!!@`+,``r2!$rU9eJ"Lm-3QHTA5m'3QHTBdU(CJ`["Mmm!2qTA@! !!)a#4#"()&!`%&0!28$rf%*ZrpaJ*%*R)%FJ8$!Zrpc"r!!')(!!)#m3U)`k(lK &E!)i"9*ZrpaT#M!Zrpb`E[rBEp)[,J!)2c`!"dKZrrj)E[rk5'lrmUQ0-#lrq*! !E[rd@%!k!,T%E3S["Mmm!2qTA@!B,`BJ"*!!48M!JI`!#&4!2`#TC5m'3QHTA5m Z!!Jr2!!"5'lrrNKZrrT)E[rbUBd[,[rk2c`!rkPG3QX!&%*V!"C-haM`6PiZRdj e6PErcNMR$aJQEJ!3+Li!$%*R2c`!J%KZrr#TF"!IC`!"%N+R,`ZT&bKI3QF[,[r bU'T+AfFQ2A`!H2rX2A`!P[rZ3QF[,[rX,blrmNkk$*T+AfB%I!&J"N)'B!*m!4! 'C`!!d#mX!!K1Z[YJ3UG#CbmZrr+SDcmX!"T)E!!F,b`!%%kkq0iTA`!),b`!#%k kr0i[#dkkrL4#4%+R6VS-%#"I,LJ!!NU(Ca4#Cbm(6VVpa"!IC`*54#"(,K"Jk!a %!!&[(#m,2c`!"NKZrqK)E[rN5'lrh+Q0,blrj%*RU9d[#cmm!!9)E[rS5'lrj%K ZrpbTM5mZrq4#CkPG3UG1ZJZZ)&mTD!!#!!`JE!!-,`K#CbmZrr+SDc!I)&q`D!! 'C`SJE!!-+9!!$'$H5'X!%+NS)%8-8!!"CQB[#cmm!!G)E[rS5'lrj%KZrpbTM5" &,@J!#[rB5'lrf+Ka3QF[,[rB5'lrh+LY%"pR0%+R)%8`+!!15-![!#mm!!!#!+K B5TpR#L"Z!!J`[!$rB!JJEJ!)-,`!"aem!!%!&'!!!2JJ46!3DaL`I!!(EK*"qJ% B-J$Q584"!6!3r`Sm!!4A`'B!!0![!%+R)%8b+!!15-%[!5m!3UF[2!!!#!![2!! !!3#S@b)I)"m[!DKB)KmJ(dU"9m(!!@F!!*S[!%+R)%8[+!!#,c`!N!2rU&JL(b! I$%%!$9I"`!&RH#m,2c`!!8KZrqK)E[rN5'lrh+Q0-#lriY"Zrpj)`)(m!!)p32r D-#lri0"Zrpa)`)(m!!)p32rB3QF[,[rN,blrf+PQ-"pV',"m!2pZ%N(k!&Sb!1C *4%%"-"$r#M`!"'B'3Li!&'!8)'i!#$#m!!%GI!!"!"4J"%)Z!"4-haM`6PiJApl m!!a1d%!!N"i"!#K19[lm51F2'#"Z!!j$l[r`5K!Lf#,B)YJLf$JZ!")YEJ!8riJ p42q5$%6rrfmN)!463$e!r[a#4f!5)!IP3#)(j8%YYK$`!*454fN'[Qlqr'rS3Ul r#NKZra5SG%+R2cc`Ad+RF2m[!+Pm+&m[$$mZ!"Sr,J!B3QHT'bm-U(-[$%(ZrhJ [#+NB,``r2!!)5'lrk%KZrq4)E[rFUBe#Tbm-5'lrh%Kk#1JI2!!"3QG#Cd*R2c` !%%+RU93YArpi,``r2!!,5'lrk%KZrq4)E[rFUBd[$$mm!!G)E[rS5'lrj%KZrp5 TM8+R,`a)E[rF5(S)RKmm!!&#Cd*R-#lrfNM!JI`!#$m!2c`!%%+RU93YArpm,`` r2!!-5'lrk%KZrq4)E[rFUBe#Tc!Z!!K)`#m!F!%[!+KB5TpQ##mZrq5T@'!H3UF `,J!)5-![!(!#,`#S@%UICJS[,[rN2c`!rkPG3UF`,J!)5-![!(!%,`#S@%UICJ4 #"@!#HJ%[,[rN5)8r"DPM3QHSKcmk#!+SL$mm!!'SL6mm!!bSLNKZrfUSLbm-2c` !"dKZrqK)E[rN5'lrh+Q0-#lri*!!E[rF9d!b,[pXdQlrDM3Zrh$838M!JF*53$e !rj!!3UG1ZJJ1)&mYD!!#ri3JE[q%5T!!CQ`[$$mm!!C)E[rS5'lrj%KZrpbTM5m Zrq5T@%*R,blrK%kkqD!3(fFH3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41Z[4b,9rrJ'! L3UlrJ#m-2c`!"8KZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA@!!!HK#4d+R6VS(LL" I,#J!!NU'Ca4#Cbm'6VVj2K!IC`*54b"',""Jk%T(CN4#V[q!,``r2!!&5'lrk%K Zrq4)E[rFUBd[,[rN2c`!rkPG,``r2!!'5'lrk%KZrq4)E[rFUBd[,[rN2c`!rkP GB!!"HJa(!!&QD%+R6VS('#"I,@J!![q%3QF[,[q%6VVic"!I#J!!!@F+)'lrK#e 3ri4Jj%+R)'lrK$mS!!Br"%KZrr![,J!86VVcN!!YArq!,``r2!!'5'lrk%KZrq4 )E[rFUBd[,[rN2c`!rkPGB!!"$%+R6VS'ZL"I*QJ!!L"m!!!#&$i3)!G%3$i!)!Y @`'F1[QX!6PE"`!&R"#C6B1`J#fFd3UG1ZJCq)&mYD!!#ri4+V[q%9X"R(#"Zri3 b+!!'XQX!5&E"`!&R#L"Zri3Y82q%B0aJ1L"m!!!$8LC33UG1ZJC#)&mYD!!#ri4 +V[q%9X"R(#"Zri3b+!!'XQX!5&E"`!&R#L"Zri3Y82q%B0a+V[q%9m"R(#m!3QF [,[q%6VVhb")I)"m+!3!"J!%#3!!"CbT#Tdkk"H`JAbeS!!,rK%*R,blrK%kkpk! 3(`S!!!&R#L"Zri3Y82q%B14#Tb"Zri3r+!!'2`4)E[r`,bi!&%kkmQ3YArq!5Ul rJ'F),blrJ%kkpPi[$$mm!!P)E[rS5'lrj%KZrpbTM8(kj)3Y52rN,``r2!!*2bl rk#mZrq4)E[rFUBi[$$mm!!G)E[rS5'lrj%KZrpbTM8(kkUJY52rN,``r2!!(2bl rk#mZrq4)E[rFUBi[$$mm!!4)E[rS5'lrj%KZrpbTM8(kj))Y52rN,``r2!!%2bl rk#mZrq4)E[rFUBi[$%kkpa3[$+N9,``r2!!)5'lrk%KZrq4)E[rFUBd`,[rJ8d! p32r82@lri[rD,``r2!!,5'lrk%KZrq4)E[rFUBd`,[rL8d!p32r@2@lri2rB5'l re+LK5'lre$mm!!)r2!!#U+N`,[rDN!"ZrpB-3!!#E4C)E[r8U+&)E[r82c`!!Mm m!!+SU@$F3IVhdLm)5'lrkUQ4-#lrkPY!CbT63'F!!BC63'F!!QT63'F!!`KA3'F !!`T63'F!!``%3!$cC`!#"'!!!c4#V[mX)'lrJ#"32@J!![m`3QG"l[mD,`K1ZJ4 329rr'%TZraKQ!!%f,blrJ%kkmc)JE[q%,9$rK%UZri4@`'FL,`"#CbmZri41Z[A !%KmJ(`S"!!(!!@F+)'lrK#e3ri4JeNUZri4Q1%+R6VS$f#"I,@J!![q%5UlrK&E !Cb)[!%*R,blrK%kkpB)5(b!I#J%!!F!"C`SJE[q%,9$rK'$@5UlrK'C#3UlrJ#m -2c`!"8KZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA5m-2c`!"NKZrqK)E[rN5'lrh+Q 0,blrj$mm!2qTA@"S3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41ZZrk,9rrJ#mZri"1Z[2 kIJ%JE[q%,""+KQF83QF["Nkkp1`3(fF#8NFJ4L`3B1J-4`!#E"i[$$mm!!C)E[r S5'lrj%KZrpbTM5mZrq3r2!$rU9d[$%kkp2a)E!!3U5KJ!!(8,blrJ%kkmISJE[q %,9$rK%UZri4@`'FL,`"#CbmZri41Z[5)%KmJ(`S"!!(!!@F+)'lrK#e3ri4JeNU Zri4Q+N+R6VS#S#"I,@J!![q%3QF[,[q%6VVd9"!I#J!!!@F+)'lrK#e3ri4Jj%+ R)'lrK$mS!!Br"%KZrr![,J!86VV['#eIri![,[q!6VVc'#m-6VVdANKX!"#T+'! !!6B3"@F3,``[,[pi2blrN!"1ZZK-B$3[$$mm!!a)E[rS5'lrj%KZrpbTM5mZrq3 r2!!"U@-[$#mZrhJr,[q3!%kkk"i[,[rN3QHTBf!!!1S3"@F5,``[,[pi2blrN!" 1ZZJ!B!!!e%+RUA8J(j!!V[m+,`"#Tbki![!L(b!IXS"[9NKZr`+TFM!Zr`53!'l r#'S#4%!-3!!$AF"X(M)Zr`+5E[m'DJ*%33a"!!0G`F!"C`JpI!!"rqTJ1LeZr`, r"Lm-,blrH$mZrj!!6VVVY%+RUA8YArm+B'*)E[m'UA)[$#mZrhJr,[q3!%kkkjC #TkPe,9rr#Q"%,`a1ZZ1-B$`[$%kkjJjJ0#m-2c`!$%KZrqK)E[rN5'lrh+Q0%!9 R#LmZrq4#CkPMB!S[,[rN2c`!!DPM)!8+!!!"'J!`,[rUDaL`I!!(EK*"qJ#S-J$ Q584"!6!3r`Sm!!4Q!2a`5UlrK'G!3UG1ZJ$`)&mQD!!#)!Y@`'F@)'lrK$)V!%L bD!!'9X(!!@F%*P0Jj#!,Ca3YI!!!!K6qrM!V!%j%3#"Zr[i`J%UZri"R(#mZri" 1Z[!f$'i!!IrUC``[,[q!6VV[UN+Zri![,[piU98[,[pmU98[$+Q$,blr&+Kc,@l rJ!!F60mBm%jH)&rHr!!86Y!!#J#3"#*I)"qJ6#k!6R&`!#m*-F!#)%jeF!"JpL* I)"qK)Lk)6[VrkL*I)&qJ)dlkrq!LAb!I)&qJ*%lkrp3L(b!I)PmJAk!Z)N&1q[r %)"mLAb"I,`#J1b+!6R8[I!!!!`J!"%je,h`!!!0@!!41G5*I%"mJAfB%S!aJ!U3 -2S"1d5*I)&qJ&ck!6Y&d!L"I2`)[#+hTG!"1q[rd0$`!#Nlk!!Bd2!!-)&mr!Lm )VHd!N!1S!*!$Z!!!!J)!N!1B!*!$)!8%2c`!!DR`"NJr2!!"UI!'8Mmm!!'Tm!C F2c`!!DR`"M`r2!!"UI!+)Mmm!!'Tm!SB2c`!!DR`#Rir2!!"UI!+X$mm!!'Tm!S X2c`!!DR`#c!r2!!"UI!!V$mm!!+Tm!1U2c`!!UR`"3Br2!!#UI!)R$mm!!+Tm!% 82c`!!UR`!TBr2!!#UI!!!$mm!!1Tm!'Q2c`!!kR`!!!-h!#3!`Y19[rk)'i!##m S!"*)HJ"35(S!6%Kk!%LTLd+R2c`"!%+RF2m[!+Pm,9rrr%+R5'lrqUQ4,blrr+Q $$'i!!IrkCJC#,J!-B!BGI!!"!!a1ALkI6RA85%9'58a843!#!!"19[lL51F"#%+ R2c`"!8+RF2m[!+Pm+&p#TdKZrrUTN3aZ!!,rqQB),`bTJf!!!)3[$$mm!!0)E[r i5'lrp%KZrqbTM5mZrr4)E[lXUC!!5'lql%KZrZC1ZJB!1flqk2r`3NGJ4#m-)!G B3$m!5'lrq%KZrr4)E[rXUBd[,[rd5'lql+Q3!%(ZrZ`J#&+!,`""l[lL,`K`"#m !6VS&FL!(j8!VV[lL!0"54`a(!!G[YLm-UB0-ha#!6Pj1GG0&9&4C8%96!!"19[k L51F2#%UZ!!KR!!(k3QF[,J!)U'Sm(d*R,bi!#+KV2Km`"J4!!3"R#&0!Cb4J!!( @3UFr2!%!U8Nr"dKZr[bT4N*R5'lqr+Qf29rqqQ!!!EB`"e0!D`!"VJa!!!CZ!!' QidJ`1`!'6[X!N!-3!4`",!%m!@`"FJ'@2A`!C2lf2A`!C2li3UF[,[lf,bhrqMm Yrr""lIr3,`K#Td+RF!%[!(!#,`#S@b!I2`"1ZJU8+&mJ$'F!!-J[,3!-5(S"U%* R6VS*4LmY!!`J9$!S!!*)`#m!2c`!!8kk#,![,3!-5(S"J%*R6VS*)LmY!!`J9%K S!!4#Cdkk#4)[,3!-5(S"AN*R6VS*"#mY!!`J9$!35-![!$mm!!&1ZJK`,bd!$%K k!5T#Cdkk#1)[,3!-6VS(eL"8-""63$e!rU*#4@!b,bd!$%Kk!3*#Cdkk#,i[,3! -)&3J"F(m!!BJF!!J,a"#Cdkk#+B[,3!-6VS(QP*&D3DkE[kLEmJ[$%kk#Ej1ZJR DB!!!JN*R3UFr2!!"6VS$aMJIB("#Cd+R2c`!!Nkk!lBi(f"J5UhrqQBB3UFr"UQ r2`FI2!!"U89"q[dH+dMrqQ"#3UFr"UQr2`G#CkP&3UhrqQ!`6VVpBQ!U2A`!C2l f2A`!C2li,blqpNKk!%4)HJ!X3UG)E[kN6VS$I'!''h`!!Irr3QHT1%cI%2"1ALk I6RA38Np$490663"3%NaeBA8J4R*[Cb"(D@*XCA4c2`!85'&fD@jR)'CeEL"hDA4 S)(P[GA)!!b#3!a!JCQPXCA-JFf9XC@0dC@3T!!)J+!!#1L!!"eC[E(9YC5"19[r Q,`FJEJ!)3qlrm#,B)YJLf#,B,@lrq[rS3QF[,[rS5'lrl+NX2Km`"e0!C`j63'F B8d"R)&9!CaaJ+%+R,blrk+Np6VVp5'!D5'lrm#mZrqbTXf!12`F[,[rk2blrrNk Y!)SZ(djH,Tp1GF4&38aA9%K0!!"19[rZ)'i!#%2Zrr!Lf#,B)YJLf#mZrr)[2!# 3!i"1ZJFm)"mp32rZ3UF`,[rq5-![!#mm!!!"!+KB$*m!!!%!CJa#TcmZrqkT2Nk kr-a1ALkI6RA%48&-9e4)5`!!6PErk+Qd3QFr22rr5'lrm+P`(9rrla!ZrqpRD$! Zrr"63'FL98"R+&G!Ca463'FU8d"R!Q"12blrrNkY!(TJ4%kY!**J2NKZrr"1Z[l HB$4)E[r`6VVr6'!U3QF[,[rbU'T+AfFH2A`!H2rU2A`!P[rX3QF[,[rU,blrmNk k!FSpArrS%#hrrfF!rhT1ANjec8&*6N9@48i!!%kk"#j19J!!,&p193!!Rqd!%%k k"$""lIr-,`LSENkk!141ZJ%q6VS"1Nkk!6C1ZJ%bU2kT%UN`UFa#TkPlU&!r2!$ )2c`!8%kY!+SlI!!Srr)lI!!+rr3lI!&8rrBlI!(drrK)EIrb5(S!GN*R(c`!!6m m!!%r2!!*6Ud!XN)Yrrp#VIrk1hcrN!2`2ccrrd*R)"qJ-N+R2c`"!+Qr,cS!2+P 03UFr2!%!UEp#CkNe3UFr2!%"UEp#CkNeU6G1Z[kQ6VS$M%jG6VS$H%je6Pj1GG0 '694&8e3J!""%8PC5#e4PFh3J9fPZC'ph)PmJ(k"-,S"1FA!!,`Na`!)J6R9`!'$ f5MJ#MQSU)(J"-%2i!43J#*!!NA3-X)*P&L*4)FJ"&#+!3K%LH!+U)SJJJY'T!!a 1GD"M6R8LAb!IS5)ZL%lkrlBLAb"IS#01q[qX)PmJAk!T6[VrSL*I)&qJ+NlkrjL J0Nlkrj3L(b!I)PmJAk!Z)N&1q[q#6PEr`%(Zrm!aEJ!)!"BKEJ!+!"+J&ce!!!j 1AL*IA)p1dA3")&mr!Lm)VHTd!L"I2`)[#+hTG!"1q[rd)'m!#$mm!!'TlL"[!!3 JJ%lk!!iJE`!%)#m!#%*RUHiJAe"26Y"19J!!,&p)jr$i3S-f!'m!!9JN5#C*)JK Q8NSi#20R"Nkk"3+SrNKk!9+SEdAk!8`eI!!%!%3eI!!*!%T)HJ)+U)Y"qJ)%-#J !"Y"3d'J!!M&!!!Jr2!!'2bJ!#+L63UHSf%(k!HiJRbY+!!`L95K4)ST"qJ%'XFT R+N(k!GC+N!"Q)NKk!F5SLd(k!Ei`+!!'d&$3D!!#-8!!#%+RU0K"qJ'b)*m-%`! 0CJ`r2!!'2bS!-+L6B!B-%`!+CLi`1J'5-J$3DJ!`@%#`DJ!8C44)DJ!33QG%36m ",cS"H+M[B!!!K%*R2`'SP'"k$"-!"fB)2c`!&+R)B'a"qJ&1FJ!b+!!%`X04Mb* 2$"-!#'B8-#S!-T!!360!!!)cDJ!b!!Bf!@!3-fS!-J!#-#S!-Y""-d!!"M!U!$# 3!&!bJ$!U!$$3D!!#-d!!"%K4U+03M``6!!KQ#N4$2`0#CkL8B!J[#d*R2`1SK5* 9)Sa-ham26R91ANje*8p99%0)3dm!N0a#V`!33IS!###[!!41G3#3"%je)&p1A5m )6VS#c%je)&p193!!6Y"#Cbm!)#m!"Lp!!!3rHJ!)!!JJ(dje!!"19J!!,&p)jm$ !)JKQ-L)k!*!!CL)JHJ#'6VVp8#))CJK"qJ"k)+d!$%(k!'SV5!!-+dJ!#'"-,`N r!#""6T!!B%)-%!!"CKSL1J"BCJSJHJ"16VVp''!X,`Nr!#""6T!!B#,4r!#3!a" )`#&!!#4#U!!Z-A`!!`!X)8N!)+!$3IVrJ$#!60m$!dje6Pj1G592994$5&-J!3# 3"J%!N!JLAd(krr3JRdl4)Pp"q[rZ)*p1d8j@!!!XAb*I)&m[#A!"(c`!$5*26VV r-K!I6R91ANje*9GI6%iJN!0)jd"J*%mb!%K!-$`J)1**C!*536m!8d&ZqL*25%" 1Z[m!,NT-h`B#6R919J!!,&mLAc!I%KmJAbm*8d"["%kkrm!I!5*2F!&1Z[l@%"p 1G8jH6R8P9ep$)*!%6PB!!#aI)Pmb(b!I)&m[#8MR(J!L6jrm!*!$$%*#,!"X!N5 !*J!S!d*%5%5)r!!++J3k!iVm!!T)4#B%0J9)43C&!$!6"9*#5S0QfNU'E!C53K- m!#df!CC#E`B`!dkkrd3`!NkkrPlIr!#3!`a-h`"i6R91ANje*9GI55#3"%j@!!! XAb)I-"mLAb"I,`")CX%&Z"NT!E`KJ#*!!38kkr`)`!8kkrKa1G8jH6R8P9ep 69&)J)%j@!!![!#m")#m!&#)[!""1ZJ!8,d!!&#)I)"p1ALpA!!4BMdje6PB!!#a I51Fq!#S!DJ*%J#`"DJ*%J53"5%*+3QBF0J"#3%K!C`5!`63!5%)`!i$"0!!L!N* !5%"J(#3!*J&#J%+"H"r8JY'!dS'`Jfd%N!#$8J&4c2r`5S9U!N5!ZiCU!N5"60m !I%je6Pj1G8P%59C06d3J6PB!!%(Yrm`[#+KZ6Pj1GFK*6NP84e*"!!"19J!!3Uh q'N)YrK4#,Ii93Uhq$N+YrJT#VIhq6Pj1GD9*6NP85%9"!*!'#P0'68GPG%CTE'8 !8dp'9%kk!$C1d8kk!$"1k3!%6VS!+%lT!!K1ZJ!J6ZN!$%kk!"K1k3!33IVra#" 3S#T"q[qm)&#J58je3IVrXNU3!'F))&"+N!"Q,+!M3UF[1[q`5(VrS+QK)"pQ"M! m!'1Tb8(kri`JJ#m!UC*"q[q#)&!L8%je#*!!!!B)d!!()P"1G3!!#3J!@!!'6PE rr#em!!!*m[rm)'lrr#e3!!K1ANje6PB!!#m-+'i!#$!X!!53!&3b,!!#NQ`!"V* !EJC#,J!-B!BGI!!"!!`SAdjH,Tp1G8j@!!![,IlqU(0#Cd*RU(JJEIr-5'J!%+K l6Pj1G8j@rrJ[,IlqU(-r,IlD2bhqf+Ki)'hrc%2ZrrK"k!!3)YJLf$!Zrrk3!(` !$ce!rri`,[rmN!"m!!mp32rm5'lrq+Kl6Pj1G8j@rqj)j`%)5'lrlUKd6VVrM#" Yrma$l[ri3HJ!%#,B)YK#"f!N5)FJ"d(YrYcP3#K`!!!3,J!*!N!!!@F',`bT9f! %,`bT@&)($!F!!@r@,bhqrUN%,blrlUKc60m3J%jH)&p86dl36PErf%MR$`Jm,J! )5'lriUKd3J9+4Qm!!8"#"bKZ!!Sp4[ri5NCH`$)YrZUbEIlkAF(!!5)(#J%!!F! "C`!!N!!JEJ!+'""64P+Z!!T)K!a%!!eQ"(i"B()-"!!)CaSJEIld)&!`,IlUd'h ql%L%%B3!!&*YrZTJ8NTYrZT[5NkkrY*)E[rB-#hqdT!!EIlN2`!`,Il3N!"YrZB r!$mYrY)`,IlSd'hqd*!!EIlQ2`#STc!YrY+3!'hqj$Y!rY*)E[rBU+06EIlUHJ& J!RS"B!$r@#!&#J!!!@FX6VVqHMmYrY)r,Il3U*-[$%*R-#lrq*!!4NL(N!"(2`# SK8KZrqDSQ#YZrqEqd$!YrZU`EIlkA-#!"`*!!!&R3#"Yr[!J8$!YrZa)`)(Yr[V M3$'YrZS!!%kk!ra+4Pl!)JF+!3!"`!&R&L"Yr[3J8$!YrZ`4[2r*!!!lI!!"rZT J!2kq)'hqm#"3-#hql%M!JHhqqZ0!-DhqkJ!!,blriUKc60m3m%jH)&pF6dl36PB !!$!Z!!K63-(YrZM3I!!+28!!#NjH)&p86dl36PErq%*R,bhqh+PJ29rrr$!YrYL 3!'lrr$e!rrK#CbmYrZ#TB$eIrri`,IlDN!"Zrrip32rk5QlrqPE!5Qlrq&E"J!& R+Nkkr@`JEIr-5'J!%$mZrrSr,[ri,bhq(UM[+flrr2lB,bhq(UNR6VS&I%jH6R9 19[rN51F2!(S+2LhqlL!(5-#"lIlk2!!pEIlmrq4i!@")2c`!"6m&U*-[,Ild6Ud !-L"Yr[3[%$m()'hqm#"3)!EM3$m`!!#SK5mYr[41V3!kfQhqk0jYr[T54VjYr[K Q"%*(3NC54'N'Z'lrj'qb5'lrjULB+flrj[l360m!m%jH6R919[rU51F$#%KZrqU SG%kkr)j#V[ri3Ulrr%KZrrLSHd)(B!!!kNL()!G"lIlFj8!SF!!!)'hrc%2ZrrK "k!!3)YJLf%L()!IM3$)f!2K638L()!IM3$f"!2K)Kd(YrVi3-(!!5)$M3$)f!2b 5I!!25)G"lIkq%$"`!%L!id!pJ3$i5)FJ"q0!-MB!r**m!!j)Kb!(id!pJ3$m5)G "lIkq%$"`!%L!id!b0J$idR`!%%L(3Hhq[K!`F!")J10!2B%!r#m-2blrqMmZrrL T@5m--#lrrT!!E[rk2`!`,[rmN!"ZrrJr!+PF5)FJ"d(YrY6M3$)Zrrb5E[ri0$! !!*4"2!*+4Q`#3NB[$$m'U@95"``(!!&[!2m56VS!&%kkrHi[,[rUU(0-ha$!6Pj 1G8j@rrJJEIr-,@J!&2rm-#lrr*!!I!!228$rq$!Zrrk3!(`!$ce!rrT)E[riU5K 1ANje6PErjNMR!3K)E[rQU(3`,J!19d"R!!#@8d"R$&0!CaT63'G`B!!"!#mYr[i [,J!+5'hqb+NPB!!!lNkkq`BJEIlq5HJ!%$!X!!D3!'`!!Me!rq``,!!%N!"828$ rkN+R,bhqrLmZ!!T)EIl!U5XYArrd5Ulrp'FD6VVr@#mYr[ir,[rf2blrp"mm!!' T(8kkrKCJ!!#83QF[,Ilq,bi!#UNH%"pR"LmYr[kT&Q"k3UHT*#!Yr[k`RfCS6VV kL%KZ!!USF8*R,bi!#LmYr[j)E[rZU@`q(dT(CdB`"`4!!"4R&&0!Ca"63'F-8d" R#!4!!'TR''!b3QF[,[rZ,bi!#N(k!ES[#+PS2KpJ(%*R,blrlLmZ!!T#TkPS2Kp 1Z[b5B!B[,IlqU4m[,[rQU(0-ha#!6PiJAe"26Y"19[rZ5'lrr+Kd5'lrpMmm!!8 r,Il3U)![,[rf6VS!RMYYrZlql%*YrZSJEIl`)&!`,IlX5-#"lIlkid!aVIlU!!! `,Ilkd@hqlM!YrZk`EIliCJ4#EIlZ5'hqd$mm!!9#CcmYr[a1Z[[`U)"1Z[Qk5'l rlMmm!!9`#T!!EIlQ2`!r,Il@-#hqk0"YrY#3!'hqjMm!U+G)E[rZ3QF`,IlS4%! r!#mYrKkSlbmYrKkT*dkk!Di[,[rmU(01ANje6PErqNMR!3K1Z[P%5'lrr$mm!$) r,IlSU)!JEIr-5HJ!%$!Zrrc3EJ!)-L`!"**m!!md,IlBe%'3!%)q!%T(EKJ`,J! )N!"Zrr`b,IlBdP53!%%q!%T(E3*#4bmYrYa#CbmYrYbTB#!(d&mr!+PM-#lrrY" Z!!Sb,!!'NR`!$c3YrYV83C!!3Mi!5NGZ'M!Z!!U3!'lrrM)YrYV5E!!#N!""2J" +4fd#3NF[,IlJ3QF[,IlJU@!J"p"I2`#TBdkkq`"-ha#!6PiZRdje6PErm%MR$aJ QEJ!+5Qi!#'F!!,i-EJ!8!!KA`!aZ!"B!#&I"J!&%!"`!3QF[#kPJ1Kp#Cbm,U@' kAel!)JE#!#!'#J!!!8MR`!"#Cbm,U@)d(dcI!!1d49l#`!+#!'G`)&0$l[rb8)J Lf#,B3QG)E[rb6VVhj"JI$'i!&J!)9m!-EJ!A!!KA`B!"Cb3JEIlq5HJ!%%L%)!6 M3%L%)J6M363d!!58G"!!P'hqk$i#B!3q,IlS%!CR"L!(4%!q!#m,)!A34cm!U@0 1Z[SU6VVhZNcI'2"1AL"IA%p1d%j@rr4)j`!B5'lrr+Kd3UHSf#KI3UG1Z[G-*Pm [#bm-U0`[,IlqU5*1Z[H#)'hrc%KS!"#SSbmYr[kT"#mYr[kTD8kkpiC1Z[T#,bh qrUNM,``[#kMF,`bSf5mZrrbSFdcI'!"1ANje!!!$+!#)!!*19[rm,`G#VIlq1fi !#[lm1fi!#2lk-#hqqX(Yr[`l32li3UF`,Ili5-![!%kY!%)VArld5Uhqp'C5,bd !$%Kk!5K#CdkY!()[,3!--#hqr%M!,`!r2!!"6Ud!BLmY!!`I2!!U2c`!!8kY!&S [,3!--#hqqNM!,`!r2!!"6Ud!BLmY!!a1V3"UB!!!P%+R-#hqr10!5-![!%kY!%) VArl`5Uhqm'Bd,bhqp%kY!#S[,3!-5(S!FN*R6Ud!FLmY!!``,Ilm5-![!$mm!!& 1V3"L,bd!$%kY!'TJ4$!Yr[a63$e!rra#4f!5)'hqm#"3)!IM3%*`!!"54fN'[Ql rr'rS3QhqlM!Yr[L3!'hqqMY!rZa#EIlU'h`!!Ikq3Lhq[biI6PiZRdje3%j[G#" PEQpeCfJJE@9YEh*j)(4[)'&XE'pMBA4P)(4SC5"%C@*eCb"AD@jNEhFRFb"-D@j P6'9Z)%&bFQ&j1L!!28j[G#"PEQpeCfJJE@9YEh*j)(4[)'&XE'pMBA4P)(4SC5" %C@*eCb"AD@jNEhFRFb"-D@jP)%&bFQ&j1L"19[lJ,`FJEJ!83qlrq#,B)YJJEJ! 33qlqq("!)YK63'lk5'lqi+Kd5UhqrQB!!6a#Td(YrL)[#%KZrrK)E[li(bi!$%* RF2m[!"mZ!!j#TkN6+erqrNKYrXJr2!!%2c`!'$!YrejC3$m!-#hrA&P!2`#STdK YrX!r2!!82c`!&$mYrei`,IpFN!"m!"3r!+LR3UHSf#YIrKj)EIl32c`!"8*R2bh qr%kY!++SJ#mYr[kSFcmZ!!USKcmZ!!LSLNKZr[#SLc!Zr[,3E[l`-LlqpY*!1d( qk$YZr[$qjMYZr[6qj%KYrY4`"H0!-Llqp-2Yr[V53$m"F!VM3$)Yr[c$lIlSdN! r!DL!3JGJ2NL()!G"lIlFj8")ji#!3UF[,Ilq)QhqrNKT!"")HJ"53QG#Cd*R2c` !!6mm!""#TkP8)Kp-h`%")B%!!&)($!F!!@qm3Uhqf%kY!))[,Ilq6Ud!8N(Y!*S [#%kY!%S[,[lJU(-Z(djH)&rHr!!36Y!!N2m!N&8"!*!$3J#3!d%!N!6Y!!$-(!! q!*!$(!$L!!4%594-!!)!+N4-6dF!!J"1689193!"!(*66dC8!*!$LN024%8!!`# @!3$rr`#3#!%"rrm!N!0H!*!%m&rrr`!!!KX!N!3"!2rr!!!"6!#3"!%"rrm!!!& P!*!%m&rrr`!!!aN!N!3"!2rr!!!"IJ#3"!%"rrm!!!'6!*!&!3!!%!!$-J!"(#` !!2rr)!!Pl!#3"3(rrc3!*TJ!!4b!!!,rrc!!-hJ!N!8$rrm`!$b%!*!%#P0'68G PG%CTE'9cT`: !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting telnet.Hqx... cat >telnet.Hqx <<'!E!O!F!' (This file must be converted with BinHex 4.0) :"R4PE'jPG!""8&"-9%a19#!!N!H`!)ll!*!%!3#3!ki!N!1Y!*!$!FS!N2-Q*94 &6%j&9#"@CA*cD@pZ)$%Z-L`J-6!J8f9`G'9YBQ9b)$%j1$8!N!-+!*!$!J#3!`' !!*!%"d&38%`!N!BF9%a19!#3!`&*3diM!*!&J%C548B!N!@!!*!$$!"3!'3!r`' N!Ecrr`#3!``!8!"N!2m"T!-*rrm!N!1X!!-!N!93!&S!D3#@"!0C49-!N!C3!2! !D3%X"!*16`#3"38!&!!C!A+)-9P[G5"SBACP)(*PBf9TGQ9N)'%J9%C88#!SCQP XC5"dFQ&ZFfCPFLNJFQ9aG@9cG#i!N!BC!"3!5`&bL$T8D'8JD'pcG#"H-#"hB@j dFb"dEb"H-5"dD'8JCQPXC5!LAM)L,L!J5A-JG'KKG#"KE'`JFQPRD(3r!*!$5!! "!*!&#J!+!"N!m)JC35"1CA4hEh*V)%9bFQpb)%KKF("PEQ9N)3#3"P!!#J#(!2# )$e0PG#"LH5"6CA4*9'9iG!#3"*`!!`#3"3S!-J!S!'i%!Np,!*!&#J#-!#J!b!3 '3d&13d9-!*!&F`!+!))!m*!!%N4PCQ&eE(4'Eh*PD@GZ5'pcG!#3"6)!#J"N!2# )5&"XC@&cC5"dHA"P)'PZ)(4SC5"ZB@eP)'pb)'jeE@*PFL"[CL"dD'8JCQpbC@P REL"SEh0d)(P[G5"hB@jd)(4[)(*PB@0S,J#3!f!!!J#3"B`!EJ#P!0)%"&&9593 !N!8+!&S!0`%XL#j")'CKG'&X)'9bFQpb)'KKF("PEQ9N)5!J9'KP)("bEfGbB@d JGfPXE#"ND@8Z!*!&8!!8!)F",)J#AM!!N!1d!!3!N!@-!'i!T3$5"!4)38a8!*! &#J"D!$F",)JT4Q&dB@`J4A*bEh)K)%%JG'&cDb"[GQ9bCQa[Gf9N)'PdFb"cG'& MDb%!N!C3!"3!A`%XL"C6G'&MDb"3EfPZG'9b)'&LEh9d)&i`!*!&C!!8!(-",)J B9'&cDb"MEfjdFQpX)'*XEf0V)'&d)&ia!*!&H!!8!)F",)J29'&cDb"ZB@eP)'P c)&ib!*!$!K)!&3#3"3m!Y!!H!3Q)"A"bC@0f!*!')`#d!$)"#BJ%F(0ZG!#3"6F !Y!"'!3Q)"A"NFQp`!*!'5`#d!&S"#BJ&BQ4MD'X!N!CI!,3!EJ%*L!CeER"bEh3 !N!9c!,3!JJ%*L!CLC(CPFR-!N!@(!,3!PJ%*L!9LC'aPEJ#3"TX!Y!#U!3Q)"R4 dE'9iF!#3"Dm!Y!#q!3Q)"@CbB@Gc!*!'``#d!0)"#BJ%CR*PC3#3"GF!Y!$Q!3Q )"'CKD@`!N!82!!S!(J#UL"&3B@0VCA4c)&*PBf9TGQ9N1J#3"L-!#J!b!+U)$9" KBfYPG(-J8f9ZG$S!N!Bh!!S!4J#UL""3B@0VCA4c)%4bEh"`C@3k!*!&5`!+!&S !USJ13Q&N)%0SC@0VFh9YFcS!N!9I!!S!EJ#UL"49EQKKEQ4XC@3J8(*[G'pMEfa c1J#3"A-!#J##!+U)$8*KC#"@CA*cD@pZFcS!N!D(!!S!PJ#UL!a#B@3J6'9ZCh4 SFcS!N!@E!!S!UJ#UL!a89%`J4AK`DA*PC$S!N!@[!!S![J#UL!T'FQ&RE@9ZG(- k!*!&``!+!0)!USJ04R*PC5"3B@0VCA4c1J#3"YF!#J$Q!+U)%%aTFh4PEL"'B@P XGA*PFcS!N!2`!!i!N!8+!!S!'3"'L!9-Ef0KE!#3"JS!6J!C!)U)"dC[FQ9TCfi !N!B+!*)!'3$1L!4)Eh0d!*!&#J$@!"N"&)J(5'&ZC'aPFJ#3"JS"(!!C!9L)!N0 Z!*!&(J!+!#d!4SJ!N!BH!%i!,3#+L!#3"Ki!NJ!Y!-k)!*!'(J$@!#d"&)J!N!B H!4`!,3&BL!#3"M)!#J""!%D)!*!'-J"1!%%!LSJ!N!Bb!*)!33$1L!#3"M)!eJ" "!45)!*!'-J%F!%%"@)J!N!3V!&S!-J$)!EJ!!3%!N!J0&80[EQCTFQdJ4QPXC5" 8FQ&ZFfCPFL!!N!-I!'3!I3$e!AF!!`#3#6-*4A*bEh)J3Qpi)!#3!bF!C!"p!28 "G`!"!3#3#$i46Q&YC5"'Eh*PD@GZ)%K[Fh3J!*!$)`!b!'3"*`&h!!3"!*!)6Je *8#"6G'&dDA0dD@0c)!#3!b-!C!",!2S"U3!%!3#3#&i19843)&0dBA4TFh4TBh- !N!0T!3#3#Iq3"!46C@jN#Na[Bf&X)%9MD'm!N!33)N&bC5"CEh8J9'KPFQ8r)J# 3"!iL3@*[FR3J6h9dF(9d)J#3"!FL3R*PB@XL!*!%$89iF'9NDA4P)%4KG'%!N!K S!3%!N!MrN!2I#%0[E@eKEQ4c"dp`C@iZN!-!N!3&3fa[Ff8!N!338f9ZC#"*E@e PC'PKG'9XH3#3"!a84P43)&0PFRCTBf8!N!3'6'PcG'9Z!*!%"&&eDA3!N!L&!3) !N!MrN!20"&0SEhF19%03)&0dBA4TFh4TBh-!N!366ANJ5@jdCA*ZCA3J3@4NFQ9 cF`#3""40H5""F("XCA4KE'XJ3@4NFQ9cF`#3""C"Bh4TGQ8J9843)%0[EQjPBh4 TEfjc!*!%$8P3)&0dBA4TFh4TBh-!N!F"!*!$2rjrq#!#3!JMiNr)*"*3+#R+8#J U+P!S+UT3+#SU6mJTbN!2j"*"k#2L3!rJ!Rri)!)J%#Gb)&!P8Mr`*h)!3#!#!-! J!J#!,rS"J#UUrrmUUS!",rUUU5J+J!%S#T99,rU!!5!#rrmL)J!!*h)!!#)L!!! J!J!!2ri!N!BrrRri2rjrrrrqIrrrrRrrrrjrrrrqIrrrrRrrrrjrrrrqIrrrrRr rrrjrrrrqIrrrrMrrrrirrrrq2rrrrKrrrriIrrrq(rrrrKrrrrlrN!2qrj!$r[q 3!rlrN!2qrj!$r[q3!rlrrcrq!!!rrJ!!2ri!!$rq!!!rrJ#3"J1J!!!$X!!!%Li !!!13!!#3!b!$9$mm!!'Tm"ak2c`!!DR`""!r2!!"UI"9QMmm!!'Tm&@D2c`!!DR `9CSr2!!"UI!4hMmm!!'Tm")@2c`!!DR`&9!r2!!"UI"9U$mm!!'Tm!Sd2c`!!DR `"*3r2!!"UI!$p$mm!!'Tm!5%2c`!!DR`"$`r2!!"UI!0#$mm!!'Tm!bD2c`!!DR `%pir2!!"UI!5cMmm!!'Tm"1#2c`!!DR`"DJr2!!"UI!&EMmm!!'Tm!A%2c`!!DR `%bBr2!!"UI!IqMmm!!'Tm!B32c`!!DR`#T3r2!!"UI!NbMmm!!'Tm#!F2c`!!DR `)LBr2!!"UI!T&Mmm!!'Tm#9D2c`!!DR`*6Jr2!!"UI!Q*Mmm!!'Tm#8N2c`!!DR `*P)r2!!"UI!QBMmm!!'Tm#E`2c`!!DR`(T)r2!!"UI!I@Mmm!!'Tm!1d2c`!!DR `"!Sr2!!"UI!+'$mm!!'Tm!2q2c`!!DR`!qJr2!!"UI!5B$mm!!'Tm!5k2c`!!DR `"3)r2!!"UI!&&$mm!!'Tm!6`2c`!!DR`"8Sr2!!"UI!&*Mmm!!'Tm!5S2c`!!DR `"-`r2!!"UI!&1$mm!!'Tm!9F2c`!!DR`"0ir2!!"UI!$UMmm!!'Tm!CZ2c`!!DR `"V!r2!!"UI!'6$mm!!'Tm"Kb2c`!!DR`9D)r2!!"UI!@GMmm!!'Tm"E`2c`!!DR `&E)r2!!"UI!9LMmm!!'Tm"@H2c`!!DR`'B!r2!!"UI!C6$mm!!'Tm"RD2c`!!DR `'5)r2!!"UI!BSMmm!!'Tm!Kd2c`!!DR`#*Sr2!!"UI!),Mmm!!'Tm!Pi2c`!!DR `#Dir2!!"UI!(%Mmm!!'Tm!P-2c`!!DR`&%!r2!!"UI!aEMmm!!'Tm$jf2c`!!DR `1m!r2!!"UI!8($mm!!'Tm!#Q2c`!!UR`!#`r2!!$UI!",Mmm!!1Tm!&U2c`!!kR `!"Jr2!!$UI!!AMmm!!1Tm!!!2c`!!kR`!(!r2!!$UI!"YMmm!!1Tm!-d2c`!!kR `!I3r2!!$UI!$FMmm!!5Tm!%q2c`!"+R`!!!r2!!%UI!#6Mmm!!5Tm!Ib2c`!"DR `"qBr2!!&UI!(f$mm!!@Tm"-Q2c`!"UR`%U3r2!!'UI!6"Mmm!!DTm"%!2c`!"UR `%fSr2!!'UI!3FMmm!!DTm"*m2c`!"UR`&8`r2!!'UI!9BMmm!!DTm"8!2c`!"UR `&'Jr2!!'UI!!!&A3!*!$98j@rr4#CbmZ!!LSDMYIrp4#CbmZ!!LSDcYIrpB`,Ir 88d"R%J4!!2pR1P0!CbC63'G#B!!"&LmYrr!r,Ir@5'hq`UP'3QG)EIl#UEBlArr HB!!!q$mYrpB[,Ird6VT-UQ!!!1Jr,Ir@,bhrq%kk8"aJ!!$B-#hreP9!C`K63'F XB!!!b%+R3UG1ZLA)5'h`(Nkk+Ca#TdKk!1C1ZP8'3Hh`(Lm)6VS,T'!!!+"#CdK Zrr4)E[rf6VS*h%TICJ!!M%(Ym"j$qJ#Z)0NJf6#4-#lrpNM!,`")EIl#6VS&r%K YrX*)EI!H%#h`(J*!!2p53$m!6VS'jNKk!(*)EI!H%#h`(J*!!2p53$m!6VS'cM! Zrr4)`#m!5'hq`Nkk"Ea)EIl#5'h`(K!Ym"i#3!$r8N!r!%kk"UC#TdKk!"T1ZP4 N3Hh`(Lm)6VS,!N*RU6K1ALkI6R896ANJ3A"`E'9dB@aV)'&NC(*PFh-k##`J6Qp NC6SJ!!P1CA4hEh*V1L!86ANJ5@jdCA*ZCA3JB@4NFQ9cFcS!6PErpLm(UE4#Ccm mrrp)EIrJUA!3(fF!!Ba#CdKYrq#TIa!ICdj#CdKYrq")E[rm5'lrqUQ!%"m+!!! "CcB-E3!"rq"Q%N*R5'hri%kk#DJ3(`S!!!&J'!aY!!Mri'B33QG)EIrJ6VS*hK! I#J!!!@!!!8!`,IrJ8d"R(&9!C`!!Z&9!C`!!XP0!C`!"#&9!C`!!lQ!!!4a#Cbm YrqT)EIrBU5`lArrF-#hrh&0!D`!!KJa!!!9ZIZ0)-$X!"Nll!*!$$J!X!%J!1!" )!(B[,Ird6VT-dLmYrrK1ZNpN3UF[,IrUU6e1Z[f%B%T)EIrJ,bhrf+QcB$i[,Ir B,bhrkNKYrm5T*@!Z)#hrf,#Yr'4Q%MmYrp`[,IrU2bhrlNkY!jTJIN+RU53J,Ir BX*pR"LmYrpLT(f"k3UHT*#!Yr'5`RfBk3Hhri#m)5'hq`NkY!hS3,Il#!N!!rce !rrCq!@!@3Hhq`K!`F!!#3!$r2`"1ZN+@8NGT"VjZrrC[j'!b)#hriV#Yr'4Q#$m Yrqj1V315B"iJ,IrLX+hmC'B%6Ud$UQ!15Qhri'B)6VS5ANkY!k)3,Ik4C`$q6Li I6Pj1G8kk!j419J!!,&p193!!Rqd!%%kk!jC1V3,+3Hd#bLm)UI&1Z[iF6VSQR%k k&[*1ZJ1'6Pe1ZJ0b6R91ANje)PmJ(k"-,S"1FA!!,`Na`!)J6R9`!'$f)PmJAk! Y6[Vrl%Si!SjU+L"i!6"$q!%8)!L3!*&d$,##C4BL85()!43LJ%)4)RJ#UL+))), 4U3!-6R@JBdje)PmJ(k%H,SK1q[qX)PmJAk!I6[VrSL*I)"qK)Lk)6[VrPU!f6[V rNL)I)"mLAb"IS#iL38lkri!LAb"I,`QJ-cp!!!41G5*I)&m[#D!d2d!!"%je6PB !!#"Z!"!LEJ!-F!!3'%K!%"P+,J!)C`j+,J!+C`5N2'!3S$aJ$%SZ!!TR"+BmB!+ L2!S!!!%G3!!86PiJAprm!*!$$%l3)Qm!"#"[!!LTEb"I8%p1d#*[!!3JE`!)U@i r3!!-)&p36dl3)Pm3(b"ICJ5J!'!#T!!qJ%l4)Pm3(b"ICJ5J!@!#T!%qJ%l4)Pm 3(b"ICJ5J!Q!#T!)qJ%l4)Pm3(b"ICJ5J!f!#T!-qJ%l4)Pm3(b"ICJ5J%f!#T"- qJ%l4)Pm3(b"ICJ5J#'!#T!JqJ%l4)Pm3(b"ICJ5J#@!#T!NqJ%l4)Pm3(b"ICJ5 J#Q!#T!SqJ%l4)Pm3(b"ICJ5J$@!#T!dqJ%l4)Pm3(b"ICJ5J%@!#T"%qJ%l4)Pm 3(b"ICJ5J4'!#T%3qJ%l46PErcN(ZrmiKEJ!1!")aEJ!-!"C#+!!D3LJ!'d+S!"b J!#*Z!!JbU!!B28!!%NjH)&rIr!#3!`T1d%j@rmj"l[r1-@i!#!!BS!%p3!!+6Pi JAe526Y"4`@!#8-&19[r13HlrcL&Z!!J!)$&Z!"!!'#*Z!!`K83!N3QJ!,%+S!#j +!@B%S!*J!U!$28!!%L*Z!!`LU!!S6PiLAprm!*!$#Nl46PErX%(Zrl!KEJ!1!") aEJ!-!"C#+!!D3QJ!(+!-28!!%N(S!#!LEJ!)-$`!%+!Z6PiLAprm!*!$#Nl46PE r`%(Zrm!KEJ!+!")aEJ!)!"DJ&6e!!!j1AL*IA)p1d8j@rl""l[q`)@i!$J!5-@i !$!!@3LJ!'N*S!"bJ$%2S!#!JEJ!)-$`!%+!Z3HlrX+!028!!%NjH)PrIr!#3!`T 1d8j@rmj"l[r1-@i!$J!B-@i!$!!X)@i!#!!ZS%3p3!!36PiLAe#26Y%JE`!%)#m !#%*RUHiJAe"26Y"#V`!33IS!###[!!41G3#3"%je)&p1A5m)6VS#h%je)&p193! !6Y"19J!!,&p)jq$J-#m!(#"[!"j$l`!L0!$P5Y,#3N&5L'!3*'K3DdN*J!K$ D8FVrr&()rqiJE`!H%)&$l`!L-#m!(19)dX!M,`!B,dN!'%cI"`FZAdje6Pj1G59 I3d&8)*!$6PB!!#aI51IJ`#"[!"J`,`!FEd!-3!$rEMSb,`!HEc3-33$rELj635* [!#"#3K3CP%'83'dHdX%3`'!#%0P4b2rm,fm!&!!J60m$"prm!*!$$%je3K"JkNj H6R8PAd028&NJ)%j@!!!XAb)I-"mLAb"I,`&)ja!J8d"Y+N*"%KK#3K34Y%"Y(MB "eN)5`b4*e-25`T4!B!)9)9(+rraJ!K,B8FRrr%cI"!K1G8jH6R8PAdP18b#3!dj @!!!XAbm!,`%`,`!3`Hm!$M)[!!c$l`!5d%&)3%*!-Lm!%X,[!!l3J5p!!"!L(b! I,eF!"&L26R91ANje*8PI699-0#"19J!!,`![!5![!"3L,`!36VS!1Lp"!"3L(b! I6Pi[9`!%@)p1G8j@!!![!#m")#m!&#)[!""1ZJ!8,d!!&#)I)"p1ALpA!!4BMdj e6PB!!#aI51Fq!#S!DJ*%J#`"DJ*%J53"5%*+3QBF0J"#3%K!C`5!`63!5%)`!i$ "0!!L!N*!5%"J(#3!*J&#J%+"H"r8JY'!dS'`Jfd%N!#$8J&4c2r`5S9U!N5!ZiC U!N5"60m!I%je6Pj1G8P%59C06d3J6PB!!#aI)(Vp[%l36Pj1G59I5%&-9#!J6PB !!#m!F!!3,J!+X'i!#'m#qYiJ(djH,Tp1G90dFP*R3fKV51I!`()"B!C)jm$!3N% JE`!B)Qm!&%*!%"L`'@B1B!5c#'B)8d"Uq!T"!!%I33!D,fm!%!!@60m$!eb26R@ Hr!%!51I!`$![!43b,`%@3Hm"'#*[!4#53'`)3Q"83@[kB!,3`5%*,dJ!%%cI!`- ZAdje6PB!!%+YrUj#,IkS3LhqU8+YrU*#VIkH3UhqNNjH6R@P58j*9%K&33!!F!" J!!#+F!&J!!#%F!*JIR!$B(T`"'"fF!9JFR!'B'j`"f"UF!KJCR!*B'*`#Q"HF!Y J@R!-B&C`$@"5F!jJ6R!2B%T`%'"'F"&J3R!5B$j`%f!kF"4J0R!9B$*`&Q!ZF"G J+R!BB#C`'@!LF"TJ(R!EB"T`('!@F"eJ%R!HB!j`(f!+F#"J"R!KB!*`)Km!-(J !i,$i!UjN!!!1)(J!i#"3)&"+N!"Q*&92,caKG("XUC``(fFL@8m[2'&dF'`r!+Q G)"pR$#"!(c`!)bm33KG1G6!i#Q"Q"$!mrd"d!()!&"mLAd(k!"S5-#!!hX%-3J! LC`S-3J!"EJ*#3$k!6Y%!N!3'!JB'"!J##!J%!!!)!JD3"33'N!31"J3!!!J!!%j @rrT)j`%)+'h`&L!-9X"R%L)X!!DbVJ!)9X(!!@F%+&4Jk#!-Cb4#Cbm-3Hh`&#m )6VVj-$iI,bi!#+Q$,`a1Z[L#(A`!!3!-B!4#,J!-60m3J%jH,Tp1G8j@rqSJEJ! )3qlrm#,B)YJLf#,B3QF[,[rk5'lrl+NX29rrkL!Zrqb`VI%HCJi[,I%HU4BGI!! "!!aJ$N*R,blrl%kkrf)GA`!-6PiZRdje6PErh#m-)'i!#%2Zrr!Lf#,B)YJLf#K Zrr+jlI%HCJJGI!!"!!aJ,%+R-#lrrNM!,`"`!5m!U&J-R`#3!`&Q#"em!!%!$'! -3QF[$%kkr`JGA`!-+&p1ALkI6R919J!!,bha(UN9,bha(UNI6Pj1G8j@rr)[,I% H2c`!!8KZrrj)E[rk5'lrmUQ0,blrqLmZ!!bTMbmYm4ir2!!#5'lrrNKZrrT)E[r bUBd[,[rk,bi!#+Q26VVrTNjH)&p36dl36PErmLmYm4ir2!!"5'lrrNKZrrT)E[r bUBd[,[rk5(S!0+Q2,bha(Mmm!!*)E[rq5'lrqNKZrr+TM5mZrrS[,J!)UBmr2!! #UFK1Z[p36PiZRdje'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bha(Mm m!!&)E[rq5'lrqNKZrr+TM5mZrrT)HJ"+UBm[,I%H2c`!!NKZrrj)E[rk5'lrmUQ 0,blrqLmZ!!`[,J!)5'lqmMmm!!*1Z[Qk5'lqmUQ22c`!!UR)6VVqcNjH)&p36dl 3'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bha(Mmm!!&)E[rq5'lrqNK Zrr+TM5mZrrT)HJ"3UBm[,I%H2c`!!NKZrrj)E[rk5'lrmUQ0,blrqLmZ!"![,J! -,bi!#%KZr[)r2!!$6VVj-NKZr[+TMcmm!!+Tb%kkrNC1AL"Ih[`!$%l3'8%JEQ9 dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[m!-#i!#!4!rm0V!!(3$%!!('i!!FMM5$! l!!C1q`!!!CS"[J$k!+B"[J'q!Ei"[J'q!Ei"[J'q!8`!8J#k!BB!d!'X!13"FJ% N!@!!NJ'q!$`"%!%i!(`!D%(Zr`"$qJ0-F!BJf90!E[S`N@!!!@j"l[m!3rS$((! ')0P63'lk-*&J!!&B3Hlr!%2k!Zj`"L$C8d"ZqQ!!!84"l[m!3rS#b(!%)0P63'l k-*&J!!%Z3Hlr!%2k!U3Jf5$C)0N`N@!!!4T"l[m!3rS#G(!()0P63'lkB!!""N( Zr`"$qJ*1F!3Jf90!E[S`N@!!!2""l[m!3rS#*(!&)0P63'lkB!!!h%(Zr`"$qJ( fF!BJf90!E[S`N@!!!-C"l[m!3rS"aR!')0P63'lk-*&J!!#`3Hlr!%2k!D!Jf5$ C)0NJf@!!!*a"l[m!3rS"I#$C)0NJf5$CB!!!L%(Zr`"$qJ&1F!BJf90!E[S`N@" b3Hlr!%2k!44`#5$C8d"ZqM#4B&j"l[m!3rS!h(!*)0P63'lkB%a"l[m!3rS!V(! ()0P63'lk-*&J1%(Zr`"$qJ##F!8Jf90!E[S`N@!N3Hlr!%2k!%T`#5$C8d"ZqQ! 53Hlr!%2k!#*`"5$C8d"ZqM#4,bi!#N(Zr`![#%kkr3K1AL"IA%p1d"9)BA*NGf& bC5"fEfaeE@8JE'pMDbiL8'9bE@PcFfP[EL"NEf9c)'j[G#"KE'a[Gb"hFQPdD@j R,J!98fpQG(GKFQ8JGQpXG@eP)'a[BfXZ(&4[Eb"YB@jj)'CTE'9c)'&bC5"[F'9 Z)'j[Gbi!)e4SC5"RDACPEL"QD@aP)("[FfPdD@pZ)'Pc)'PZGQ&XD@3Z*94SC5" QD@aP)'Pc)'&XFQ9KC(NJEh"PEL"QEh)JGh*TG'PZCbiB9'KPFQ8JDA-JEQmJFh9 MD#"fEfaeE@8Z!!p0C@e[FRNJDA-JCR9XE#i24'PcDb"*,dmJCA*bEh)Z'84TCQC TBh9XG(NJGfPdD#"bC@jKE@PZCbiB9'KP)'CTE'8JC'pPFb"ZEh3JCAKTFh3Z!"0 8D'8JCQPXC5"TFb"XEf0VC@3Z%94SC5"QD@aP)'Pc)'*eFhNZ'd9iG'9bEQ&X)'C TE'8JFhPcG'9Y)'9bFQpb,Ja&EQ3JEfBJCQPXC5i!%94SC5"NDA0V)'Pc)'CeE'` Z&P4SC5"NDA*PBh4[FRNJDA-JCR9XE#i!'&4SC5"QD@aP)'&XFQ9KC(NJCAKTFh4 c,J!C9'KP)'CTE'8JEQ&YC5"TFb"TERCKE'PN,Nj@!!![,J!-3UG)HJ!@6VT$Y#m Z!!K1Z[Z@6PiJAe"26Y!81L"*)'0KELGd)'GPG#"K)'jPGb!!6PB!!#mZ!!a#TdK k!"C1ZN0m,bi!#%kkqej1AL"I8%p1d#Bk)%NJBf&Z*h3JEh"PEL"K)'0[EQjPBh4 TEfiX)("bEh4[BfpX)!"19J!!,bi!#%+R5(S!1Nkk3c*#TdKk!""1ZN-S6VVl$Nj H,Tp1G4j8D'8JEh"PFQ&dD@pZ)(GTE'`JBQ8JB@*[FR4PC#i!*cSJ9'KP)'C[FQ9 TCfiJD'pcG#"TFb"ZEh3JFQ9cF'pZC'PZCbiJ)%j@r`")HJ"#,bi!#NKk!$4)E[m !2c`!!dkkp#T"lI!H3qlr!("!)0P63'lk3Hh`(Lm)2bi!#%kkqa"1AL"IA%p1d!3 L1b!J!!j*)'0KELGd)'p`C@iJ)J"19[m!5(S!3LmZ!!T)HJ!d5'lr!$mm!!01Z[2 53Hh`(N2Zr`"`3#$C8d"ZqN(Ym"i[#$mZ!!K1Z[Ui6PiJAea26Y!%)MXJ)!!555" MB@iRG#"hFQPdC5"dEb!L!%j@r`")HJ"#,bi!#NKk!$4)E[m!2c`!!dkkmhC"lI! H3qlr!("!)0P63'lk3Hh`(Lm)2bi!#%kkqPa1AL"IA%p1d!3L1b!J!"0*)'0KELG d)(*PB@3JCR*[E5!L6PErrLmZ!!T)HJ!b5(S!,NKk!#UTLd*R2c`"68+RUBBpArr q%#i!#'F'6VT"Z'!%6VVe*%jH)&pF6dl3!!"19[rq,`Gq!@!1)!G"lI,Uk8"#-!! 18NF-4`!3Eq`Z(djH6R919[rd,`FJEJ!)3qlrpL,B)YJbN!"q!3a(!""I`'i5)JG "lI,Uk8(!-"!1C`454f$Q$%F!%'ib)!G"lI,Uk8!KVJ!-!!!J"d(YmZVT3%(`!!4 $l[rf)0NJf6#4)!G"lI,Uk8!4[!!"!!iZ(djH)&p36dl36PErrLm(IJ&J1L!(3Hh bkZP!%M!!$QFU)!G"lI,Uk8!L-!!!XUi!#'BB3UFJ"d(YmZVT3%K`!!41ZN#f,9m !$'!-8NF-4`!3Em"#VJ!-,Kp1ALkI6R919[rq,`Gq!@!`)!G"lI,Uk8!5-!!1Cb! J"d(YmZVT3#)`!!#bVJ!)CJiJ"d(YmZVT3%)`!!jJ#&*($%F!%'r+,Kp1ALkI6R9 19J!!3Hhbh,(YmraR+%+R6VS$1#!IX+hcr&r!)'hcr#)YmrbbU!!+9X'!!@F),bh cr%kk!M*1ANje6PB!!#"Z!!J4I!!"!!j1ALkI6R919J!!,bhcr%kkrq*1ZJ$+6Pj 1G8j@rqK)j`-B)'i!$%2ZrrBLf#,B-T!!-#i!%0"m!"!q!#!(!N!!!@F#8NFQEI, X3SDCc#!,9X!L$&I"`!&R+VjV!!KZ(#K,2LX!#%U'CJJVD`!%mZaJ%#"')@X!"!! %B!BX#bCV!!4JbL!-CJT#Tcm(6VS#C#KI,`a)abm(,bi!%LmZ!!K1ZJ(d'A`!!3! 1)'i!&LPS!!3!"#"Z!"BK6!!%,`a)E[rf6VVpjMP(!!JT6!!+,8`!'NcI'-"1AL" Ih[`!%Nl36PB!!%kkrY3VEI2mm[!JEI,`+fJ!"2,`)'hbm#!Ym[#`U!!+C`J[,I2 m6VS"&#"Ym["++!!1CpBJEI,`3LJ!$L!Ym[#`VI2mC``[,I2m,bhbm%kk!6`VEI, `mr`3,I,jCa4#,I,j)'hbp#&YmZ`!"#YYm[6bl%jH6R919[rm,``SEI2m)#`!",# YmraR"LKX!!4Jm#"Ymr`TD!!%!!3EI!!"m[NVEI2mm[3[,I2m6VVpiNkkrda#TdK k!"*1ZMjZ3QG1Z[bN+&p1ANje)%4TFf&cG'9b1L"dDepPH'Pd+#NJFQ9dGA*ZD@j R)C!$!%j@r`"#TdKk!%4#TbmYmra1Z[dm5(S!)%KZr`!r2!!$6VV[M%KZr`"1ZMi 83QG1Z[a+6Pj1G43JDA-JG(*jD@jR)(4[)(*PG(9bEJ!&9'&cDb"19[hq3UG1ZJ$ F5'lprNkkla)[,I2m5'lqrNkkl`C)E[hq5'lqrN+R,bi!#%kkr-j)HJ!FUBY#Ccm m!TT#TkQ'29rrrNkk2FC1ALkI6R8!!%(k!#!JRb*251FI2L"T!!3JMb"4,P"-hhc i8)mLHJ!%6Y%!N!4"qJ"!))iJE`!3dHm!$#%[!!4$q[mN)3NK,`!))3NX5*(m!*! $&#%*)3P)i"mq)Qm!%#+))&rIr!#3!a!XHJ!%6Y!!N!3J$b"I3S%b(j!!J82k!!B LJ%l3!*!%)&p#J$!I3rVrp*'4,VVrlNl3)&mL6bk*6Y"19[rq51F"##KZ!!K#Cbm -6VVVHMiI3T3jEJ!@!!T#E!!-)!ab%Y#"+8!!"MPm,`d!%MPm3IJ!&$Pm#33!&MP m+P!!'$Pm,c`!'LPZ!!`!($Pm6VN!)#PZ!"!!)MPm+Pm!*MPm6R8!+%*R,`a1ZZX 32Kp-ha#!6PiJAplm!""1d%j@!!![,J!8F$`[!%kkl[i[,J!3,bi!$#mZ!!K1Z[p L6PiJAplm!""1d%j@!!!JEJ!)5QJ!#QB'3Li!$'!D3QF[,J!)6VVUaNTIC`C#,J! -B!BGI!!"!!a1ALkI6R919[rf51F$##KYrVJJ$'F53QF[$%(YrVB[#%kkk[Jm(f! @ILT#Tbm(6VVU2LKI)!aQ"N+Z!!KJ'N+81A`!!3!%3Q`!#LPYrV)!$LY-rV)Y6!! )60m3`%jH6R919[rf51F('#CZ!!K#Cbm,6VVU3NTICK"#Cbm,6VVU+$SI3Li!$'" QYqhqXQB)+fX!$[kbB#JSEIkbIJ&+V!!19X!L"m)!CaDhl!!1CJSTD`!1!!j#"f! %+'`!$Q$H3NBSEIki)!aR"P*'+&4JpJa'!"jX$Lm,3HhqYLm)6VVU,'!',`Y1ZZQ 8(A`!!3!-60mBi%jH,Tp1G8j@rrT)j`%)+'hqXL!-Ca"#Cbm-6VVTTMiI+'`!$Q$ X3UhqXNcI%)"1ANje6PErl#m-3UFr2!!I3UG`rbm!UA`SAbm-2c`!!dKZrrK)E[r d5'lrl+Q0,blrp%KYmG5TMbm-2c`!!d*R2c`qJ+Pq3UG)E[rqUC%-EJ!"rrjQ#Lm Zrr3[,J!)UC!!,`bTJ`aZ!!(rrPI!4!!G3!!-+&p1ALkI6R919[rm,`F3,Hle5)! p32rm3NGJ+L!(3HhZpX(m!!SL-!!%XUi!$'B5)!G"lHlf`I`!#L'Z!!J!!'"H8NG T"VjZrra[d"!Yl[4)J%(Yl[E"r!!+)Di!$!!%%#hZp%L!3HhZpX(m!!SKVJ!)!!! 3,Hld5)"53"Y!l[3-,3!+l[4["%)Yl[3-,3!+l[9R$"!Yl[9)J&*!'d$Zp5iI6Pi JAe"26Y"19[rk51F"##KYlf3i[!!$1A`)!!!#F!3C3!!%F!3C3!!&1@i!%!!'3QG )E[rk5'lrr%kklUT+AfF#B(SEE[rllfilE[rmlfa`5"Y!lfmTEHpX!!JTEI,8!!` TEJ!-!"!TEJ!)!"3JEI'Q)&!-D!!"!!*Q"NkkqA*Jl#"YmDBS8$Pm!"F!#"!Ylfm #3!$r18!!#LPZ!!`!$$Pm!"J!%#PYlf3!&%*R,bhaTN*R(c`!!8kklG3q(dcI%)" 1AL"Ih[`!#Nl36PB!!#m-6VS#c%kkqI!SEHpd$&3!!fF'8QhZjQ$S$'`)!!!#C`C 5EHlQB0SJ,!!8X+hbe'F'8QhZl'$+,b`!$#mX!!K1Z[j8-#`!"P0!C`C63'FBB$" 5EHl`2c`!!LmX!!J[,!!-6VVqe'!H5Uh[F'F+,bh[F%kkq*KJ"&*YlZK#VHp`B!4 5EHlUB!$rHLKI6PiZRdje6PErr#m(%#hZp8L!28$rr%*(B$)J"d(Yl[E"r!!+)M! !",+Z!!aQ'L!(3HhZpX(m!!SLEJ!))V!!!"em!!%!%'!18NGT"VjZrra[b%)Z!"! Z(djH)&p36dl36PB!!%UZ!!K@`'F@)Li!#,+Ymra@`F!"C`J[,J!)6VVi!%jH,Tp 1G8j@rrT)j`-),#i!$%*R,`B[,J!)6VVrD"!IC`4J!!$@+fhcr1p`2c`!!5mYlfJ ["NkkrI*#TdkkqlBSAb!-CL)JEJ!)3P!JEJ!)F!!43!!$)'i!#(!!%8!!!N+Ylh" J!!#8,c`!!!1%3IVrELm),bh[F#m-6VVkQNkkq'T+VHp`CNC#Cbm-6VVl-KiI3QF ["LmZ!!K1Z[lS%"pR$N*R,`a1Z[ZQ(KpJ6'"),c`!!!1%3IVr*Lm),bh[F#m-6VV k8LYYmrc[F'!U3QF[$%kkqhSH(b"Z!!K#8#"Z!!K`!"&!!!-JEJ!)F!!43!!#3Uh [F'!#B)4-ha$!6PiJAe"26Y!-!!")C`4J!!#Q4qS!!3`V!!%!!PD,CJB3+`!%B!3 3+`!-$!!!&QC54rS!c%U6CK*#3dkX!!*1ZJ$f3IS!Z&*36R8UHJ#f1d%!$MBm!PJ QHJ#Q6U`!!N(k!*j#N!""qJ#F,a"1ZJ$+4ZS!(#"I3rJ"DL&4!!T1ZJ#N6R8-!!! AC`*J,%Ik!%*+%fB#B#)f2!!B4rS!i%kX!!*+JfF#6R9"qJ!Q3K"'kJ!F6VS!C%j e3N01V!!#3IS!2P*36R9"qJ!)%,`!!8je!!""qJ!i)+m!%%(k!$JJV`!-3IS!*## [!!K"qJ!N)+m!"%(k!!T#8%kk!$j1G3#3'N(krrS[%%kkpH*1G82krq)L8DP[3IV rhLm36VVecNje,`a"q[r8)P"+U3!#Ca`JD3!#+%LTENT!CK""q[q`))a"q[qQ)+` !"Q!-3IVrS%+3!%(krjC#N!!SAdje3IS!##p)!!41G3#3'%j@rpa)j`mB*Qi!$N+ R,`Y1ZJ6f,"p#Tbm'6VS&!#KI1#`!!N*X!!)3,J!0!N!!!@F3,8crhL"Zrpi`,J! -3M!!!%+R3QF[$%+!-Li!$&*"-!(LL$m!6VSe0$!I5-![!+KD)"mp32rZZ'lrlQF 1183!!Lm,6VS&P'!!!B)j4!!#%"3#3!$r9d"R%&9!C`!!P&&!C`!"#'!!!9j#Td+ R,`Y1ZJ4Q6VS%GLeIrr"#4f!Z)!G"lHq8jd!LE[r`)M!!!,+T!"KQ&L!(3Hh[P1G !3V!!"#m,6VS&0'!!!5*54`a(!!p[c#"Zrr!`,Hq53qh[P1G!)kJ!'!!!-#h[NN( Ylj6R3%+`!!3-E3!2lj*Q"N*Ylj*J"&*Ylj)[#dkk"1jJ!!$F+Ja#4f!`)!G"lHq 8jd!L45)`!!#bU3!BCKSJ45!(3qh[P1G!)kJ!"!!%,`Y1ZJ5kB!!!U&*($%F!$fr +)%8`,Hq53qh[P1G!)kJ!'!!!)%8`,Hq53qh[P1G!)kJ!"!!%$'d!$qq5CJC#EHq 5B!45EHq5,`Y1ZJ4bB'"`$KL!3Q`!!N+R3QF[$%+!-$`!&1+)2`"1ZM2@-"p)`#m !U&SJ(cP!!!)J4L*')fJ!%!!-)%BKEJ!)!""#CbmYlii[#cmm!"3[,J!)6VS%kNT I,`Y1ZJ3BB!B[#dkk"""-haM`6PiJAplm!!T1d%j@rqj)j`FB3Qi!%N+R2c`#!%* R6VS$(LCI)!YQ)%+R5(S!Z%kk-eC#TdKk!+C1ZM0-6VV[K%*Z!"*J!!#)3UG#Tbm ,6VS#Z%kk!XJSAh!$')!CEJ!*!!f!H,#i!#L!-8)!U!#"&)!FL4K)aF!!#33$ r%B%!!&*($%F!'frF3Q`!!N+R3QF[$%+!-$`!*1+)2`"1ZM,X-"p)`#m!U&SJ(cP !!!*#CbmYlii[#cmm!#3[,J!16VS%%MiI5NF[#dkk!cj-haMJ6PiJAplm!!T1d!C `B@0VCA3!#dP$69"I4%969&916PErq#eZ!!crq#eZ!!Mrr%+R%#lrq!*!!2p)`#m !,c`!N!1!U&K+RfBd%#lrq!*!!2m5,[rm!N%!rl*!9m!5,[rj!N%!ra3Zrrd#3J$ rY%&A`F!"4!!G3!!3B!!!dN+R%#lrq!*!!2p)`#m!,c`!N!2!U&J-R`#3!i"Q5"! ZrrJ#3!$r%Llrr!*"!2qb3&I!%Llrq3*"!2m8,[rp!N)!rl4"9m(!!4)ZrrS#33$ r&#lrrJ*#!2qd39I"`!&%!"e!!""JDN+R%#lrq!*!!2p)`#m!,c`!N!2JU&J-R`# 3!m"Q5"!ZrrJ#3!$r%Llrr!*"!2qb3&I!%Llrq3*"!2m8,[rp!N)!rl4"9m(!!4) ZrrS#33$r&#lrrJ*#!2qd39I"`!&%!"e!!""J"%)Z!""1AL"I8%p1d%j@rrj)j`- )+'i!##`Z!!a#4f"+)!G"lHq8jd"+X!!!CJ4J1'!f)!G"lHq8jd#mX!!!CLJJ"d( Ylj6R3#L`!!3J"d(Ylj6R3%U`!!4Q$#!(3Hh[P1G!3V!!!'!J8NF-4`!2El"#Cbm YmY3["NkkrP`3(fF%+)CJ"#LYmYK-ha$!6PiJAe"26Y"19[ri51F"'#iZ!!j#Tbm (6VS!5#CI3UF[#dkk!&)SAa!8!N!!r`a!!!KQ'R!!')"#CbmYlh`["cmZ!!`[,J! )6VS"j%TI,`G1ZJ%560mBJ%jH)&rHr!!+6Y"19J!!)'i!##eS!!B!$%jH,Tp1G8j @!!"#J#"Z!!J5%!*"!!m`!H@)d+i!##e!!!a1ALkI6R919[rd51F('$`Z!!K#Tb! '9N")`#m!3UG`!bm!U&US@#!I2!!J"Y"m!"3b,J!+dN"536i")!F#3!!"C`*64`a Z!N!!#Qm'3Ui!$'"N+'haV#!-CaK#CbmYmDa"lI'U,`K1ZYlB1Kp+4@F#QF`J$'B H3UG)HJ"16VS[d%+R5(S!2%kk,mC1ZZ[q3Ui!$'!L3UF[$%kkrcBQAb!'5-#"r!! %@N!#3!!2!K2rm)%6,8`!$%cI'1"1ALkI6R8'F'&MDf9d!!K*6Pp"6%a23`"19J! !,bi!#%(YmDS[#%kkhNj1ALkI6R919J!!5UhabPE!4!!G3!!)6Pj1G8j@!!!YEI, 8!!a1ALkI6R919[ri51F"#$!Ym9j63$e!rrK#4f!H)!G"lI&Jj8!JF!!!-"#`EJ! -CJC#VJ!1B&*54fN'[Qlrq'rF$'d!#[&HCJ5Cc'!5-#haAN(Ym5,"r!!'3I!!!#K ))!aQ"N+Z!!jJ)$LZ!!`TEJ!)!!)`,I&H3HhaB19!)B`!!&*Ym9iY6!!160m3J%j H)&pF6dl36PErm%MR!aJ-EJ*B!!a[#Memrrm!&Q!!!6)[,J!)3Hlrr#m)6VVp,%U ZrraQ#%*Z!"CJ!!%@3UF[,J!16VVppLCI!P-2r`"63!!`2!$r&d!!#!*V(rm!"J* Vi!!!"MGYmBJ!"&*YmBK#D`!+*fhbe!!-*fi!#!!33S!5%`*"!!m`!H@)-Li!$%M "dS!q!6G(!!*`!"G!!!%JEJ!5&fJ!!3!*3UG#Cbm,3S!5%`*"!!m`!H1)2`"1ZLi !-"p)`#m!U&SJ(cG!!!T5EI'+,blrr%KZrr*1Z[A-%#lrp!*!!2p+3'B)2AcrrJ! @B&iJEI'Q)&!-D!!"!!*Q"NkklE*Jl#"YmDBS8$Pm!"B!#$Pm!%J!#LPZrr)!$$P (!"!JEJ!1+@J!"J!83QF[,I'Q3QFI2!!"6VVL'$`I5NCQ#$em!!%!&Q!'2Acrr3! @60mB`%jH)&rHr!!16Y"19[rk51F"#%+R,bi!#%kkr-`SAb"Z!!J`+!!1X'`!!Q` -8QhaR%)Z!!aJ!!#B%"6S5!*!!!m-3!!%C`T5EI'83Li!$'"q2L`!#N*X!!T)ad+ R3QF[$%+!%K3#33!2-!(ML$m!6VSXqM!I5-![!+KD[TpR$MP(!!T5EI'H3Li!$'" #)#`!%,#YmY4R"N)Z!!aJ-M!X!!B#3"rr5N"@`")X!!EU53*"!!F#33!"J!%#3!! "C`T5EI'3!%)Z!!aJ"Kem!!%!$%cI%)"1ALkI6R919[rU51F2'%kkl$"+VI(+CJ4 1ZZe-+'habL!-CJC5EI'1B1K#Cbm-3Hhab#m)6VVE6NTIC`*Je&*YmC*#Cbm-6VV qm"!I#J!!!@F-8QhaS#m-6VVm['#d3UF[$%kkql!QAcJV!!*#"6!Ym9j63$e!rqT #4f"1)!G"lI&Jj8!X-!!!)%B3+`!*!N!!rl"3CM!J4NUS!!*Q"'!`B#3[$#!%N!" m!"3r!#mV!!`J4LmS!!*1ZL[56VVVK(S"6VVVc'!+8NGT"VjZrqT[V#!&#J!!!@F L3QF[+`!-,`Xr2!!#6VVi-$eIrr45EI'@8QhaS#m-6VVm(Q!!raC-haM`6PiZRdj e6PErrN*R2c`!5%kki"3pArrq6Pj1G8j@q[`YEJ!-rra#Ta!Zrr`#3!$r5-![!#m m!*!$rkKB5'llr%kkh)4#Ta!Zrrd#3!$r5-![!#mm!*!$rkKB5'lmr%kkh'C#Ta! Zrri#3!$r5-![!#mm!*!$rkKB5'lpr%kkh%K#Ta!Zrrm#3!$r5-![!#mm!*!$rkK B5'lqr%kkh#SJEJ!),`K)E[[m5(S!1%KZr2a)HJ!`5'lpr%Kk!#K)E[lm5'lkr$m m!!G1ZY`f)&p$l[VmF%!Jf90!E[T1AL"I8%p1d!%Z6PErr#m(3UF[,J!)6VS!+#i I5SGQ$%+R,bi!#%kY!b)Z(d(Y!b)[#+Ra,8F!$#iI6PiZRdje6PErpNMR$`!JEJ! )%"!#3!$r2J"+4fB)3Ui!$'!!!-3JEJ!)F!%5-!!!!N%!rfXBXR`!2fi53IS!a$! "jNK%3!-`!2m+2!!%CPK#"!a(!!KX"$S(B!*k#$e&rrCm!Q!B)'i!#"!`B!!#3!$ r$%!!,QB#H!&54QN'['lrpQrL%!4R%%+R,bi!#%kY!cSYA`!-B&"#TbmZ!!K1V3- U,9m!$'"!)'i!#(!"%M!!!!*"!2m-33!M9m!JEJ!)FJ%8-"!!!N)!r`a#!#4A`B! "Ca"#TbmZ!!K1V3-b,9m!$'!%3Ui!$%(Y!c)[#+Ra60m!m%jH,Tp1G32r!*!'6PB !!$!Z!!U`EJ!)AF"%!"e!!!a1ALkI6R919[rf51F(#%+R,c`!!!LS6VVAP#KI)!a Q)%+R5(S#CNkk+6j#TdKk!Q"1ZLNd6VVPE%+Z!!KJ!!)m3T4#,!!%,``JEI3%,a" 1ZYIi3UFr2!*!3QG1Z[M#+9m!(%UX!"aQ0N+R5(S#(Nkk+2C#TdKk!Ja1ZLMX6VV P*%*R,``JEI3%,a"1ZYI-2Km[$%kkeb4#VJ!)B!!"hN+R3UF[,!!F6VVi3%kkq&! TA`!J)#`!)()8d)%T3!!N3UG`$#m!6VV@jLPI!#K+V!!SCKj#Cbm-)'hd"#m36VV AHMiI,`a1ZYE53Ui!#'!!!B`JE!!SF!!43!!))'`!+(!'%8!!#5"Yp!4#D!!H3Q` !"R!mj8!j3!!f!QcIr`LQ3U`!#%*X!$K#E!!q!Qbrr`LQ3U`!,N*X!$)#E(rr#+B #E2lr#+C#,!LR)'`!)%+S!!3JE!!J3UJ!##"X!#!#D2$r!!`JE!!J!QMr2`!-)'` !)!*S$rm!$#"X!#!#D2rI!!`JE!!J!QMrl`!-)'`!)!*SrrF!$#"X!#!#D2rl!!` JE!!J!QMrrJ!-)'`!)%(S!!`#82rp!&!!!L"X!#"#D!!5)'hd"%*S!#BjI2rr!$S jI2rr!$a#Tdkkkf`TA`!85U`!&'BF3QF[$#"Yp!3[%%kkeQJq(bm-6VV9`%+Z!!K JHN+R6VVV3#PI!"K+V!!BCLK#CbmX!"41ZZZ''Kp#Cbm-)'hd"#m36VV@-$iI,`a 1ZY@)3Ui!#'"#3Q`)3%*X#%*#E!K%3Q`)4N*'B#3J"R)"!N%!!63!!N)!"qC)3q` )L%(a!!"`rZ8ij5R"%)-38NB-4J!2EpBY6!!)60m3i%jH6R8'F'&MDf9d!!083e! +BfpZEQ9MG'P[EJ"19[rm51F$##KZ!!K#Cbm8)'hd"#m36VV9TM`I)&3[+!!F6VV h,%*R)&3[+!!86VVU4KiI3QFJ9#mS!"41ZZV'(Kp#Cb"8,bJ!'%kkkLSH(d*R)&3 [+!!B6VVUUKiI)&3[+!!S6VV8Z#m86VV8XN+860m3`%jH,Tp1G8j@!!![$%UZ!!K Q"'!!!+3SEJ!)-#`!"QF88d"RC&0!C`a63'FDAd"R"'!!!)B[,!L@6VSQ&NKZ!!K 1Z[p+B()jI!!%!!BJE!!J3HJ!$!*3rri!8!!")'`!)!*SrrF!$%(X#+B#8(rr!&# !!%(X#+B#80rr!&!J!#mZ!!K1ZJk)B$!JE!!J3HJ!$!*3rrX!8!!%3H`)TJ*3Irm !8)!!3H`)TJ*3hrm!8#!!,bi!#%kk$PBSAdjH,Tp1G8j@rqK)j`mB+Li!#%UZ!"" Q"'!!!2)SEI3%3UG#TbmZ!!a1Z[6f6VVe"LCI2LX!!MG6!!)fKb`V!!3RD`!)!!3 R4J!)!Q[rh`!-!Q[rl`!-!Q[rp`!-!Q[rrJ!-!Q[rr3!-F"4)`)(m!!4"k`!-!N! !$qP)!K$r$i%33HX!$!*3rrX!8!!%3QX!$N+R,`91Z[@i,9rrkLe&rqj`!"e!rr* `"Ke!rr-pI!!8rr4#Cd(ZrqS[#(!-5-#"r!!#2`"1ZL6H0em!%%+R3QF[#h!85-# "r!!#2`"1ZL6'-"p)`#m!U&SJ(cG!!""#CbmX!!B[,J!-2c`!&#m&6VVel$JI5'i !%%kkrFK-haM`6PiJAplm!!a1d%j@!!"#TdKk!"41ZL4i,bi!#%kkfpC1ALkI6R8 (3fa[Ff9N1Nj@!!"+EJ!)CaK6EJ!))'i!$L*Z!!S5N!"5VJ!+8Ui!$Q$L6PiJApl m!!T1d%j@rla)j`mB,#i!$Nkkip3JEI3%,8Mr`N+R3UF["NkkmjC1Z[1Q*Pm3+`! -k%J#3!!2j8")`0#,,8$rq"!V!!cS5!*!!!rP3$)Z!!b53$e"rqiJE[r#-#J!&P* !)'lr`M&!!"C+E[rZE43`,[rZ5-$3V[ri,8$rjL"ZrqC#%(!!(8$rd(!'(8$rd4! V!!cS5!*!!!rP30"Zrqip32r53UF["Nkkma)YArrD3UFJE[rD,bJ!$%kkp#iYArr -)'lrfLeS!!crb$eV!"$rj%*R3Hlrb#m)F!a)`)(m!!)r!%kk)eShA`!33UG#Cbm ,-#lrlP*!5-#"r!!#%LX!$1K*!N%!$q0"dN!r!8kk)c!`(dM!,`#S@L!I0d!!%$! V!"#`E[rNCa`JE[r#-#J!*P*!)'lr`M&!!#B["NkkmhjJ!!M+)'lr`L"3,@J!![r H(A`!!Ir95UlrhPE!`#lre@F!!**#Cb"Zrpi[+!L56VSL`"!ICaBJE[r#%A`!!3! %6VVLXL"Zrm*#+!!%)'lrhM!V!!+`D!!-CNiJE[rH5QJ!"PI!Cb!JE[rH$'J!#J! '9X&R,L"Zrpid%l4S!!j@`X)#J!&R(#"Zrm)`+!!H8N!JE[r#-8!!(Lm'6VVbi'! !##a#,[r9B!JJE[rH,9$rhQ!!rf33,[r9Ca`JE[r#-#J!(P*!)'lr`M&!!"i["Nk kmUaJ!!Ii+'lrhJJV!!)!$@Fk$'`!#J!'9X"+E!!'9X(!!@FH3UG)HJJ36VSKr%k krA3[,!L@6VSKk%KZrpj1Z[XF,`C1Z[*QB!!(XJaX!!%!"QB!!0))+`!%!!eA`!b V!*!$!3!)9X'!!@F+,`C1Z[)mB!!(L!JV!!%!$@B+,`C1Z[)UB!!(GL"X!#!#D2r p!!`JE!!J!QMrq`!-)'`!)%(S!!`#82r[!&!!%#"X!#""k!!-!P$rp`"3!!JJE!! JF!%K3!!%)#X!"&+!*d!!"#"X!#!KD`!%!!JJE[r#-A`!!3!D1@X!$J!X1A`!!`! 'F$cM3$P!!$C"l!LQ!P$Ir`"3)!"#CbmX!"41ZZ6'(9rribmX#)T1ZL%),blrhNk k#EaJ!!'N$'`!#J!'CJ!!X!JV!!%!$9I!##X!"!!09X'!!@FB,blrhLm',bi!#%k kqd!["Nkkm9CJ!!DL1A`!!J!'+@i!#!!)19-!$L"X!#J[#%+R,b`!#%kkm9iJ(b" I))!JE!!S)@`!#!!%)'`!)$#X!!`JE!!J-@`!$J!#)#X!"&+!)'`!)#&!!!JJE!! J3HJ!$!*3rqm!8!!33H`)TJ*3hrm!8#!!,blrhNkk#3i["Nkkm0KJ!!BNB!!!l!a X!!)!"QB!!1)JE!!J)#X!",#S!!KR'#mZrpi["LmZ!!K1Z[U1,`C1Z[#NB!!&m#" X!#!)+!!%!!eQ#Lm'6VV`MQ!!"GSJE!!J)#J!"&+!X+X!#'FB,blrhLm',bi!#%k kqP!["Nkkm'CJ!!@b)'`!)!*Srrd!$#"X!#!#D2rl!!`JE!!J3HJ!$!*3rqm!8!! 3)'`!)%(S!!`#82rh!&!!##"X!#!KD`!%!!3JE!!J)@X!#!!))'lr`M&m!!%!'MP V!!i!,$Pm!!-!"R!mid!j3!!f3QF[,!!86VVM'"eIrq-[,!L+6VSI@JJV!!3!$@F !!F)JE!!J)#X!#*!!U!!%28$rp#"X!#!)+!#3!`e@`!*!!!(3E!!b28$rpM!Zrr5 `E[rfCK`JE!!J##J!N!-09X!#3!!"-Llrp**!28(rmQ!'2@lrp2rb-#lrp,"ZrrC [1L"X!#""k!!-!P$rq`"3!!4#TdKk"-C1ZKlJ6VV9e#"X!#!KD`!)!!3[,[rH6VS (ILm'6VV[5'!!"*4+E[rdE`!")JaX!I3!-Qd),b`)SNkk(U!JE!!J##J!"3!0Cc! JE!!J-#J!%T!!E[rd)'`!)$&!!")JE!!J5QJ!%Qi5)'`!)%*S!")JE!!J!QMrh`! --#lrp,"ZrrCQGN*R,b`!&%kkiJ)GArrM!QcIr`LQ3Q`!1%*X!$)JE!!N3K!JE!! J##J!N!-0CdBJE!!J!QMrrJ!--#`!"PP!C`T93'F198"R%Q"N1A`!"`!'B&`jI!! *!!CJ9#mX#*C1ZKhi5'lrhNkkpb`["NkklRCJ!!2#B$J`,!!bN!"Zrr)j3!!b-#l rmNM!d+`!*#m!,b`!*$mX!$*1Z[PS-#`!-NM!d+`!*#e!rqBJE[rQ3K!jD`!1!#` JE!!J-#lrp%M!d+J!"#"X!#!K3!!%-#lrlNM!d+X!"#"X!#!LE!!J-LN!$NM"dUJ !#,+!E$`JE[r#-#J!+&*!)'lr`M&!!#J`,[rZ5-!b,[rZ5-(5U`!%)'`!)#*X!#! d+3!15-,8U!!)NS+3!)%p32rZ)'`!)#!S!!L3!+X!"$e!rr"`!*!!E[r`1!!`,[r Zd%463$S!$%8)!'d%1M`(r`JV!*!$$@F33H`)TJ*3[rm!8%!!188!2VTX!$aX(#" Zrm)`+!!L8N!JE[r#-8!!)Lm'6VVYAQ!!!USpE[r`rqT+E[rUE!4#E[rU-#lrlP0 !28$r`$iZrqTJ-%M()!I3V[ri,8$rjM!X!$T530"%d%G)`)(m#!")3%T!)'lrjK) 35)%CJ3"!8NGT"VjZrm"[bNTX#%"Q6$!X!$a53,"%E3Bj43!mB$Jj4!K)188)D(! !FJ!#33!"0!!#3J!(jNK$l!L)3I%!!($qj6MP+F%3Ja!jI!!"#%"#E!K#3Q`)4'! !!G)-E2rr#%4H`'mU-L`)410"3H`)D$3`%!"53VK#9m(!!@F5-#`)410!3H`)D$' &!!"J!!'H2@`)3[qq3NGJ!!#`-!IQ3%(X#)J2-!!!C`4J!!#D)!IM3%(X#'Jb-!! !8N'i3@m%B!!!K#!(id!L"9*"3H`)5,*`!!"X!Q"Z)!IM3%(X#'LkF!!!E!`J"q0 !3H`)D$S`!!!J"q0!3H`)5,K`!!"[$#!(id""l!K)1$!!!#!(FJ%#33!"0!!#3J! (jNK$l!L)3I%!!($qj6MP+F%3Ja!`,!K!8d!j3!K!5Q`)3'B)1Acrr`K#B!a54fN )[Qlr[Qm!rd`-E2rr#%4H`'mF-L`)4$3"jN*"l!L)!c!J!&E"`!&R"MPmrrm)4%T %EJJj43!mB!!!UM!X#%*53$e!rqS-EJ!2rqT["Mem!!rrkMeZrqVr[%*(B(i`"qC !3H`)L!m`!!"Q!Q"U)!Gb!!*"!!%d!!*#!!IQ5%2X#)K"m3!!F2lP118T`4#$%#! (id""l!K)-B3!!#!(id""l!KS-B8!!$!X#%"53$P!#%!`,!K#8N#`4fB%18F)3Vj X#%C["$P(#%B-E2rr#%4Q"$P(#%4J$&*(D3LqE[qmE`$rI%TX!$aF`!aXrrm!2PI "J!&R'%(X#+B#82lr!&!"!#"Zrm)[+!!16VVD(#m'6VVUXNcI'2"1AL"Ih[`!#Nl 3,94$8&p53eBk)(9ZFf9ZG#"NBA4K)'&MDfj[GfaPC'GPC#!SFQ9cCA4dD@jR+3e QEh*PD@GZ)(*PFf9d6PErmNMR"aK1ZYUX)'hd"#"3*QJ!!L!,Ca!)+`!!#+CR"'! 'B!)Q8f$X)!YQ"NkkfSCJ!Q!#B03SEI3%5QX!2&c!$'[rr`!q9m'!!@F!!MT#Cdk kkLS3(fF'6VVCJQ$`!Q[qr`LQ3UHTG5`I2@X!22rb3NGJ1$!V!$T530"(5-#"r!J !5%"+3")c!%!#33$r2`%[+`L16VSC3N+RUA@mRf`)UE4#TkPe,"p54fN'[QlrmQr #2LX)3Q"L-!IQ3%(V#)J2-!!!Ca+qD`K#CJS`+`K#8d!h3!K#B%!J"q0!-LX!2&* "3HX)5$3`!!#835!(id""k`K)-B)!!#!(id!b+`!m8N&"k`KS0$!!!*4")!IM3%( V#'JaJJ!!8dG+4fbD##X!"JLQC`i`+`!qN!"V!$a63$G!!$i`+`!md'X!1P*!5-# "r!J!5%"+3$G!!$S)+`!'#+CRD!aVrrm!2QCJ-#X!"PG!C`T63'FN9d"R+'!X0h` !"3!')'X!)%(S!!`#82rq!&!!!6Gm!!J!"Q!10h`!"J!'B!BhI!!*!!BJD`!J)#J !#&+!)'X!)#&!!!J`,!!D8N!j3!!D,`Y1ZJ$D)'X!)$!V!$a)`0#S!!K5J#"V!#! K3!!)-#X!20"X!"T53$P!!"SJD`!J-#X!2&*!-LJ!$T*!)'X!)$&"!!iJD`!J-#J !$V"V!""G`'dB,`"#Cdkkk'`5(b!I#J%!!B!"!N!!!@F',`Y1ZJ"Z0hcrr`!m)'X !)$!S!!k`D`!3E!SJD`!J-@X!%J!13QF[+`!B6VVE2"SI3UHTG5!V!#k3!*mh3!! d5QX!0'mD-#X!0%M!,`""qJCL,`J[#bmV!"K1ZYTNB!B[#dkk!""J!2f#60mBi%j H,Tp1G8j@!!!JEJ!)%A`!!3LR)'hd"#mS!!T1ZYFF6PiZRdje6PErpNMR"aK1ZYI f)'hd"#"3,@J!![rf5UlrpQFB)'lrpK!S#+GR"'!-B!JJE[rf,9$rpQ$L5UlrpQB '6VVA`Q!#B!*JaLKZrrBQEI3%)'lrpN)S#+G+E!!'Cl!),!!&#+CR*N*R,b`!&%k kfQSF(c!X!$C)`#m!3IS&YLm),blrpLmX!"41ZYQN5Q`!1'F!!+J`+`!J8N!h3!! J$'`!$!!iE94#CcmX!!Br2!!$6VVY!K!ICai[,!LD6VS@D#mX#*C1ZKCJ5'lrpNk klj4J!2mmB$j#TdKk!HC1ZKC36VV6#LmX#*C1ZKBm5'lrpNkklh"J!2mBB"T#Ccm X!!Br2!!$6VVXVK!IC`J[,!LH6VS@&%*R,b`!&%kkfE`F(c!X!$C)`#m!3IS&##m ),blrpLmX!"41ZYMf3QFr,!!'2c`!!dkkl'i3(fF%3NGJ"$iX!$)JE!!JF"4)`)( m!!4"k!!-!N!!$qP)!K$r$i%3)!I3I!!8)'`!+$&!!!SJE!!J,`K#CbmX!#K`$%M !JI`!!Mm!6VS9RM!I)&ma3!!3)'`!)#m)3UF[#%*R,b`!)#!(d(`!&&*!5-#"r!! #2`"1ZK9b-"mJAdM!,`#S@L!I)&ma3!!33QF[+`!',b`!(#!(d(`!&$m!,b`!#%k kjSik(dT&EL*#TdKk!+C1ZK8`6VV-*#mX#*C1ZK8F5'lrpNkklP"J!2hi3UHTG5! IFKl3J5P!!#i`+`!88N!h3!!8)'`!)%M()!I3U!!%0d!!'$GV!"S!(#"X!#!)+!! #!!eR)%+R5(S!3Nkk&041Z["-,b`)PNkk&-")E[rf6VVYp'!B$'`!#3!'CK![,!L @6VS8TNKZrrC1ZZhDB!$pJNcI'1"1ALkI6R8(B@*[FR4PC"p848a1493k)%4PFh4 TEQ&dD@pZ)(9ZFQ9KBfKKBQaP!e4$8%j@rrT)j`!B+'i!#JJX!!F)TQC#$'`"p!! bE3SGI!!"!!jJ1'!f-#`!-NM!d+`!*#C!&Ui!#6!X!$*53$P!!$)`,!!b5-$3V!! N*N"#%d)Z!!jJ#'!'(A`!!3!160mB!%jH)&pF6dl36PErr%MR!"JSEJ!+$'`"p!! bE3JGI!!"!!jJ8!JX!!F)TQBU-#`!-NM!d+`!*#C!&Ui!#6!X!$*53$P!!$)`,!! b5-$3V!!N*N"#%f!)(A`!!3!1B"C"l!LQ!P$Ir`"3)!![$%kkr&"#,J!160mB!%j H)&pF6dl36PB!!#m-+'i!##"X!#!aE!!b!")JE!!J3HJ!$!*3rpm!8!!J,`a1Z[` @+&p1ALkI6R919J!!51F$##KZ!!`q,J!+2#i!#!a(!!&G`!a(%!"H`B!"$%B!!9h "J!%-4K!!AX'!!@F11A`$k!!51A`"p!!3B!Jj4`!518B!%%cI%-"1AL"I8%p1d%j @rrT)j`%B*Qi!,$iZ!#K#TdkkkBiSAb!-CL"#TdKk!3K1ZK,H3UG)HJ$b6VS5e%k kc`a#VJ!`B!!!e#PZ!#!)LLPZ!"`)MLPZ!"J)NLPZ!"3)PLPZ!"!)QLPZ!!`)RLP Z!!J)SLP6!!JjEJ!U!!ij4`!-5NGQ(N+RUA8J(cP!!!`-E!2S!!aX$$!X!!c3I!2 S18!!$#m-2bi!*MmZ!#41Z[m)1A`!!3!')'`!+#m)3UF[%dkki[`J(b"I))!JE!! S)9-!"#"X!#!`Kb"X!#!aEJ!U!!)JE!!J-@`!%J!1-#`!0NM!,`""qJ%F,`J[$#m X!"41ZY8-3H`)TJ*3hrm!8#!!,`a1Z[UZ,8`!-%cI')"1AL"Ih[`!+%l3#Q0[EQj PBh4TEfi!!e4$8%j@rr`[$%+R6VVSE#KI)!aQ(N+R5(S!L%kk%Ea#TdKk!(*1ZK' b6VV0kN+Z!#TJ9LPZ!#!)LLPZ!"`)MLPZ!"J)NLPZ!"3)PLPZ!"!)QLPZ!!`)RLP Z!!J)SN*X!!ijEJ!S!!`[$$mZ!#Br,J!N6VVq$L"X!#!aE!!5!!ijI!!+!!BY6!! U+&p1AL"Ih[`!)Nl3#Q0[EQjPBh4TEfi!!e4$8%j@!!![,J!)6VVjhL"Z!!JJD!! J3HJ!$!*3rrF!8!!)6PiZRdje6PB!!#mZ!!K1Z[Qi6PiZRdje6PB!!#"Z!!J`+!! i8N!JEJ!)-8!!1#mZ!!K1Z[Q86PiZRdje6PB!!%(Yr(BV52aS)'hmD$#m!!%JEIa S3LJ!!L"Yr'K#+!!$)'hmD%*S!!3JEIaS3QJ!"L"Yr'K#D!!))'hmD%*S!!SJEIa S3LJ!%L"Yr'JaI!!"!!iJEIaS3QJ!%#"Yr'K#U!!8)'hmD%(S!"K$qJ!D)0NJf5" Yr'JaI!!"!!`YEIaS!!K1ANje"e9ZDfj[Gfj19J!!)'hmD"!S!!2!,Ik3!'F-6Ud $5L"Yr'K#+!!$3UhmD%jH6R919J!!(A`!!3!)6Pj1G8j@!!"#,Iai6Pj1G8j@rrT )j`-!2Li!#%UYr'KQ"'!!!2BJEIaS$'J!"!!'9X!JEIaS)'J!&!aS!!-!"PI"`!& R!!$8)'hmD$!S!!CR"'!!!-BJEIaS%#J!!QF)6VS0''!!!,3JEIaS$'J!!3!1CK3 r"dkY!iS-4`!0CJJr2!!+6Ud$LJa(!!eQ"(i+B$B-4`!+CKC#Cb"Yr'J[+!!82c` !$8kkq`3F(f!D$%F!rfB83QFJEIaS,bJ!&$mm!2p1Z[VS("mJEIaS$'J!!3!-CKT #Cb"Yr'J[+!!82`G1Z[Xb%"pR"%kk$*!!B#a#Cb"Yr'J[+!!82`G1Z[U`%"pR"Nk k$(CJ%Ja(!!TQ$#"Yr'J[+!!86VVpbNcI!-"1AL"I9%p1d%j@rqj)j`-!2#i!#%U Yr'KQ"'!!!p)JEIaS-#J!#QFN"%!!qfF!!9463'F!!KK63'F!!Pj63'F!!ea63'F !!)CJ!!1N$%B!rfB-)'hmD$&m!2m!#Q"U)'hmD!aS!!%!#&E!$%B!$&E"`!&R9$m '6Ud$LL"Yr'J3+!!5Cd4#Cb"Yr'J[+!!82`C1Z[Rm(Km-4J!0CK4#Cb"Yr'J[+!! 82c`!#NkkqH)H(b"Yr'J-D!!"!!aQ$#"Yr'J[+!!86VVmrQ!!!b3`"J4!!2&R!!# B@8"R1&P!Cha93'GZ8d"RDP0!CfC63'GL8d"R"'!!!))JEIaS$'J!!3!)C`Br"Nk Y!iSJEIaS3QJ!#Q"X3QFJEIaS,bJ!&$mm!2p1Z[PX(Kp#Cb"Yr'J[+!!82c`!mNk kq9JH(b"Yr'J[+!!86VVk+L"Yr'K#D!!+B#iJEIaS-8B!#Q!N)'hmD%*S!!T)HJ+ @6Ud$BQ!5)'hmD%*S!!TJ##"Yr'K#D!!+B!!#EM!'8d"R"P9!CcjJH#"Yr'J`+!! 3C`T63'FF8d"R)'!H)'hmD!aS!!)!$QF),bhmD%kk!UaJ##mYr'K1ZJ,Z)'hmD%* S!""JGN*R)'hmD#mS!"3r2!$r6VViY"iI3QFJEIaS,bJ!&$mm!2e1Z[LJ(Kp#Cb" Yr'J[+!!82`C1Z[Mf(KpJ1N*R)'hmD#mS!"3r2!$r6VViH"iI3QFJEIaS,bJ!&$m m!2j1Z[KN(Kp#Cb"Yr'J[+!!82`C1Z[Lk(KmJEIaS3QJ!#Q!!!D3-4J!"CMSJEIa S-#J!%'F+8d"R*&0!CaKJ(L"Yr'J-D!!"!!jR##mYr'K1ZJ')B!J[,IaS6VS"bL" Yr'K#D!!3)'hmD%*S!!TJ!!&B$%B!!@C+)'hmD"&m!!%!%N*R)'hmD#mS!"3r2!$ r6VVheKiI3QFJEIaS,bJ!&$mm!2Y1Z[I#(Kp#Cb"Yr'J[+!!82c`!!8kkq"BH(f! !!+`-4J!'CQT1V30U3QFr2!!S5'lrlUP`%"pR'#mZrr![2!!!!3"1ZVlL)"mr!%k kqpaJf%*R)'hmD#mS!"3r2!$r6VVhC"iI3QFJEIaS,bJ!&$mm!2Y1Z[G3(Kp#Cb" Yr'J[+!!82c`!"Nkkpk3H(f!k3QFJEIaS,bJ!&$mm!2p1Z[FQ(Kp#Cb"Yr'J[+!! 82c`!r%kkpa)H(d*R)'hmD#mS!"3r"NkkpfJH(b"Yr'K#D!!+B&)-4J!"CN3JEIa S3LJ!%N*R)'hmD#mS!"3r2!$r6VVfe"iI3QFJEIaS,bJ!&$mm!2a1Z[E!(Kp#Cb" Yr'J[+!!82c`!!8kkpa3H(b"Yr'K#D!!+60m!`%jH)&p86dl3"#T(35S!6PErrNM R!3JSEJ!)3QF[,!!82c`!rdkkpRBH(d*R,b`!&$mm!2j1Z[CQ(Kp#CbmX!"3r2!! "6VVf[KiI1A`!!3!160m3J%jH,Tp1G8j@!!!JEJ!)-A`!!3!16PiZRdje6PErrNM R!3JSEJ!)3QF[,!!82c`!rdkkpKBH(d*R,b`!&$mm!2e1Z[B'(Kp#CbmX!"3r2!! "6VVfAKiI1A`!!J!160m3J%jH,Tp1G8j@!!!JEJ!)-A`!!J!16PiZRdje6PErr#m -5(S!(NkY!eT"lIaf+%J[$%kkriJjI!!#!"!SAdjH6R8%6h"PEJ"19J!!5(S!#Nk Y!eT1ANje"%p`C@i!6PB!!%Kk!!j1V30D6VVjSNjH6R8'3fa[Ff9N!%j@!!"#TdK k!!j1ZJR16VV'L%jH6R8'9%9-6N98!%j@!!!r2!!Z6Ud$LNjH6R919[rN,bi!$NK Ym"j1ZYiU$'i!#`!)CJSYI!0RCA6rk'!),A`$F(9drqK)EI!H5'lrk#mZ!!T)HJ" #UBY#Tcmm!!Y#Th$r,`#TI#eIrq4#TdKZrr#TN5mZrq5TJ`aZ!!(rm'B)2A`!!3! 5B!4#EJ!56PiJAplm!!T1d!!!6PB!!%(Ym"j$qJ"SF!NJf90!E[S-EJ!"!!KQ'NK k!%K)EI!H%#h`(J*!!2p53$m!6VUl0Q!B5(S!*NKYm"i3,I!H!N!!re*!2`"1ZVX F5'h`(NkY!eT1AL"I9%p1d!GQB@PXC@3Z#R0eBf0PC@4PC#i!)P4&6%j&9#"84P4 3)&0PFRCPFMSJ4QPXC5"dFQ&ZFfCPFL!!6PEqrM!Z!!a63'X!!KB-3!!&EJ!#$Z0 )-$X!"Nll!*!$$J#1!33"+J&8!I4+VIaSCRC#CdKZr[j1ZXeF%"m+!!!"C`4J!!( F6Ud$JN+R5'lqrMmm!"G1ZJG`5TpQ&N+R5(S"b%kk#$4"l[lq,`K1ZX5HB$33,Ik 3!'FZ%#hmF3S!!!&R&N(krQJ[#%(krYi[#%kY!d)EI!!"r(&1V305)'hmD"&m!!% !!f!!!AJJEIaS)'J!&!aS!!-!"QBJ)'hmD#mS!"41ZZ'#)'hmD$#m!!)JEIaS-A` !!3!%B%)JEIaS)'J!&!aS!!S!"PI!)'hmD#"S!"3-D!!"!!CA`B!")'hmD#"S!"3 -D!!#!!CA`B!"C``JEIaS,bJ!&%kki5aJ!!%#)'hmD!aS!!%!$'B-)'hmD$&m!!) !$'!+)'hmD$&m!!%!$'!!!0`JEIaS%#J!!fF16Ud$5L"Yr'K#+!!$B!j1V305)'h mD"&m!!%!!f!!!,*#TdkkpN`VAraS6Ud$JL"Yr'J[#%+R2c`!&cmm!qJr2!(d3rV mq#m*3rVi$Lm*3rVff#m*3rVmr#m*3rVp%Lm*3rVp+Lm*3rVfcLm*6VVe"#!I)&m K3!!8)'hmD%)S!")JEIaS-A`!!3!1%#hqN!"R,K!Yr(%+!!!"CaC"q[d!,`K"q[e f,`K1V30#'h`!!Iaa6Ud$8L"Yr'J4I!!"!!0J%%UYr'KR"%kkpMSEI!!"rT&1AL" IA%p1d!C848a1493!6PB!!#m-+'i!#%UYr'KR!!$U,``r2!!"U6SJEIaS)'J!&!a S!!-!"PI!)'hmD#"S!"3-D!!+!!CA`B!")'hmD#"S!"3-D!!"!!CA`B!")'hmD#" S!"3-D!!#!!CA`B!"C`S[$$mm!!+T1@!),``r2!!#U6S[$$mm!!8JEIaS)'J!&!a S!!S!"PI!4!!I!+P&,``r2!!&U6SJEIaS)'J!&!aS!!-!"QC),``r2!!$)'hmD!a S!!%!$&I!4!!I!+P&,``r2!!%)'hmD"mS!!1T45m-2c`!!kNj%#hqN!"R##m-2c` !"+Nj,``r2!!'U6TJ@#m-2c`!"UNjB%i[$$mm!!0#CkP&,``r2!!%3QHT45m-2c` !!DNj,``r2!!#U6S[$$mm!!1T1Lm-2c`!"+Nk,``r2!!&3QHT45m-2c`!"DNj,`` r2!!'U6NSAdjH,Tp1G8j@rr`["b"Yr'JJD!!8$'J!!`!'C`4J!!%S-#i!$&0!CaT 63'G-8d"R!!##8d"R!!#Q8d"R!!$qB!!""L"Yr'J-D!!#!!jQ&#mYr'K1Z[R5)'h mD$&m!!%!%'!5,bhmD%kkqKiJEIaS-A`!!J!3B!!!d%*R)'hmD#mS!"3r2!$r6VV `,"!IC`C1ZJ(bB"T#Cb"Yr'J[+!!82c`!pNkkm(J3(fF%6VS"eQ!!!*C#Cb"Yr'J [+!!82c`!rdkklr)H(d*R)'hmD#mS!"3r2!$e6VV`4KiIB'T#Cb"Yr'J[+!!82c` !rdkklmJH(d*R)'hmD#mS!"3r2!$d6VV[Y"iI3QFJEIaS,bJ!&$mm!2p1ZZqJ(Kp #Cb"Yr'J[+!!82c`!mNkkli`H(b"Yr'J[+!!86VV`AQ!-)'hmD#mS!"41Z[+Q,Kp 1AL"IA%p1d%j@!!![$#KZ!!K+VIaSCK)[$%*RU6S[$$mm!!&#CkP&B(![$$mm!!% JEIaS$'J!!3!19m"%!"m!U88JEIaS5QJ!%'B+,``r2!!"U6PJ##m-2c`!!DNk)'h mD#"S!"3-D!!$!!CQ##m-3QHT1@!',`a#CkNk)'hmD!aS!!%!$'B+,``r2!!&U6T J##m-2c`!"DNj+&p1ALkI6R919J!!5UhmD&I!)'hmD%US!"4A`B!"Ca"#TdKk!'" 1ZJ-86VUk#'"!5(S!5%kY!f)JEIaS5'J!'%kY!f*)HJ!`6Ud$BL"Yr'JJD!!8,bJ !#%KYm"j1ZYGU5'h`(NkY!f*)HJ!+6Ud$@NjH6R8"+3)J+!!)9'mJD'pcG#!!'e4 &6%j&9$SJ6QmJEh"PEL"MEfjZC@0dD@pZFdj@!!!JEIaS%A`!!3!#3UG)HJ!X6VS #KN+R5(S!$Nkk!Ra1ZVNJ6Pj1G4*2GA4`GA3JBR9QCQ9b)'CeE'`!"P4&6%j&9!" 19[rm,`G#TkPe,Kp#Tbm(,c`!!2rrU&JZ(`b(!!!$k'`'hV`!!!2S28F!##iI6Pj 1G8j@r[a#TdKYrC!!6VVAE#eIrra+V[rmCLT#TdKk!5K1ZJ)%3HhpN!![#%+R5(S "$%kk!I41ZVRD6VVaTN+Yr'a1ZX-b$+i!N!-"rraQ'N+R5(S!bNkk!G"1ZVM%6VV aJN+Yr'a1ZX-1)'hmD#m)5'hpN!!r2!!"%#hpN!!#3!$r2`")E[lm6VUcG#"I3HJ !'%2Zr[a`3#$C8d"ZqL"Yr'J[#%+R3qlrr#m*2bhpMLm)3QG1Z[mH-"mJAcm!2c` $k$mm!I4$q[FU,`P$q[*X,`P$q[%f,`P$q[GD,`P$q[G`,`P$q[H),`P$q[%X,`P 1ZZif)"mJAb&!!"41Z[hi5(S!&%kY!f*#VIaX6VV#DNjH,Tp1G3P8FRPTEQFZN!- F6Q&YC5"cCA*fCA*c)'j[G#"bCA0`EfjND@jR,J!,)'j[G#"VEQphELi04QpbC@P REL"SEh0d)%j@!!"+VIaXCa4#TdKk!+"1ZJ$!6VUhY%+Z!!jJBL"Z!!T$lIf3!(" !)YK63'lk1fi!#2f13UF[,I2m3IVqFLm)2c`-!%Kk!'"#Tdkk`*BVAraX5UhmE'B H3UG)HJ"#6VS!FN+R5(S!)Nkk!'K1ZVbJ3Ui!$Q!+3UG1ZZq!,9m!$NjH)&pF6dl 3&'jKE@8JFQ9cEfaeG'P[EL"dBA0V!!C848a1493!"P*PFdjKE3!D3@abC@&NH5" dFRPTEQFJG'mJBfpZEQ9MG#i!)'m!"#kI6Y!JAbkI6Y!LAd+"-KmJAe1"3S$3@'3 #8S"4bIri2S"1d5"i!Ul3r!!+6Y!!!!PD!UJ!!8j@rr`["kN`2A`")2rmF!%G[!! 8!2a#Tcmm!!&)E[rmU6%VArr`,bhrm#mk!'5T68+R2c`"!DQr+errp%+R2c`"!+Q r+errq%+R2c`"!UQr+errr(i"B!iJ"q9!,c8!l%*RU6954`a(!!4[l+Nh%#hqN!! +!!!"C`J[,Ir`3QHT1LmYrr3r2!!&U6NZ(djH6R9%8PC56PB!!#"m!!!*##e3!!K 1ANje6PB!!$Ym%!$r`MYm%!$aaN+R6VVrfM!YmFE3EIr#d(`)!0"m#!$3I"J!d(` -!0"m#!")`#)INS![!8kY!HT1V3&L6Ud"DNkY!@T1V3&U6Ud"DNkY!@T#Tbki!US JAbm)3UFZZ!+U)"mJAb)3NS!-J3!"N!!!E!C#,Ik3!'!''h`!!Ik3!%(YlVS[#+K ZU2ir22rr3QFJ(k!bU4*1Z[l!3UHTHkK3UFa"lIr-3qhZ4L$C)0P)EIr%2c`!"$m m!"J`,Ir5@8!r!$!Yrp"C3$m!U+G1ZJ"m3QG1V3&b5PpR%%+R5(S!6%kY!K*#Cdk Y!+T#TcmYrm*1ZJ"m+erd!%kk!141ZJ1%6VS#X%kk!c41ZJ@k%#hqN!"R"%kk"J! r2!)!6VS'%%kk"hC#,Ik46Pj1G4p$B@iRG#"[F'9Z)%eKBb"3FQpdEf0[E#"3B@0 VB@GP6PB!!%+R2c`!-8+R3UHTI#YIm4j#EI!83Uh`&N+Ym"T1ANje6PErqNMR!3K #EI2k3Lhbq8+YmZ``,J!)d(`!%$i!)!F#3!!"C`*54cm(6Ud##N(YmY`S5#Y-mra #,!!16Ud#`Lm-5(S!*NkY!U)j4`!)3Hhbh#P)!!ST6!!%,8`!#NcI%)"1AL"I9%p 1d!40B@PZ!%j@!!"#,Il"3QhqYN+YrVK#VIkm3UhqXNjH6R919[rd51F"##KZ!!K #CdKZrr4)E[rf6Ud!mNTIC`4#E[rd-#lrp%M!d,b!!J!!+)!TI)!#!%!!"#PmJ!) !3!!)+Ab!!J"!!!a"l!!33rS!X#$C)0N`N8(X!4*$qJ#D)0NJf4Pm!!%"%%*R5(S !G%*R5'lrr%kY!-Sq(dT(CPJYI!!!!K,rq%*R2blrr$mm!!&#TdkY!ISq(dT(CK* #CcmZrra)E[ri,`a1V3$52Kp#CcmZrra1V3$#2Km3,!%3CaC#Tbm8,ccrN!-!U&J `,[rd5-$3RbL!60m3J%jH,Tp1G44$GA0dEfeTHQ&dD@pZ)&CKE(9PF`!(9@jVEQp hEJK"F("XC8e"3`"19[rm3QhZmN*Yl["#EHlZ3QhZl%*YlZT#EHlS3QhZjN*R5'l rr%KZrrj1V3$b5PpR!Q"Z'flrrHpZ1flrrZpXF%JE31p[3Qh[D$!m!2mE31pUF%J E31pV'hcrrqle3LhZp%+Ylh"#Th!B,`"1V3'#+er[C%+R6Ud!iLYIlh4#TbmYmra "l3!U,`Jr2!J!5(S!&N+R6Ud#+LYIlhJYEHpi!!K1ANje#%&58#"5C@0f!%j@rri ["d*(B"SJ"d(Ylj6R3%+`!!!J"d(Ylj6R3%+`!!454`a(!!p[i%*Ylj)lI!!$lia #VHq)1h`!!Hq!3Uh[K%*Yli*#Tcmm!!&"l3%#,`K1V3&#+er[MNUYlijQ'%+R5(S !(NkY!K*#TdKk!""1V3)56Ud!@LiI6Pj1G3**8!!%58008!"19J!!3UFr2!!$3Hd !qLm)6Ud"3LYIlha+VHpmCKK#TdKk!#*1V3)53UG)HJ!16Ud#%NkY!&T1ANje#%P ZG'9b6Q9d!!0(4e"19[hB51F2#%(ZrHJ[#%kkr@JVE[hSmY3VE[hXmYJVE[h`mF) VE[hdmEj"lI'd3qlpq#$C)0N`N4YZr[Madd(YmG4$l[lkF%!Jf90!E[T#EI&H3Qh aLMYm!!(aL%*YmD"#EI'H3QhaR%*YmCT#EI'B3QhaPN*YmC4#EI'53QhaN!"#EI' 13QhaM#Jm!!#!!%+R,VJ#UL"I,`K#Tbki!USJ(b"I)K#5J#m",`41V3*U+KpFK6` m!PK#EI()3UhabN+YmFj#EI'U3UhaV%+YmE!J"91!28$pf%*(B'"#Th!3,`"1V3' #+&mJ$'B33UG)HJ%d6Ud#%N*R6Ud!UN)X!!4#V!!+3Q`!$N+R5-B["NkY!B)TA`! '5U`!"QB33UG)HJ$D6Ud#%N*R6Ud!ULm-3HhaULm)6Ud!LP*(D3DqE[hBEjT#Th! D,`"1V3&k+eraTL"YmDBJ8%*S!!*#TbmYmra"l3%5,`Jr,I('5(S!KN+R6Ud#+LY ImD)[,I'L3UG1Z[d#3Hhab#m)3HhaULm)6Ud"@Mem!%MpjN*R5'lpjN(Y!9)[#%k Y!(*+AfFB3UG)HJ!d6Ud#%N+R5(S!%NkY!K*1V3"D60m3m%jH6R8@FfpMDf9d)'C [FL""F("XC@*eFb"*8!!04%436h"PEP0[BfYPG!G*8%4PEA9i+&*KEL"[GA3JEfB JF'&MDf9d)(0dEh*KCf8JC(9bD@jR)'PZAfPZDA3!(QPZAfPZDA3k)'0KELGd)'e KDf8JCR*PC5"aG@9eC3"19J!!3UhZ`N*YlXC#Tcmm!"&"l3-D,`K1V3&#+erZ[NU YlVjQ'%+R5(S!)NkY!K*#TdKk!!j1V3)56Ud!@NjH6R8)5@jdCA*1CA3!!e9%8%j @!!"#EHlN3QhZb%+YlXT#VHl16Pj1G8j@!!![$%+RF#S[!%kY!B)VAr3%5Uhd"'B 33UG)HJ%`6Ud#%N*R6Ud!ULKYp!4#Tcmm!!C"l3+U,`K1V3&#+9m!"NUX!!CQ%%+ R5(S!j%kY!K*#CdkY!+T#Th!+,`"1V3'#+*p+P'B33UG)HJ#D6Ud#%N*R6Ud!UL" 83P!J9%+S!!)J9%+S!!C#E!!83Q`!&N*X!"K#E!!D3Q`!(%*X!#"#E!!L3Q`!+%) X!!4#TbmYmra"l3+b,`Jr2!`!5(S!3%+R6Ud#+LPI!!T#TbmX!!T"l3+k,`Jr2!` !5(S!'N+R6Ud#+LPI!!j1V3)k+&p1AL"I9%p1d!G83e"`FQpM"e4$8(0PEQ3T9%0 3AdP1593k)'0KELGd)'&XE'pMBA4P)'0[EQjPBh4TEfiJFA9PG@8G9%03AdP1593 k)'0KELGd)'p`C@iJ5@jdCA*ZCA3E9%03AdP1593k)'0KELGd)'GPG#"RE'pLB@a c6PB!!%+Yr'K#,Iaa3UhmE%kY!h*1ANje!!!%$!+`!!T19J!!3UF[,J!)6Ud")Le I!!a1ALkI6R919J!!)#i!#&#!,8!!$%jH,Tp1G8j@rri["c!Z!!T33&*!2J!J"`* !!!&R!P0(3UFr"cmZ!!K1V3%D,9m!$#iI6PiZRdje6PB!!#mZ!!K1V3%U6PiZRdj e6PErpNMR!"JSEHl#)!aR-$!X!!5`EJ!39m!b,!!'XQi!%PI"`!%L,!!)XUi!&&I "`!&R"N+Z!"KJCLC-+&4Jc%+RF"3[!%kY!B)SAb!-CKj#TdKk!'C1V3)53UG)HJ" 36Ud#%NkY!&*#VJ!BB$*+VHl#C`3QM'!%+dcZ`N+81@i!%!!%1@i!%J!'+@i!&!! )+@i!$!!-+@i!#!!3,8`!'%cI'!"1AL"Ih[`!%%l3#Q0[EQjPBh4TEfi!!e9%8%j @rr`[$#KYlX)J$'FJ-#`!"V"Z!!KA`#)X!!LbVJ!+9m(!!@F%B!CJ!LK8B0`Y6!! 1+&p1AL"IA%p1d%j@rra)j`!B*Qi!##!,CJ*J-,IYlX*Q#L"YlX)V81l#B"SSEHl #)!aR#VI8CJ*J"#K8B2)J$'B#B!JSNbm,6Ud!JNcI'!"1ALkI6R919J!!5QhZaQF -2@hZaJ!)8QhZaQ!N3QG#TkPeU'XlAql'$'d$k1l'E!B'E32SlXBpEHl'!!K5EHl '6Pj1G8j@rpK)j`mB,#i!$NkY!'*#Tbm'6Ud"-LSI3UF["8kkrH`SAcJX!!5iEJ! -E`S["NkY!5TJ!!$m2L`!"NT(C`!!N!!3,J!0!N!!!@F5-#i!$%M!d)`Y32rD)'l rfN)3,@i!#2rX)%8YD!!3rr"`!"e!rr4`%4e!rr8pE!!%rrC#Cd(Zrq`[#(!-5-# "r!!#2`"1V3"U19m!"N+R3QF[$$!Z!!a53%M!JI`!!Mm!6Ud!DM!I5-![!+KD)"m p32rQ18F!"VjZrqCR##m'6Ud"+Q"L*QhZ`L!,CcT+D`!%9X!b+`!%XQ`!!PE"`!& R"#C6B#"+U`!-CaJ["L!%88!r!#mZ!!J[+`!3,bX!$%kY!%TJ)Q$#3QF[,J!)3UF ["NkY!6)r2!!$6Ud"#MeIrpJ["Nkkr6T-haM`6PiJAplm!!T1d%j@rqC)j`FB+Li !$N+R3UF[,J!+6Ud"-Nkkr,)SAc!Z!!K33$i!)!F#3!!"C`T)ab!-d)FQ3%)6)%8 X+!!)18F!"#"&1+J!"#"&1@J!"J!#3UF["NkY!6SYArr`,8Erp(!!(8$rq(!4(8$ rq6eX!!6rqN*R3Hlrm#m)F!a)`)(m!!)r!%kY!'SjA`!'3UG#Cbm-)!G53%M!JI` !!Mm!6Ud!DM!I5-![!+KD)"mj3!!'3QF[,Hkq,bi!#Mm(,`C1V3&+29m!%NcI'1" 1AL"Ih[`!#Nl3!!!&f!-!!!419[ri51F$##KZ!!J3&!*!!2pV',"m!!pZ%N(k!5) b!1C*4%%"-"$r#M`!"'F)3Ui!$'!!!1K#Ka!8!N!!rce!rrKm!Q!!!-S30'!!!N! !rfXBX(`!2fi53IS!iM)!jNP%33%`%2m+2!!%CKSJ"qQ!%M4J!!*"!2q5I!!`5-( 5J#i"B!!!L"!dB!!#3!$rDaL`I!"[EK*"qJ#F-J$Q584"!6!3r`Sm!!4Q(#!(kB! 50'!!!N%!rj*m!'(5I!!+5-(5J#i"B%B30'!!!N!!rfXBX(`!6fi53IS!@$)!jNP %33%`%2m+2!!%CK`J"qQ!%M4J!!*"!2q5I!""dR`!#NM"dS!Z!@!')!ITJ#i!8NC T#,aZrrK[!2mb,8F!$%cI%-"1ALkI6R8!IJ#3$!2r!*!'!ra19[rZ51F2##KZ!!K `!4fmri!!mA!#(E`!!J$aF!0#0J$aF!4#0J$aIJ&m!@!!!)C#44!8!N!!rl"(A-! 50(!!!N%!rfXBXR`!2fi53IS!`M3"jNT%3J-`)2m+2!!%9m(!!@FN)!AR3")dF!! #33$rdN#5I!!`1J%-43$rE`C#VJ!-B(T54f#U)!BGK3$l1!B3&!*!!2q`4fdL%$4 `!!*!!2m-3!!XCJ454f!'3Ui!$'"-8NB-4J!%E`$rGK!8!N!!rl"(E3C#VJ!-B$) pI!!%rrTJ($!ZrrS50N$l!N%!raf"!2&64!a%!!&Y$&0ZrrS-EJ!"rrTXh#eZrr) !$%cI%2"1ALkI6R8!r`#3"Nj@rqj)j`m)+'i!#(!"(EcrJ!$aF!)G[!!#!2&`!d) f!2&`"%)f!2&q!A`"B!!!L%*&%"3#3!$rX%GF`")dF!!#33$rDaLbI!!rEK*"qJ$ %0!(Q5N4#!c!Jr`Sm!!4A`F!"CbBJ"F(m!!S50(!!!N%!rp*!NR`!-$S"$%8!rfm '3Ui!$'"k8NGJU#!'(B8!qcJ'%"3#3!$rX%GY)K!dF!!#3!$r$%!!,QB%8NGJ"N+ Z!!aJ6&*'$%B!"'m!rh33&!*!!2q`4fd'3Ui!$'!b2A`!"2rkB"``,[rk%MC!q`* "!2mGJ3$a8d3-4!!"E3a6E[rk$'i!!IrkE0`YE[rb!!a-ha$`6PiZRdje!rm!N!C 19J!!,bhYdNkY!M*1ALkI6R919[rX51F2'#"Z!!J[#%Kk!Fj1V3+#%"pR#LeYmY3 !$'!!!C`JEJ!)%"!#3!$r2!"#Tb!'9N!r!%*R6Ud#dLCI)!YQ)%+R5(S"KNkY!K* #TdKk!B41V3)56Ud!8N+Z!!aJ!!&F3UG#Td+R,`Y1V3%b6Ud#qNkY!ZSSAaLm!!& 5M"L'8S`p4[rXIJ&J&#"Z!!J3-(!!!N!!raL!8Sa54fN'[Qlrl'rQ3K3VEI2mlG* #VHh@3QhYlN*(B'`-4`!"CJ*JDL!(3HhYfZ9!51H!J%+R)JG$lI(#j8%[-4!!2c` !+NMRJ)"#CdkY!`Sb(dcI!3%r!82k!1i[#8+R6Ud$!L)I60m"!5'"!!"#Cb!(3Hh YfZ9!,c!!!#m,)!C@3$m!6Ud$%MJI8NF-4`!"Eii[#dkY![*#TdkY!N)U(dU&CKj #TdKk!)a1V3)53UG)HJ"m6Ud#%NkY!&*#VJ!-B'*`#5m!3IVqM#m)3UF["8kY!PT 1V3)D3QF["8kY!NS3(dL!1!"#Cbm&6Ud#8K!I5)!i!%*(B")J"d(YlGVP3#m`!!" 1V3,L8NF-4`!"EqK+EHhZCJK`!5e!!!aJ"LeYlGB!$%cI'2"1ALkI6R8&G'PYCA) (98436N&043C`B@0VCA3!!QeP!%j@rrK)j`!B8QhYlN+R3UG#TbmZ!"*1V3%b6Ud #qNkY!ZSSAa!X!!%#3!$r5-$3M&5!+%!3&!*!!2m-3!!#9m"+VHh@9m(!!@F53UG 1Z[fq)!a8J#C!+e2YeQ!1$'d!!HhZCJC#TdkkrD3[,J!56Ud#mNcI'!"1AL"Ih[` !$Nl3!!!IL!-J!!019J!!3UG#Td+R,bi!#%kY!6*1V3,k6Ud#kLeI!!a1ALkI6R9 19[rq3QFJEJ!),bJ!5N*R6Ud"XMeIrrj+E[rqC`JGI!!"!!aJ&#"Z!!JJD!"+5UJ !(&I!4!!G3!!-6PiZRdje6PErqNMR!4JQEJ!)+'i!$%*(B#*+&'BD,bi!$#!,8S! [!%M(,`G1V3!b+%XBKf!1B!*5M&*($%F!rfrB3K0-haL!6PiJAe"26Y"19[rm51F "'#CZ!!`Z,J!))!Y5J#K!,``["a!6!N!!rdM!,`"1V3!b%"-#3!$r5-$HJ#"(3K" -haL!6PiJAe"26Y"19J!!51F!'#KZ!!`QEJ!)5P0Q6N*R,b`!5N*R6Ud"NMDI5P0 R"'!!!Aj"l!"5)Q`!5L0)!")JE!"+-@`!8!!@)'`!5N)S!"T#CbmX!%T#CdkY!D) fRdT6C`4J!!&+0Vcre3a6rp9Q!!%D3H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T #+!!D3QF[,!"+3QG1V3'D0Tp+8fB!!*!!3H`!8L*X!%SM5!!5)'`!5M&X!&!!&L" X!%T#+!!D)'`!5Lm)3UHTG5!I)&mK3!"))'`!5Lm)3UHTG5!I)&mK3!"-)'`!5L& m2j!%!#3JE!"+3QJ!+#"X!%T#D!!Z$#`!!3"1CJiJE!"+)Aa"8&"-!#"J$#"X!%S KI&4&@&3!)%*R,b`!5N*R6Ud"dMDI5P0Q9%(X!&)LE!"+)dJ!%L"X!%SaE!"3!"B JE!"+3LJ!'L"X!%S4I!!$!"XJE!"+3UJ!(%SX!%jQ%%*R,b`!5N*R6Ud"`MDIB!j #CbmX!%T#CdkY!ESfRdT6CL!JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!GS fRdcI'!"1AL"I8%p1d%j@rra+EJ!-CcC#Tb"Z!!j)D!"56Ud#%MmZ!!a1V3#b3UH TG5YIlZ"+VJ!)C`J[,J!)6Ud#mP0YlZ4#,J!5B!BGI!!"!"*1AL"Ih[`!#Nl36PE rr%MR!`JSEJ!+1@i!#!!3$#`!!3"1CJ!!d%*R,b`!5N*R6Ud"NMiI3L`!6N(X!&) LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%T#U!!F$'`!#J!QCKSJE!"+%A` !!`!E3QF[,!"+3QG1V3(#2KpJ2#"X!%S4I!!"!"Y#CbmX!%T#CdkY!F)q(dT(CL! JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!GSq(d*R,``r"d+R6VVqp"`I)!B +!!!"CaS[,!!',b`!#Mmm!!&#TdKk!3K1V3)56VS"l"e'!!jJ!!$+$#`!!J"1CJ! !`"Pm!!%!6N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%T#U!!F$'` !#J!QCKSJE!"+%A`!!`!E3QF[,!"+3QG1V3'k2KpJ2#"X!%S4I!!"!"Y#CbmX!%T #CdkY!ESq(dT(CL!JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!GSq(d*R,`` r"d+R6VVq+"`I)!B+!!!"CaS[,!!',b`!#Mmm!!&#TdKk!"T1V3)56VS")"e'!!j -ha$!6PiJAea26Y!K3fpeE'3JEQpd)'0SB@jRC5"dEb"bCA0[GA*MC5"QEh*V(80 [G@aN)'j[G#"MD'&ZCf8JG'mJC'&dB5"QEh*V6PEriNMR$aJi,J!)3QF[1J#kUC` m(d*R3UFr"%kY!J*+AfF%B!!!Q%*R,bi!#UQA2Km-4rrrCJ4J!!#%3QF[1J#+UC` k(la&E("#Tbmk!(`r2!!"UCdSAb!-9m"R(Lm!3QF[,J!+2`4)E[r`6Ud!kM)I)"p +39E"J!&R"Mm(UCTJ2#C8,92rp%+R-#lrq%M!,`![2!!!)!#S@b!I28$rq%*R,bi !#Mm%5'lrm%kY!I*+AfF'2`HTQQ!%2`HTQNcI'2"1AL"IA%p1d%*14%a19[rd51F 2'$`Z!!`U,J!)IJ4#TbmZ!!j1Z[SS*Pmf[!!&0dB!!NT'CL!S45m&)!YBJ#m!6VV kZL"&%"!#3!$rd%G53$i!B!!![M!'D`!!P!a!!!GZ!!#-idJ`1`!'6[X!N!-5!#! !,J!m!%S!@!"Q!(4#TdKk!6K1V3)5+&pJEN+R5(S"'NkY!K)SAf"J3UG)HJ$k6Ud #%LKIB&*#TdKk!1*1V3)5+&pJ4%+R5(S![%kY!K)SAf!f3UG)HJ#D6Ud#%LKIB#K #TdKk!(K1V3)5+&pJ'N+R5(S!A%kY!K)SAf!-3UG)HJ"!6Ud#%LKI,``J#eL!,`" 1Z[Ri%"3#3!$rd%G53$i!3QF[,J!5,bi!$Mm(6Ud$%MJI60mBm%jH)&rHr!!16Y! 09@jVEQphEL"PFR*[FJa1Eb"cG@0S)(9cCA)!%dCTE'8JB@abC@&NH5"PH'PcG(- 69@jVEQphEL"dFQ&ZFfCPFL"*4"C*E'aPCf&X)&4'9&!JEh"PFQ&dD@pZ!!P%DA0 V)'CeE'`33@0MCA0c)(CTEfaKG'P[EJ!14QPXC5"ZEh3JCQpeEQ3!'&4SC5"+6N- J6@9YEh*TB@`J3P9(5%&-9!"19[rq51F"##KZ!!K#CbmX!"K1V3*+(KmjI!!$!"` [,!!86Ud#-NcI%)"1ALkI6R919[rm3UF[,J!)6VVi2LeIrr`J,[rm@)!Y3!!-6Pi ZRdje6PB!!$Ym!!(ZdNjH6R919J!!3QhZdNjH6R919J!!3QhZdN+YlZ"#Td+R3QF r2!"&3IS!6Lm)3UG1V3-#+erZe%UYlY4Q%%+R5(S!)%kY!K*#CdkY!+SVEJ!)lY` VEJ!-lYK1AL"I8%p1d"9$B@iRG#"[F'9Z)&9%8#"cEf0VCA419[h551F2'#CZ!"* 1V3"L3UG#Tbm,6Ud"-NkY![SYArhH3UF[,J!-)'lphMm36Ud#fNUIC`S[#dkY![* J!!9J)#hZi,#V!!T[#Lm,6Ud#mQ!!"8`-E3!"lZ4Y,%kY!MS-E3!"lZ4Y)#mYlY3 [#d*R3UG)HJA36Ud#%Nkk&C!!,`Y1V3,bB!!&'&*YlZ4#Tbm,6VVh&LiI)%FL4c+ 3!#"($&!!!Qm1,`Y1V3,b8fhZj'!!"1iJ"e5!,`""l[lb,`K1Z[G-)!G8J")Zr[) #33$r5-(5J&+",`&"l[hb,`K1Z[FZ3JC#CdKZrI*)HJ983QFI2!!"6Ud!NK!IC`C k!R`"B(a#CdKZrI*)HJ8`3QFI2!!"6Ud!NK!IC`Ck!R`"B&j#CdKZrI*)HJ8)3QF I2!!"6Ud!NK!IC`4k!@"#3QG)E[hb5(S%iN*R(c`!!8kY!*)3(fF'HJ9m!Q!N,bh Ze#m,3QG#TdKk",C1V3)56VS8S#m,6Ud#mP0YlZ4J!!3N5QhZdQBN,bhZe#m,3QG #TdKk"'T1V3)56VS8GLm,6Ud#mP0YlZ4J!!2k)%F-8!!"CJ4i#f!#H!T#CbmZ!!a "l[lb,`Jr"#mYlYK1V3"#5PpQ,#mYlY3[#d*R3UG)HJ3-6Ud#%Nkk&#T#TkPe+er Zi#m,6Ud#mP0YlZ4J!!1Q3UFr2!!,2c`!!8kk$Z)SAb!-CLC#TdKk!m*1V3)53UG )HJ1q6Ud#%NkY!&)[#dkY![*6EHlNB!!$E#PYlY`!(N(X!&*$l[lbF%!Jf90!E[S C4J"13Q`!8$P&!#4#TbmZ!!`JE[hH2a"#CdkY!`T"qK(Z,`J[$%kY!`)TA`!'5U` !"QBb3UG)HJ056Ud#%N+R5(S$4%kY!K*1V3"D3UF[$%kk%$SYArh8,`Y1V3,b8fh Zj'!!![!J4`a3!!&Q!!'#1A`!#`!Q1A`!!3!53H`!8L*X!%SM5!!5)'`!5M&X!&! !&L"X!%T#+!!D)'`!5K&m!!%!'b"X!%T#U!!F%#`!6NL!C`j63'GB8d"R!!#LB!! !a%*R,b`!5N*R6Ud"`MeIrG*+E[h5CMC#Cbm-6VVdT"!IC`JpI2rCrG*J)L"X!%S aI!!"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud"fMeIrG*JG%*R,b`!5N*R6Ud"ZMeIrG* +E[h5CMC#Cbm-6VVd9K!IC`JpI2rCrG*J5L"X!%SaI!!"!#`JE!"+3UJ!,N*R,b` !5N*R6Ud"fMeIrG*J*N*R,b`!5N*R6Ud"`MeIrG*+E[h5CK"#CbmX!%T#CdkY!C) pArh53QF[$$mZrG)[#dkkpR!3(`S!!!&R0#mX!!B[,!!+2c`!!8+R5(S"[NkY!K* 1Z[PU3QF[,!!H6Ud!1N+R,`a1ZJl),9rpe'!!!BK#TbmYmra"qJ2i,`Jr2!J!5(S "S#m-6Ud#+LPI!"4J!!&J1A`!#J!Q1A`!!3!53H`!8L*X!%SM5!!5)'`!5M&X!&! !&L"X!%T#+!!D)'`!5K&m!!-!'b"X!%T#U!!F%#`!6NL!C`a63'FN8d"R2'!!!*K #CbmX!%T#CdkY!F)pArh5,`a)E[h56VVcpQ"k3QF[,!"+3QG1V3'k29rpdLm-5'l pdNkkmpTJAN*R,b`!5N*R6Ud"`MeIrG*+E[h5CK*#CbmX!%T#CdkY!C)pArh5B$B -E[r9rG*Q,N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'N*R,b`!5N*R6Ud "QMeIrG*#Cbm-2blpdLm,6VVe(K!I#J!!!@Fb,b`!"LmX!!Sr2!!#3UG)HJ"X6Ud #%Nkkq"K#CbmX!"j1V3!k3UF[$%kk$ABYArh8B$C#TbmYmra"qJ$U,`Jr2!J!5(S !-Lm-6Ud#+LPI!"41V3)k1A`!!3!3,`a#Cdkk#R)[#dkY![*-haM`6PiJAplm!!j 1d!4dCRGb!"G$Eh9XC#"ZEh3JEh"PEL"dD'8JCQPXC34dCR*N!!094&!%9%C88!! +BfpZEQ9MG'P[EJ!49(*KER0QCA)JFQ9QGA0PC#iK9(*KER0QCA*c)'&bC5"ZEh3 JBQ9TEQFJB@0MCA"dC@3Z#%*KC#"YEf4P!!PYB@0TER4[FfJ)EQ9dBA0MD@N!"@P YB@GP"@pMG'9d&94[Eb"YB@jj)'0[EQjPBh4TEfjc,Nj@rrK)j`F)+'i!#$Pm!!% !(!aX!!%!('B'6Ud#'Q$b$'`!"!!FCJ!!LNTX!#*[1%*R,b`!"LmX!!Sr,!!16Ud $%MSI3QF[,!!B6Ud#5K`I,b`!1N(k"4J[##m-,b`!'%kY!Q*JTQ"+3UG)HJ&-6Ud #%NkY!BS-E!)!!#aY'#mX!!B[,!!+3QG#TdKk!4"1V3)56VVfH%*R,b`!(NkY!$T #Tbm-6VS,eLiI8fhZj'!!!,S-E!!)!"aQIJaX!!8!*&I!5L`!6PE"`!&R-MPm!!F !(%*R,``r2!!"6VVcC"!I#J!!!@F@3QF[,!!H6Ud!1N+R,`a1ZJZ',KpJEQ!k3QF [,!!B6Ud#5K`I2c`!!5mX!"j1V3!k3UF[$%kk#f!Z(dU(CJj#TdKk!&"1V3)56Ud !SP0YlZ4J-JaX!!F!('FQ3QF[,!!B6Ud#5K`I3QF[,!!H6Ud!1N+R,`a1ZJXJ,Kp 6EHlNB!4J!2kN6Ud#)NcI%1"1ALkI6R8L9(*TC@3JG'mJG(*KER0QCA)JHQ9bEbe XC@jRG'JJCQPXC3!E9'p[)'eKERNJFQ9dFQPPFb`JCfPfD@jR)(9`"&4'9&!!6PE rc%MR$aJSEJ!)3Llrc#im!!!#!%)%3Q`!%#eX!!VrkN+R2c`#!%*R6Ud#dLCI)!Y Q)%+R5(S$CNkY!K*#TdKk!f*1V3)56Ud!8P0YlZ41V3)L1A`!!J!F%#`!6NL!C`j 63'F+8d"R!!$UB!!"&!aX!!%!*'G%)'`!5Lm)3UF[#dkkpX)J(b"I)8!!)#"X!%S KI!!!!J!!*#"X!%T#D!!X3QF[,!"+3QG1V3(+2"mJE!"+,LJ!+'!!!-JZ2!!!!J" #Tbm,6VVfILSI%#lrc'F13Llrc#"&%,`!#P1(8S8JE!"+-A`0J!!X5SG[@L"X!%S K43!J)'`!5L&(!#4#CbmX!%T#CdkY!FSm(b"X!%UHU!!S5NCR!Q!Z)'`!5L!S!#K 6J0U!)%8-%!!0CKK+KfB)(A`!!Ir-B!a5K5"&%,`!#P1(8S9JSL!m!!!#!*!!Kbi !B#j+E!!3CL4#Tbm,6VVejLeIrr4#CdKX!&)r,!"3,blrp%kY!1Sm(hi3B!4#Khc C5NC@`!a'rpP@`F!"Ca4#TdKX!&*1V3)52`C1V3#kI0P#Kb!(d+`!+#P!!#K+E!! 3CJBjI!!'!"`-E!!#!"aQ"NkY!KTJmJaX!!3!('B!!*T+E!!LEd*#CbmX!!B[,[r U2b`!$NkY!a)pArrN3QF[,!!B6Ud#5KeIrmd[,!!k3IS"VLm),``[,!!B6Ud#BMP m!!)!('#LB&"#TdKk!B"1V3)56Ud"LLmX!!B[,!!+3QG#TdKk!8K1V3)56VVc%$P m!!-!(%*R,b`!(NkY!$T#Tbm-6VS)D#eIrq![#dkY![*6EHlNB!!"#!aX!!B!('B !!-S`,!!38N!j3!!3%!4R!!##$'`!"3!N9m"+,!"19X(!!@Fm3QF[$%*R6VV[j"! I#J!!!@FL2c`!!5mX!"j1V3!k3UF[$%kk#!3YArrJ,`Y1V3,bB!!!U%)%B!$pKQ! b3QF[,!!B6Ud#5KeIrmdr2!!",b`!(NkY!$T#Tbm-6VS(c#eIrq![#dkY![*6EHl NB'`YE[rUrqiT5`!+,8[rkLCZrqj#Cbm-2`G1ZJL%29rrj!b(!!!#!&h!$%Erf9I "J!&R!RJ"B!$p'N*R,b`!'%kY!NSGArr03QF[,!!H6Ud!1N+R,`a1ZJGN,9rri#m ,6Ud#mP0YlZ4J"'!!r1C1V3)L60mBm%jH,Tp1G4p5CA4bH5"XD@eTG#"PH'0PC@4 PC#`JCfPfD@jR)(9`"&4'9&!!#fCTE'`JF'&MDf9d6PB!!#m-+'i!#$!X!$*53$P !!$)`,!!L8d!j3!!L-#`!0&*!18!!0$!X!$j53$P!!$ijI!!%!"`[,!!86Ud#-LK I6PiZRdje6PErr%MR!3JSEJ!)3UHTG5iIRU`!4JaX!!%!3'B'1A`!$!"#$'`!!3! qCKBJ,!!fd)F[!(!#,`"1V3*U+9m!0Q!i$'`!!3"!AX!-E!!"!%*H`F!"C`S`,!" #8d!j3!"#,b`!0M!X!%*)`#m!6Ud#DL!Id+`!0LP!!$B[,!!fF!-[!%kY!RSTA`! k$+`!!!0J!$T[##Pm!!!$B!!k$+`!!!*B!$TX##Pm!!!#@!!k1@`!2J"!60m3J%j H,Tp1G8j@rpK)j`mB+'i!$LCZ!!Sk,J!)6Ud!BJa&!!4X)#mX!!B[#d*R3UG)HJ, k6Ud#%Nkk#3`[$%kkmLKJ!!+H$'`!"!!FCJJjI!!"!"aJ)JaX!!%!('FD,b`!"Lm ,3QG#TdKk!U*1V3)56VS)e'!!!Qa#Tbm,6VVUC#eIrrKC45"ZrrJ`+!!#X'`!%'F 1-#`!-&*!18!!-'!!!N*#CbmX!"K1V3*+(9rrlbm-6VVqPLm-)'lrq$mS!!*1ZJ+ !5-8J"G#X!#JT3!!S3UF[#dkkmF)YArrm-#`!*'XBX(`!"fi53IS#)M)!jNP%33% `%2m+2!!%CJ!"SK!X!%j)J'F18d"R#P0!C`!!q'!!!9B-E!!"!#4R,L"X!%SKE[r m!#")a5"X!%SK43!N)'`!5N*S!#a#CbmX!%T#CdkY!H)m(f!!!5!3,!"%C`T#,!" %H!&q!@!%3N4#4b"X!%T#D!!X3NDk4fm!!*K)ab!(d+lrr#e!rr!JE[r`$"!!$9E !Ca#k4el"`!&R#&*(8Ulrm'$NZNGQ#L!&8d!q!&1Zrr!J"j!!4&*!5-!JE!"+)8! !*%M%)!63V[rm)'`!5L&!!#"#CbmX!%T#CdkY!H)m(dT'C`*J,#!&8d#`4eI!CKB JE[r`$"!!$9I"`!&R#"Pm!!%!4'"`)!G83$J!2J4J!2pQB'*#Tbm,6VV`M#eIrqK #CdKX!&)r,!"35'lrf%kY!1Sm(b"ZrqJY82rB)'lrk#eS!!6rh%+R,ccrrrlr)'l rk$!S!!K)`#m!U&JJ(ce!rq"#CdKX!&)r,!"35'lrf%kY!I)m(dT'Cc![,!!',`X r2!!$3UG)HJ#'6Ud#%Nkk"X4#TdKX!&*1V3)52`C1V3$D,`a1ZZr3B%CJ(LmX!!B [#d*R3UG)HJ"'6Ud#%Nkk"T3[$%kkll"J*Ja&!J"Q#$Pm!!F!('!'1A`!#!!F-#` !%&*!18!!%#mX!"41V3)b60mBm%jH)&rHr!!+6Y!25@jdCA*ZB@`J4A*bEh)Z#84 TFfXJ4R9XE!!f(P*PBf9TGQ9N)(9ZCAK`C@0dC@3JC'&dB5"LE'pMD`!9@@pe)'K KGQ8J3e05)'4TFf9KFf8Z6PErqNMR!4JQEJ!+3UF[+`!+6VVRM#KI0h`!"!!11,` !"$PZ!!J!!N*R,`Xr2!!%6VS!%$iI60mBJ%jH)&pF6dl36PErr%MR!"JSEJ!+3UF [,!!+6VVR5LCI$&-!!9E!$&-!!PE"`!&R#$Pm!!S!)Q!'1A`!"!!L0T-jEJ!)!!i `,!!Z8N!j3!!Z3QF[,!!',b`!#MmZ!!K1V3-55PpZ$Memrrm!$MPm!!8!('!Q,b` !1N(kq``[##m-,b`!'%kY!Q*#TkPe+9m!4MPm!!%!2N*Z!!j-haJ!6PiJAea26Y" 19[rk51F"#%+R,c`!!!&56Ud"JLKI)!aQ)%+R5(S"T%kY!K*#TdKk!E41V3)56Ud !8N+Z!!aJ!!&`3U`!"N*X!#a#E!!Z3Q`!-%*X!$*#E!!d1@i!#J!Q1@i!#!!N3U` !+$Pm!!`!3R!N+8!!0N*X!&"#,!"%,b`!0R!$,`"1V3*k+9m!1JbX!!!$B!!kE`J TI!!!!f!!1JbX!!!#@!!kE!JTI!!!!PJ!1N*X!$ijI!!"!%!jI!!%!#*#TdkY!N) TA`!B5U`!''BQ3UG)HJ$d6Ud#%N+R5(S!rNkY!K*1V3"5,`a1V3##3Ui!$'!!!,T #Tcmm!J"#CdkY!Y)TA`!+5U`!#QB`3UG)HJ#i6Ud#%N+R5(S!Y%kY!K*1V3"53QF [,!!B6Ud#8KiI,`a1V3##3Ui!$'"b3UG`8#m!6Ud"JLPI!%T+V!"+CMK#TdKk!(4 1V3)53UG)HJ"D6Ud#%NkY!&*#CbmX!"K1V3*5(Km[,!!+6Ud#mLm-6Ud!JN+Z!!a J*L"X!%SaI2rr!"JTEI2m!"4#P%)X!!3[$%(YlXJ[#%kY!)SY6!!-60m3J%jH,Tp 1G3p`BA*KE@9dCA)JBQa[BfX%9%C88!!0Eh9dF(9d)("KBfYPG!9dD@ePFK"MEfj ZC@0dD@pZ)'*XEf0V!%j@rrK)j`F)+'i!#%kY!MS-E!!$!#4R,N*R,b`!5N*R6Ud "NMiI)'`!5N+S!")JE!"+-@`!8!!@3QF[,!"+3QG1V3'U2Km-E!!+!#CA`$)X!#4 V',*m!!GZ%N(k!)Jd!HC+4%)$-#$r#M`!"&I"`!&R%N+R5'`!8NkY!K)r,!"36VV T3#mX!!C1V3,L3QF[,!!B6Ud#5K`I3QF[,!!B6Ud#8K`I,b`!5NkY!))[,!!+6Ud #mMPm!!-!(#SX!#K#Cbm-3HhZb#m)6Ud!HMiI,`a1V3##,88!$%cI%1"1ALkI6R8 !&%j@rra)j`!B*Qi!#N+R,bX!#NkkipSSAcLm!!-jD`!3!!*#Cbm,-#i!#&K!2`" 1Z[aJ29m!$NcI'!"1AL"IA%p1d%j@rrT)j`%B+'i!$N+R,bi!#NkkijJQAc!V!!+ `E!!3C```,!!`8N!j3!!`B#![$%kkpq*#CbmX!"K1V3*+(KmjI!!'!"`[,!!86Ud #-NcI')"1AL"Ih[`!#Nl36PErq%MR"aJSEJ!)*Qi!%MSZ!""1V3"L-#`!,&*!18! !,%+R,`Y1ZZ-S,KmJ4c`3)%F`KM!'9d"R$&0!Cb463'FkB!!!KN*R,``[#dkk!+S 3(fF+,``[#cm&6VVi'Q!!!)*#Cbm-,`Y1ZJ#1%"pR#Lm-,`Xr"8kkrbaJCNTX!"* A`#"X!!C)ji#!3UG)ji#!3UF[#dkY!6)L(dcI!3%[!8kY![SL(dcI!3%L36)S!!D b89I"J!&R%#m-,`Xr"8kk!9B[$%kkkK"J'#mX!!B[#cmm!!4#TdKk!"j1V3)56VS !e#m,6Ud#mNcI'1"1AL"Ih[`!$Nl3!5"19[rd51F$'#KZ!!`X,J!)3UF["NkkiN) QAcGV!!)!!N+R3UF["NkY!6*1V3,k)&mq%%TX!"*Q1$!X!"#`D`!#CK!jI!!"!") JE!!'-8F!"Q"',b`!"Lm'3QG#TdKk!%j1V3)56VS!9N)Z!""J-'!S)'`!"VjS!!C R(LmX!!B["Mmm!!9#TdKk!#*1V3)56VS!,%)Z!""J"Kem!!%!%%cI'-"1AL"I8%p 1d!%J$QpXC#"MEfjZC@0dD@pZ!%j@rrC)j`-B+'i!%LCZ!!iq,!!',#`!#%+R3UF [#dkY!6*1V3,k)&mj8!!'3UF[#dkY!6)JAbPS!!`!##m-,`Xr,J!-,bi!#%kkja! j4`!'+8B!#%cI'-"1AL"Ih[`!$Nl36PEqq%MR!3K#TbmZ!!T1ZZ%U+&mJ$&L!,`" "l[lm,`K1ZZ&m%#lqr!*!!2mp32liIJ&J1%(Zr[`3-(!!!N!!r`a!!!eA`%(Zr[` 5-(!!!N%!r`a"!!TA`B!"C``J"d(Zr[`4[!!J!!"54fN'[Qlqq'r#3UG)HJ!S6Ud #%N(Zr[`[#%kY!*SJEJ!1-A`!"3!F60m3J%jH)&rHr!!+6Y!I9%C88$SJ4A*bEh) JCR*[E5"QEh*PD@GZ)'K[Fh3k)!!!&A`$1!!,6PB!!#m-+'i!%(!-`Hi!$PC!1)! `,I4i`Hi!$&C!18!!!R!-`Hi!#Y"818!!"$!Yp(M"lJ!)d'`!!MP!!!BSAdjH)&r Hr!!-6Y$038Y&8N9$9!!!6PErk%TYp#*Q!Q"%5'lrq$mYp)Jr,I3N2c`!!6mYp#* 1Z[q+%#hdI@F-5'lrq%KYlUUST@!'5'lrq+LM3Hhd*Lm)3QFr,I3LU)9#EI3LU&C 1ANjeaNa98dK#98B!!%j@!!"+EI3LCJBlEI5'p#3`,I3L3E`!8%(Yp#B4VJ!*!!" 5EI3L6PiJAe426Y$#98CI3dK"8J!!6PErqNMR!`!q,I5+-#i!#&0!28$rqN*'B"4 2[!!A)!G"lI`Qid!q-!!!8NCT"VaZrrT[jMe(!!T-h`$!6PiJAe426Y$'6N4548` J)!!!6PErrLm(2LhdLNqm!"FJ"d(Yr#EM3$)`!!#bEJ!)Ca*2[!!A)!G"lI`Qid! q-!!!B0Jp4`!+,Kp1AL"I9%p1d-C14&"59L!J!!"19J!!-#hdH-(Z!!Sr!(!-`Hi !#$m!U*3`,J!)d@hdL$!Z!!V4EI5'3QFr,I5)6VVr1$YIp)41ALkI6RA548a06eC &)!!!6PB!!$!Yp(M"lJ!+9N!r!$!Z!!K53-(m!!a@3&G!2`#SNcYZ!!MdL$YZ!!V dKN*R2bi!#%kkrZ`lAr5%6PiZRdje`8*668p@45!!!%j@rri["d*(B#!`,J!)3E` !&d(Yp)c"r!"36l`!6b)(dN!4[!!J%!"54`a(!%p[fLiI6PiJAe426Y$D49*26%P 143!!6PB!!$!Yp(K%3$m!3QHSNN)Yp!e1ANje`e958dp5Ad8!!%j@!!!r,I4i3QH SNKYm!!(d$8jH6RA$99*66e*I4!!!6PB!!%TYp)C[#Mmmrrp#CdkkrYC1ANje`N& $5ep68%%!!%j@!!"#CcmYp)K1Z[m!6Pj1GF0"8P**38G&!!"19[rk51F(!$!Yp)5 `EI`JCJ!!`%KYr&a#Ccmm!!`[,I3HU1mr,I`L6VVr%M`Yr#)q,I`J-#hm)N'm!"G "lI`Qid!k-!!!-#hm)N'm!"G"lI`Qid!aVI`J!!!lEI`Lr#"#CcmYr#*1Z[hi1er m)VjYp)TQ#$YYr#$dLQ!D3QFr"dkkrGi`(d'm!"G"lI`Qid!aVI`J!!#mEI`NCK` lEI`Lr#3`,I`N3E`!&d(Yr#EM3$'mrrm!!'!5-#hm)N'm!"G"lI`Qid!aK3!!1fh m)25%B!T#Ccmmrrp1Z[h360m!i%jH6RA549CI6%P143!!6PErqNMR"`!`,I5%X'h m)QB!!-C)EIaF3QFr22rd,bhd(UM[2bhm)%kkrL3m,I`L2Lhm)$!Yr#*"[!!A3Hh m*Z0!1M!!!$!Yr#*"[!!A3Hhm*Z0!-Dhm)!!!1fhm)2`L-#hm)%'m!"G"lI`Qid! lF!!!r##qEI5+CJJlEI`Jp)TJ'N*R2`G1Z[cU-"p"[!!A3Hhm*Z0!-Dhm)!!!['h m*'BF1fhm)[`N-#hm*%'m!"G"lI`Qid!a[2rr!!"J%M!Yr#*"[!!A3Hhm*Z0!-B8 !!$YYr#,dK'!+3QFr2!!"6VVmh%cI!1"1ANjec%P149p'488!!%j@rr4)ja%!5'l rpMmYp)Jr,I5'2c`!!A"3N!"Yp)Br!%kkqb4)E[rf2bhdH%*R,bhd(UM[-#hdKP4 !28$rp(j2B%)`,I5)3E`!&d(Yp)c"r!"3)JG6381m!%md,I5)4E`!&d2Yp)c&r!" 36l`!6bB(eN)8-6!!!N)!rp*!%B)3!&0(D3DqE[rdE,J`,I5)3E`!&d(Yp)c"r!" 3-LhdKN1m!%r53"'m!#!3!%cI!)K1ANjeb8j649*8Ad-!!%j@rrJ`,I5%3E`!&d( Yp)c"r!"3-LhdKN1m!%r53"'m!#!3!%KZrrJr,I5)2bhdKMmm!!%r2!!"6VVk8"! Yp(eR$%KZrrK)EHkUU+9J"NKZrrLSSdjH6RA&8N&649p$5!!!6PErrLm(3NGJ-Nq m!!JJ"d(Yr!lM3$)`!!#bEI5'EaT2[!!))!G"lI`1id!r-!!!2bhdL%kkqlTJ#&* ($%F!#'r),Kp1ANjee%&#)*!&!!"19J!!2c`!!kR)6Pj1GF*&6%`JN!3!!%j@!!! lEI5'p))lEI5)p)"1ANje`e958dp5Ae-!!%j@!!!r,I5#2bhdJ%kkqeT1ANje`e9 58dp5Ae)!!%j@rrj1ANjed99&8PPI9%8!!%j@!!!-E3!rp(T@`'F5$'d!"23D9m( !!@F''h`!!I`-6Pj1GFP18d959&p0!!"19J!!$'d!2r4k9X"R%!aY!!6d'PI"`!& R"%)Yr!a1ANjea8j%AdP18d8!!%j@!!"+EI3DCJBlI!!"p"T#Cc!Yp"T%3$m!6VV kI%jH6RA98##3"J!!6PB!!%TYp"TQ"MYm!!(d'N*R2bhd'NkkqP41ANjea%pA6L# 3"!!!6PB!!%TYp"TQ"MYm!!(d'M!Yp"T%3$m!3QG1Z[SS6Pj1GFa&4P3JN!3!!%j @!!"+EI3DCJBlI!!"p"Sr,I3D3QG1Z[S!6Pj1GG**4dK8)*!$!!"19J!!8fhd'NT Yp"TX"%*Yp"T6EI3F5Qhd('`%3Qhd($mYp"`r,I3D6VVk$%jH6RA$99*66e*I8!! !6PErpLm(5'lrpN*R3QFr2!!B2c`!8%kkq"a)E[rfU+0#4f!)2`G1Z[SJ8NF-4`! AEr)Z(djH6RA$6%9"8Pp63`!!6PErp#m(-#hd'QF18d"R9&0!C`!!RQ!!!,K)E[r f2bhdL$mYp)Br2!!"F!'hdKMm!6VVh[MiYp)CJ)$!Yp)4"[!!A3HhdM-(m!&" 2[!"2)JI53"'m!#!3!&*($%F!6frDB'a)E[rf2bhdL%*R2c`!!6!Yp)C53$m!6VV hGMeYp)Erp%*(B#)`,I5%3E`!&d(Yp)c"r!"36l`!6b)(dN!4[!!J%!"54fN'[Ql rp'rBB"j)E[rf2bhdL%*R2c`!!6mm!&"1Z[FX2bhdK%kkq6K)E[rfU+-Z(djH6RA $6%9"8Pp-53!!6PErp#m(-#hd'QF18d"R8P0!C`!!MQ!!!)j1Z[lq5'lrpM!Yp)K 53$m!3QG`'*!!EI5)2`!r2!"36VVfdNKZrrDSSc!Yp)K53$i!B!j#Ccm(6VVhT%k kq-T54`a(!"G[l'"%6VVqYNKZrrC#Cd*R2bhdL$mm!&"1Z[D85'lrpULM2@hdL2r d3NGJ%%*R2`G1Z[GQ6VViM&*(D3DqE[rdEqTJ"%kkrM3Z(djH6RA&8N&649p%53! !6PErp%MR%3"+EI3DCJBlI!!"p"T)E[rf2bhdL$mYp)Br2!!"F!'hdKMm!6VV f+("3N!"Yp)C63,"Yp"TX$("3N!"Yp)C63$Y!p"T)E[rf-#hd'X(Yp(K%3$m!3QF [,I3HU1p`8*!!EI3D8d!p32rd2LhdKQ"%-#hdL%'m!"G"lI5-`I`!8%qm!%mL"c3 Yp)K&[!!A3qhdM-Am!&!f,I3DeNG([!"2eN)8-6!!!N)!rp*!%B)3!&*(D3DqE[r dElB-4`"3E#)`,I5)3E`!&d(Yp)c"r!"36l`!6b)(dN!4[!!J%!"54f$B60m!L%j H6RA%48a&9%9I3`!!6PB!!$!Yp"TR*P0!CcTA3'GJ9d"R!!#'"%!!$fF!!)j93'F !!0"A3'F!!4*J!!%B3LhdI8)Yp(`r1J%DU)Jr2!!"U)PJ!!%!F!'!,I4m'd$dI%* !%#hdI$m!2c`!!Mmm!!*1V3++2c`!!P52U)KJ!!$@F!5!,I4m'd$dI%*!%#hdI$m !2c`!!Mmm!!*1V3++2c`!!P52U)KJ!!#X'h`!!I4p2c`!!kL*B!!!R(!!DaL`I!! (EK*"lI4p-J$Q584"!6!3r`Sm!!4Q+(!"4J$!,I4m'd$dI%*!%#hdI$m!2c`!!Mm m!!*1V3++2c`!!P52U)KJ8R!#DaL`I!!(EK*"lI4p-J$Q584"!6!3r`Sm!!4Q+(! %4J$!,I4m'd$dI%*!%#hdI$m!2c`!!Mmm!!*1V3++2c`!!P52U)KJ#N)Yp(dr2!! "U)P1ANjee%9B9&p06d3!!J!!6PB!!%*R3QG1Z[A'6Pj1GFK2689I3e95!!"19J! !8fhd'NTYp"TX"%*Yp"T+EI3FCJBlI!!Bp"a`$-(Yp"T@3$Y!r&a`$-(Yp"a@3$Y !r'!lEIaJr&C#CcmYp"T1Z[581erm)%*R-#hd(&0!2`"1Z[5#1erm)NkkriK1ANj edd98Ae0$8Nm!!%j@!!!`,J!)Ad"R)&0!Cb*63'FN8d"R*P0!Cb*63'FH8d"R)!4 !!!jR)'!N6VVjI'!H6VVejQ!B6VVj(Q!56VVh"Q!-6VVepQ!'1h`!!I4q6PiJAe4 26Y$%6ep$9&*-)!!!6PB!!%)Yp!j#,I323Lhd&%)Yp"9"lI31+dMd#(!!3E`!rcY !p(SlI!!#p(j1ANjec99-9%PI3dJ!!%j@!!!`,J!)"%!!)fFS@d"R*&0!Cb!%3!! 1Cb*63'FN"%!!$@FN88"R*J4!!!eR*P0!CbKJ+MYm!!2dIQ!L6VVijQ!F6VVirQ! @6VVfAQ!36VVeDQ!+6VVj#Q!%6VVrD%jH)&p86dl3a%pI8d963b!!!%j@!!!`,J! )"%!!3@Fi8d"R1P0!Cca63'Fq@8"R3&9!Cd*63'G%@d"R4J4!!"0R4PG!CdK93'G +@8"R6&0!CdjE3'G3B&*1Z[N8B%a1Z[NkB%C1Z[Q)B%"1Z[P@B$T1Z[QNB$41Z[X !B#j1Z[S8B#K1Z[ZUB#*1Z[KiB"a1Z[Q'B"C1Z[KqB""1Z[LQB!T1Z[b%B!41Z[h Z6PiJAe426Y$%6ep0490$)!!!6PErpNMR!`!JEJ!)3qlrqL,B-T!!3NC#4dqm!!9 +0R$kCeT2[!!&%$C`qNL!$%!!-&c!E5j2[!!&%MC`qNL"$%%!19r"`!&R'L!'`I` !#Nqm!!850R$k5)'5I!!`dN!m!@!@6l`!"4!fF2T)J!a!!#eQ"L!'4%!m!&*(B*` p4J!-60m!`%jH,Tp1GG088Pp86ep*!!"19J!!,`Fq,J!)$%F!)'`-6VVaGMm(6VV pR'"X$%F!J'aQ$'d!825'E5)3,I`0C`j1Z[&@6VVcZNkkp-"J$Nkkmij+EI3LE`4 6EI3L%#hm$'F16VVeQNkkpP)r"kL$B!Br"dkkmB)`,I5%3E`!&d(Yp)c"r!"3-Lh dKN1m!%r53"'(%!"5EI5',Kp1AL"I9%p1d-e%8N&A3dK"!!"19J!!,`Fq,J!)3UG )abm(F(m[!+KB)"p"[!$r2J!`,I4qCa*63'FB8d"R)P0!C`!!`Q!!!-)r"dkkrbT J!!#i3QhdIMm(6VVpE'!!!+S-4`!JA-"YG!a(!%"G`F!"CfS-4`!mA-"Y%!a(!$p I`F!"C`Bl4r4kB(i-4`!`A-"Y#Ja(!$PI`F!"CK3-4`!Y9m'!!@B+$%F!+eI"J!& R($!("%$rJ%'m!2mJEI3)%)G5VI3))'hd#%)3B$`-4`!lCJK"lI38+dMd#'!X3QG )EI316VVprMYIp"T#CdKYp"41Z[h`1erd($m(6VVp2%*Yp(jJ"%*Yp(iZ(djH)&p 86dl3d&**6P4*9#!!!%j@!!""lI31+dMd#$mm!!QSLMmm!!'SL8)Yp(a#3"!Yp(` r!$mm!!)r2!!#6Ud#LMmm!!*8MkL)3Qhd)N)Yp(e#,I`-3UHTG5YIr&K#EI4q1h` !!raH1h`"iraL1h`!!raFF!c"r!!B9N!l32aJ1fhmB2a@1fhdL[`J1fhm*2`L6Pj 1GF90Ae*&8d98!!"19[rD51F$!%Kk!@T)E[rmU3!pI!!Srr3pI!!%rrBpI!&8rrJ pI!(mrrT#Td+R5'lrp%Kk!6*)HJ%N5'lrfMmm!!*1V3+53HlrfKm32c`!rdkY!TS [#"mm!!%r2!!%F2m[!%*R3UHT%bYIr'3[,IaNU(-r,[rmU)Fr2!!*U)Sr2!!"U)P #,I4m3N!3,I4m2`!r2!!#2c`!!NkY!SSr2!!#9)qSL%+RU0JVAr3H2c`!#ULF3QF r2!"AU)dlAr4i3QhdLMYm!"Im*%*'B"BJ"P*!6E`!&b)'3Hhm*Z0"-B!3!&*'$%B !&frN-#hm*%'m!"G"lI`Qid!a[2rr!!!EI!!"r!e1Z[jZ3NGJ'#!(8N$R3%qm!!J L"d(Yr!lM36'!%!"54`a(!!K[iN*(B!Jr"dkklq454`a(!"G[mNkkpC!!6VVjZ%k kmrj#,I3060m!`%jH6RA&69p*6NP8)!!J#6)a,8j[GLdi03e0B@0848a1493JEfB J"Qe[EQ&ME`"19J!!%#hd$@F%6VV[d$mZ!!K1Z[cB6PiJAe426Y$&65#3"J!!6PE qr#m()'i!#%2Zr`"`3#,B8d"ZqK!Yp!eR"%kkljB3,[m!!N!!rce!r[aq!@!D6l` !rd(Zr`!3-(!!!N!!rcm!6VVmJ&*(D3DqE[lmEq!Z(djH,Tp1GF908e45)*!$!!" 19J!!%#hd$@F%6VV[4Nkkl6j1ANjea8eI4Na98dJ!!%j@r`!JEJ!)3qlr!("!)YK 63'lk3Hlr!"m32c`!rdkY!TS[#%kkreK1ZZd%6VV[D%kkm'j1ALkI6RA&68a1)*! %!!"19[rk51F"'#KZ!!JQEJ!-1,`"@#mV!!)[2!#3!i"1V3*b)"p"[!$r2J"#Tc! V!!j)`#m!,c`!!!%!U&K+ReE!CcS-4`"!A-(!!@F`$%F!B9c!E4B-4`"kAm(!!@F -)!H3!(`!)%'m!2mq!(!")JH5I!"!3l`!raQ"!!"JE!a(!'"Q,%+R-#X!$NM!,`! [2!!!!3#S@%UICJj`!A)E3l`!raQ"!!"J3R!"'BF!!'!k$%F!)'BZ3UF`+`!15-! [!#mm!!!"!+KB5TpQ#R!"'E`!)!!!B"4`!A)!3l`!raQ"!!"J"R!"'BF!!%cI')" 1AL"I8%p1d-90AdP18&98!!"19[rm51F$!#mYr'5T)MiYp)T#4Q"#2c`!!b!'8N$ "r!!-9N"A3$m!U*02[!!A)!G"lI5-`I`!8%(`!!![#%*R2c`!8+L&6l`!&b!(3Hh m*Z0!2M!!!&*'$%B!&fqi-#hdH-(Yp)C@3$m!-#hdL&*!`I`!$&C!9d!r!+L66VV YP#mYr'5T)dcI!-"1ANjeb8p98%4"9%8!!%j@!!"1ZZY13UHTG5!IN!#Yr&J[!%+ R,VJ#p#)I)"qbJ'iF%#hd$3S!!!&R"Nkkl8aJ"%kkl54#TkPe+erm@%jH6RA*6dP %6%8J)!!!6PB!!%jH)&p86dl3b8p"3e4*9N%!!%j@!!"1AL"I8%p1d-P23da*3dX J!*$r!*$B(!(+!!P86%j8!*!$8P0*@N8!N!0H4P*&4J#3!fT#6N4-!*!$GN&-8P3 !!3##4%P86!!'!*T%6%p(!!3!lNe&6P8!!J%U5801)`!!!8j$6d4&!!B"@J!!rrm !N!MrN!3!N!-U!*!&J2rr!*!$1!#3"B$rr`#3!d-!N!3"6Irr!*!$B`#3"!+Drrm !N!0c!*!&$Irr!*!$J`#3"62rr`!!!6-!N!8qrrm!!!&r!*!%!Ecrr`!!!Km!N!3 $#Irr!!!#J`#3"8lrr`!!!cX!N!9Hrrm!!!94!*!&#rrr!!!'43#3"6(rr`!!"R3 !N!8Irrm!!!DA!*!&6Irr!!!'`J#3"9hrr`!!"ZN!N!3"!2rr!!!(%!#3"!%"rrm !!!Gp!*!%!3,rr`!!"qN!N!@!rrm!!!Kb!*!'rrmJ!!Pf!*!&!Irr0!!0'J#3"3, rrc!!BZi!N!8$rrm`!'a-!*!&"2rr-!"`A!#3"3Arrc!!GMJ!N!8'rrm`!*A%!*! kEK`: !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.
tim@k.cs.cmu.edu (Tim Maroney) (11/26/85)
echo extracting tftp.Hqx... cat >tftp.Hqx <<'!E!O!F!' (This file must be converted with BinHex 4.0) :"(4QG(!!39"36&4'9&!J!*!(V!$cG3#3"!%!N!1S!*!$T`#3!`)G!*$c*#084P4 3)&CPFR0TEfiJ-Lib,#!a-#"6CA"dC@eLCA)J-6Ni03#3!`G"8&"-!*!'(&4'9&! !N!-"5801)`#3"B"'8N9'!*!&J!#3!``!8!"N!2m"T!'mrrm!N!--!&!!C!$r!D3 $#Irr!*!$m!!*!*!&FJ!2!)3!S!3#6dX!N!9b!,i!K!&2"!C$B@jMC@`!N!8L!!m !-J&2N!!08(9d)%jKE@8J5'9bC3#3"P3!$`"N!8q3!!e3GA3J5'pcG#")CA*P!*! 'PJ!2!+8!@JB&390$58N!N!D@!&m!T3#U"J9*E@&RC3#3"TB!V`#P!2S'"8pMG'9 d!*!'PJ$r!+8"5JB$6@&M!*!'$!!2!"`"6iJ$AM!k!*!'2J!2!%i"6iJM6Q&YC5" [FL""C'4bCA0c)'pQ)(4SC5"5C@e[G'8J5'pcG$S!N!4q!!3!N!9"!#)!B3"b"!* 25`#3"8%!Y!"K!33%"N0"6N0&6!#3"5-!)`!d!(-&"8CPG'0S!*!')`#d!$-"'!8 &8h4[FQ8!N!B+!#-!(`%BL#**EQ4TBf&dC5"hD'PMD#"dFQ&ZFfCPFR-JG'mJB@a XEhFk!*!$5!!"!*!&#J!+!"N!m)JC35"1CA4hEh*V)%9bFQpb)%KKF("PEQ9N)3# 3"P!!#J#(!2#)$e0PG#"LH5"6CA4*9'9iG!#3"(!!!`#3"3S!A`!H!*X%!Np,!*! &#J#j!"i!p33'3d&13d9-!*!&33!+!&!"5T!!!f4TFJ#3"LJ!#J!h!8U)+P"XC@& cC5"dHA"P)'PZ)(4SC5"QEh*PD@GZ)'4TFQ9MG'pbH5"ZB@eP,J#3!rS!#`#3"4` !SJ!Z!2)%"L!J6h"PEJ#3"6X'%!"0"0#%#b!J5@jfDA0TBQaP!*!'@J#L!'`!mJ3 ))#"$B@jMC@`!N!8@!3)!0J&LJ!#3"MX"#J"0!9S%"b!J4@TPBh3!N!CD!3S!E!& D"!FJ)%4bDACP!*!'#`!-!(d!K`#3"`X!KJ"p!*B!N!F8!2i!G!$rJ!#3"K3%&!" d"(Q)$NPZGQPcD@*XC5"8CAKd!*!&I!!-!)`!K`#3"hd!V!#-!9S&&deeE(4TF'a P)%CTE'8J8f9XC@0dD@pZ!*!%R!!$!*!&#J!b!#J!EJ3#6dX!N!8+!)`!+!$)"!C $38j$48`!N!9c!!S!JJ$`N!!54'9QBA9XG%C[FQ9TCfj)Eh0d!*!&-J!+!'3!m)K )8'aPBA0P)(4jF'8JD@iJG'KP)'jKE@8JEh)JER9YBQ9b)'pQ)(4SC5"QEh*PD@G Z)'K[Fh3JH@pe)(GKER3JG'mJFQ9KBfJZ!*!$B!!#!*!&M!"Z!+8!dJ3%899*9!# 3"3S!@J!h!5b),N%JCQ&dB@`JCA*bEh)JD'&`F'9ZC@3K)#"8D'8JF(*[Ch*KE5" hD@aX)'4TC5i!N!93!"3!K`%XL!*H-!#3!l3!"!#3"B`!EJ#P!0)%"%K"6&3!N!8 +!&S!0`%XL#P'BA4KE#"&FR*[FL%J35"dBA0V)'pfCA*QE'phC@3JDA4c)(0dB@0 V)3#3"P!!&!"I!5b)&P0dB@0V)&"[D@jdCA)JB@*[GA3JAM!!N!9N!"3!F`%XL"K 8BA0V)'0[ER4bEf`JBQa[BfXJBA3JAM%!N!9i!"3!K`%XL!p8BA0V)'jKE@8JDA- JAM)!N!-#%J!9!*!&$`#d!"i"#BJ&F(*PBhB!N!BM!,3!-J%*L!4`Ffjd!*!&0`# d!%B"#BJ&F'4bEh!!N!C,!,3!@J%*L!9LC'0SD`#3"Pm!Y!"Z!3Q)"R9ZF(*[G!# 3"A-!Y!##!3Q)"Q*NGQ9bF`#3"BF!Y!#@!3Q)"@*NE'9Z!*!'Q`#d!+S"#BJ'G(4 XCAK`!*!&V`#d!,i"#BJ&CR*KCh-!N!E$!,3!dJ%*L!4QFQ9P!*!&e`#d!1B"#BJ %CQ&TE!#3"3m!#J!H!+U)%9"KBfYPG(-J8Q9MC@PfC@3k!*!')`!+!$)!USJ08'& MDf9dFb"6C@jd1J#3"MF!#J"'!+U)%&"KBfYPG(-J4(*[F("PC$S!N!9,!!S!@J# UL!j#B@3J3fKPBfYcG@ec1J#3"9m!#J"Z!+U)&&9ZD'&ZC'aPC#"3FQpdEf0[E(- k!*!&F`!+!))!USJ03Q&N)&CPFR0TEfjc1J#3"SF!#J#@!+U)$%*KC#"-C@jRG'K c1J#3"CX!#J#U!+U)$&486#"&H("TFQ9N1J#3"Dm!#J#q!+U)#NCbB@GYC@jdFcS !N!A$!!S!dJ#UL!e'FQ9P)&"KBfYPG(-k!*!'e`!+!1B!USJ36'PcG'9Z)%CKD@a eFQ9c1J#3!r!!$J#3"3S!#J!C!%D)"8a[Bf&X!*!'#J"1!"N!LSJ(4QpbC@PREJ# 3"JS!NJ!C!-k)"%K[Fh3!N!8+!0B!'3%8L!G)B@jNE'9b!*!'#J%F!"N"@)J#3fi !N!8H!!S!,3"'L!#3"Ki!6J!Y!)U)!*!'(J#5!#d!cSJ!N!BH!0B!,3%8L!#3"Ki "(!!Y!9L)!*!'-J!+!%%!4SJ!N!Bb!%i!33#+L!#3"M)!NJ""!-k)!*!'-J$@!%% "&)J!N!Bb!4`!33&BL!#3""8!@J",!33"U3!"!3#3#"%!N!39!'3!C!$A!C!!!!) "!*!)0!#3""m!C!"p!28"G`!$!*!*-`P&FR*[FL"#EhJJ!*!$*`"N!&!![J'N!!% "!*!)24&'Eh*PD@GZ)%4TFQ9MG'pbH5!!N!-9!*!&Q!&Q!!%!N!M`A`#3"#F!C!" p!28"G`!"!3#3#$i46Q&YC5"'Eh*PD@GZ)%K[Fh3J!*!$)`!b!'3"*`&h!!3"!*! )6Je*8#"6G'&dDA0dD@0c)!#3!b-!C!",!2S"U3!%!3#3#&i19843)&0dBA4TFh4 TBh-!N!0,!3#3#Iq3"!4'D@aP"NGPG#k3!`#3"!C3GA3ZN!-!N!3*8f9bGQ9b,T! $!*!%"8&LEh*d!*!%"&&eDA3!N!L$!3%!N!MrN!3)8f9dG'PZCh-+390$58NJ6@p NC3#3"!T*68&(45"0Ef4P!*!%#Np$9%98)%e[C'8!N!3168&$58j86e0))%e[C'8 !N!318Q9YEh4P)%K[Fh3ZN!-!N!368Q9YEh4P)%4TFQ9MG'pbH5k3!`#3#'S"!J# 3#2q3"!46D'ph$P9%8#"6G'&dDA0dD@0c!*!%%dej)%PZG'9bEQ9d)%&NC(*PFh- !N!386ANJ3A"`E'9dB@aV)%&NC(*PFh-!N!3059!J8h4KG'PcG'PMF`#3"`%!N!9 rq"rq3!J3!Nr)%r*3+"3+8#J8#P!S&!T2b"2b3!J3!N(S%(T!$r!#IrJIrL!3#!3 J%!J%2r!2r!#3#(rN"rj!+!3#AlrpqN!S"!*2T!Ab3#!%!PmJ"2T!)#3#Ak!9qN! rr!*2S"Ab3#!N!RrJ"ri!N!arq"rqIrrrrRrrrrjrrrrqIrrrrRrrrrjrrrrqIrr rrRrrrrjrrrrqIrrrrMrrrr`rrrrm2rrrr"rrrrJIrrriIrrrrRrrrrjrrrrqIrr rrRrrrrjrrrrqIrrrrRrrrrjrrrrqIrrrrRrrrrjrrrrqIq!(rJ#3#L+f6[S2&%l k%AK1qKK`6[S4l%lk%a!JE`!%,Tp1d%j@rmC)j`mB*Qi!#%+R,`ZT&b"I,9$rj%+ R,`ZT&b"I+#J!"#m,2c`!"dKZrpa)E[rB5'lrd+Q03Hlrb%2Zrp!Jf5$C5'lrb$m m!!%r2!!"U+P)E[r)U+0#Tbm,U4FJAbKS!!JJ$'B%B!!!d%KZrrLSLc!Zrp$3E[r i8N!q!%*R,blrj+PJ1Kp#Tbm,U4FJAceS!"MraN*'B!!!Q#!'d%8J9,"3E34J!!# 5-#lrdP4!,`"#Cbm%U@!b(b!Ijd'3!%%r!$m(U*-J9#!'d%A"r!!')(!!)#m3U)3 J9#!'d%A"r!!'%M!!*'Fb5'lrl$!Zrp*53$m!)!H3!'lrq$m!-#lreP0!2`!`,[r kd%Fb,[rqdN!r!DLR5'lrl+LN-#lrq0"(-LlrqY*!-#lrrY""2J"54QN)['lraQm !rf4-haM`6PiZRdje6PErlLmZ!!Sr,J!)5'lrrNKZrrT)E[rbUBdpE[rbrqipE[r drr#SRMmZrr!r,[rZU*-`,[rZX'lrpQ`D3QFr2!!#U*3`,[rZ9%!p32rZ3QG#CkL 5B0a1AL"IA%p1d%j@rpT)j`%B,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8KZrr+SSd+ R,bi!#UNA)&mSD!!))!aQ"'!!!**)E[rQU)Y#TkMB*Pm[#kKk5'lrmUKl3QFJ9%K S!!5SM$iI-#lrq*!!E[rdX%G[1M!ZrrL3!'lrp*!!4dM!JI`!!Y"Zrr3r!$!Zrr* @3$)ZrrD5E[rb5-'$r!!#dN!r!DL6)&4)D!!%U)4J)#!8@)"5J#m!)&33+!!%!N! !rdM!,`")E[rb2c`!!DR1,`ZSH5m,U0P-haL!6PiJAea26Y"19[r-51F2'%+RU0J SAd+RU0JQAd+R,bi!#+NA,KmJ4bS3,bi!#$mm!!G)E[rF5'lrf%KZrp#TM5"(1#J !&%*R,`@TB$`I5'lriUL,)!53!%Bb,[rNdQlriM3ZrqM83F(#28$rqNKZrr)r,[r 52blrd$mZrpBr,[r8U+G)E[rb2c`!!6mm!!'SU8KZrr*#CcmZrrS[$+M[,`ZSHL" 85'J!!UKl,bi!#%kkr1![#kKj)%Fa4J!8,`ZSf5m-U0P-haM`6PiZRdje6PB!!#m -+'i!#JaZ!"8!#&I!CMB[!%*R,`bTB$)I)"p)jm!!3QF[$+PL0"p-h`!$Y%&H`F! "Ca)[$%*R,`bTB$!I8N!r!+PMB$i-EJ!8!!KA`'Bd,`"#Cbm-U@!b(b!I51I!!%* R,`bTB63I60m!!l4"AF(!!@F3,`a#Cbm-U@!`(e0!2`#TBb"8,bJ!"%kkrUSSAdj H)&pF6dl36PB!!#m-+'i!#JaZ!"B!#'BJ,`a#Cbm-U@"#Tb"8,bJ!"+NA)&m`(j! !D!!B2`#TBf!Q$'i!&`!)CKi[$%*R,`bTB%+R)&3[+!!%U4FJAc!S!"M3Acm!U@0 #Cbm-U@"#Cbm-U@)`(l"IE!`[$%*R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%`(l"IE`S [$%*R,`bTBDPM)&3[+!!%6VVq"#KI6PiJAea26Y"19[rd51F$!%KZrrLTFN*R,bl rq#mZ!!K)E[rmU@`q(dT(Ch``"`4!!"4R&&0!CbC63'Fi8d"R3!4!!'TR4Q"Q3QF [,[rm,blrq%(krR)[#+PS2"pJ8%*R,blrr#mZrrK"q[jF,`LTD$`IB$S[,[rm2`G 1Z[lQB#i[,[rm2`G1Z[lDB#*#CbmZrr`[,[ri3UHTD%TIC`J[,J!)6VVpA'!'2c` !!kR)60m!`%jH,Tp1G8j@rp4)j`mB3UHSf#KI3UHSf#CI3UF[,J!)U4FZ(b"(+LJ !"#mZ!!Jr2!!(5'lrj%KZrq")E[rBUBdJ4cJS!"C#Cbm&U@!m(b!%N!"'jd!p32r k5'lrmMmZrpSr,[rB2blrhMmZrpbSTdKZrr)r2!!"2c`!!DLT5'lrmMmZrrT#Cbm -U1m[#kKk)&4)D!!#U(X[,J!)6VVk1Lm,U(NJ4c&'!"B[#kMC,`bSf8cI'2"1ALk I6R919J!!,``SEJ!+$'i!&3!)9m"Q0Lm!3QF[$+PJ-KmJ(dMR`!"#Cbm-U@)d(dc I!!1d39l"`!&R%Lm-3QF[$+PJ-"p53$m!U@0J2JaZ!"3!#&I!CM3[!%*R,`bTB$) I)"p)jm!!3QF[$+PK0"p-h`!$Y%&G`F!"Ca![$%*R,`bTB$!I8d!r!+PM)&3[+!! %6VVq[#KI6PiJAea26Y"19J!!,``SEJ!+$'i!&J!)CK)[$%*R,`bTB$!I@d!r!+P MB"J-EJ!A!!KQ%#m-3QF[$+PJ-"pD3$m!U@0#Cbm-U@"#Cbm-U@)`(l"IE!`[$%* R,`bTBUPMB"a#Cbm-U@"#Cbm-U@%`(l"IE`S[$%*R,`bTBDPM)&3[+!!%6VVq-LK I6PiJAea26Y"19[rd51F$!%KZrrLTFN*R,blrq#mZ!!K)E[rmU@`q(dT(CeS`"`4 !!"4R&&0!Ca"63'FL8d"R(J4!!'TR*'"%3QF[,[rm,blrq%(krSi[#+PS2"pJ,Lm Zrr`r"dkkraKJ)N*R,blrr#mZrrK#TkPS5PpR##mZ!!K1Z[fXB!Br2!!$UFK-h`$ !6PiZRdje6PErkLm-,bi!#MmZ!!K)E[rq5'lrqNKZrr+TM8+RU0JSAbm-U(T)E[r bU(Y)E[rbU+%[,J!+6VVi-#m-U(N[$+MC+&p1AL"IA%p1d%j@rqK)j`-)3UF[,J! )U4FJAbKS!!K#"L"8-""63$e!rqK#4f!8)&3J"m(m!!B5-!!NC`*m!9*(D3DqE[r SEqB[,J!)2c`!!8KZrrC)E[rb5'lrkUQ0%!CR#LmZrr*#CkPGB!S[,[rb2c`!rkP G60m3`%jH,Tp1G8j@rmT)j`m)3UF[,J!1U4FJAbKS!!JJ$'B+2c`!!kR)B!!$1NK Zrr+SLc!Zrr63E[rb-Llrq0*!1!%[,J!12c`!"dKZrq*)E[rH5'lreUQ0HJ*#Cbm Z!!UTBMeIrmj#CbmZ!!UTB$iI3QHTFa!IC`!#j+Qd5'lrr+Pb-#lreP*!X'lrr'm !!*4+4fm1,bi!#L!(8d!r!+PMB!*Jc&0(5)8`"@Fm8d"R5&0!C`*J6L"8)!I"r!! '%M!!*'F%HJ&J!N)&)&3J"m(m!!B5-!!N#J%!!5"8)!I"r!!'%B%!*'!F)&3J"m( m!!B4[!!"!#4J$#"8)!I"r!!'3M!!*$e(rmS[,J!16VVj&R!%,`")E[r56VSBbQ! !re*J%M!ZrpC53$)Zrrb53%M"Jm3m!6!Z!!K63,"'E!!![,jZrmjX$LmZ!!SJ"e* !2`#TBf!%B!$r'P*(5)8`"@G18d"RB&0!C`*JE#"8-#i!#&0!d%I"r!!'%M!!*'F %HJ&J!N)&)&3`,J!)8d$34m(m!!B5-!!N#J%!!5"8-#i!#&0!d%I"r!!'%B%!*'! S)&3`,J!)8d$34m(m!!B4[!!"!#4J%L"8-#i!#&0!d%I"r!!'3M!!*$!Z!!K630" (28$rbLmZ!!j1Z[JmF!3[!%KZrp*1ZKI`B!$qH#!'d%FJ9,"3E34J!2jU5)8`"@F !!)C63'F!!1*63'F%B!!"0#"8)!E34m(m!!B5-!!NC`4k!@!#3J8J9#!'d%I"r!! '%M!!*!S"!!%J9#!'d%I"r!!'%B%!*$!ZrpK53$e!rq``,[r@8N!L"X2%dN!p3Ir U-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST'!!!,iJ9#!'d%I"r!! '%M!!*!S"!!&R6#"8)!E34m(m!!B4[!!"!#3`,[rB8N!p32rX-#lreP*!)JE$a0* !28(rkM!Zrpa63$e!rr!`,[r@8N!L"P*"`m653$e"rqj)E[rUU+4J@L"8)!E34m( m!!B5-!!NCdSJ9#!'d%I"r!!'3M!!*$!ZrpK53$e!rq``,[r@8N!L"X2%dN!p3Ir U-#lrh&0!28$rm$!ZrpC53#)'8N($a0*!28(rlNKZrqUST#!'d%Fp32r+B!$p&Lm Z!!j1Z[`Z60m3m%jH)&rHr!!+6Y"19[rX51F$##"Z!!K$l[ri5K!Lf#,B+'i!%M` Z!"!J9$!38d!p32rX3NGJG#"8)!I"r!!'%M!!*'GL)&3J"m(m!!C#-!!N[%GI`'j 3)JH54V*Z!!jG`F!"Cd*)E[rZ-#lrqP*!2`!`,[ri8N!L"j*'`qi!$0*!2`%`,[r q8d!r!$!ZrrK53#)(NNC53F2Z!!c53$m"U+G)E[rZU+454fN'[Qlrl'q'60m3`%j H)&rHr!!16Y"19[r+51F2'#CZ!!Si,J!)3UF[,J!1U4FJAbKS!!JJ$'B+2c`!!kR )B!!#5%KZrr+SLc!Zrr63E[rb-Llrq0*!2!%[,J!12c`!"dKZrq*)E[rH5'lreUQ 03QF[#kPL29rrd%*R,`ZTB$iI,``r"cm%2`C)E[r@6VVqe%*RUA-3(fF!!CDTY%K ZrrbTFM!ZrpC53,"Zrra[D%*R,`ZTB$iI5NG[-#"8)!G63-(m!!B5-!!N#J%!!@F 3,``r"cm%2`C)E[r@6VVqKLm,)!G63$m!U@0J!Q#N)&3J"e0!`I`!"K'm!!%!*#m Z!!j1Z[85F!3[!%KZrp*1ZK6'B!$rIQ!5-#lreP*!-Llrr**!5-'$aMS")!463," &E(C#Cbm,U@!q(ljZrp"X0L"8)!G53#)%8d(53-2m!!B3-"!N#J!!!@F3,``r"cm %2`C)E[r@6VVpr#m,)!G53$m!U@0J"'!!raSJ9#!(8N!L"&0"dN$$r!!'%E`!!4! N,bi!$Nkkp)"`"#m!5'lrdNkk&$4J!2lX3QF[#kPJ2KmJ"G"()&5`8'd%B!$qeL" 8)!A34m(m!!B5-!!N#J%!!@GD,``r"cm%2`C)E[r@6VVpK#"8)!A34m(m!!B4[!! "!#4)E[rU-#lrf&*!2`!`,[r@8N!L"F2'dN!r!6!Zrpa63$m!-#lreP*!)J953F2 'dN!r!DLR5'lrkULNB!$qC#mZ!!ir2!!"5'lriNKZrpj)E[r@UBdJ9$!38d!p32r +3Qlrc'!J)&3`,[r-`I`!"K)`!#4R#LmZrpj#CkPGB"T5E[r-D3S`,[r-X'lrbQr @,blrhMmm!2qTA8cI'2"1AL"Ih[`!#Nl36PEqJNMR$aJJEJ!-3qlrm%S3)YJLf#, B)YJq,J!55NGQ$L"m!!!$8LK32L`!6Q"L5NG[-N+R6VS6(L"I+'J!!L!-9X"R$Vj X!%K@`F!"C`3S9'$X)!aQ#$mmrmJ`(kR*2L`!6Q!X3UG1ZK,X)&mSD!!#)!a@`'F 1[Q`!6PE"`!&R"#K8B1`J$'B)2ccrh6!IUFNpE!!8rST#Th!'`HlqLY"m!#C)`#m !6VS5BLCI3QFqZ!)J5Pp@`'B))JYA`B!"C`Jr2!!C-"qTb5"6-8F!!L"6@)K$l!! XF!FJf90!E[T#E[k-3N3pE[k+rS3pI!!"rqaJ!!%N3LlrXMe(rkj#,[kB3HlqQ#e )rkSpE[rXrl4#Cd(ZrjJ[#%*R6VS54$`I5NCR"Mm'-"qTb3aZrrm!%'G!3J8`,J! 38d!p32k#3QlrlQ!L-#lrlZ9!3IB!m%2ZrlK`!l%*9XMrr'B%HJ&J&&*ZrqjT#M! Zrqk`E[k#Ep4J!RS"%!9R)%UZ!!K@`F!"CaC#Cd(ZrjJ[##mZ!!K1ZZpZ'Km+"3! "%!9RFN+R%#lqQ!*!!2p53%M!,`"1ZK&H,9rqP%*R2VJ#)%TI9X"Q#NUZrT4A`B! "C`Jr2!!C-"qTb8(ZrTJ[##"ZrT3[%"!ZrTJ#3!$r8N")`#m!6VS43#"6)!6"r!! ')DlqP!!J)&-J"-(m!!C#-!!N8N4J"&*ZrSa5E[rXD3``,[rXX'lqK'm!rY3`,[k +N!"ZrS`J8c#!,`XJ8h!'`G$3I!!Q5-![!%kk%1"#Ccki!L!m(dT'C`Br"M!IUFN Y5`!860mBm%jH)&rHr!!-6Y"19[rk51F$##KZ!!JJ$'B#B'!J9$!38d!p32rk3NC J0#"8)!E"r!!'5V!!)'FL)&3J"X(m!!B[-!!J6VS3G%*R2VJ#)$iI5NGR"Mm(-"q Tb9*'D3DmE[rkEmB[$%kk%&*#Ccki!L!q(dT(C`Br"c!IUFP-ha$!6PiZRdje6PE rpNMR$`JSEJ!))!aQ"'!!!3a#4#"8-""63$e!rrC#4f!!!0SJ9#!(`I`!"K)`!#3 +!3!"Cc)J9#!(`I`!"Lm`!#"1ZJrZ3QFqZ!)J1Kp+4@F'2`8`(kR*)&3J"m(m!!C #X!!JB!!!NP*%5NGH`'m!!)JJ9#)(8d($r!!'5V!3)&I"`!&RFL!(8d!m!'!5)&3 J"X(m!!C+X!!JC`*J"P0'5NCXkNT'9m"Q$L"85UJ!)&I"`!&R!Rcr)&3J"m(m!!B L9#)'8N($r!!')l!!)"!J)&3J"P*!`I`!"K'm!!%!*#"8)!I"r!!'3V!!)#"8)!I "r!!'3M!!*&*(D3LqE[rfE`$r)L"8-)3[$#"8F!E"d0"m!#C)`#m!6VS2)NcI%2" 1ALkI6R919[rf51F('#KZ!!Km!4!'C`!!kN)')&3`%&9!28$rpN*(B!!!c%*R)&3 J"m(m!!BJF!!J)""5J#m!)&3J"e*!`I`!"L"`!#!J%&+!,`!J9#!(`I`!"L"`!#! J8"!3!N!!rcm!)&3J"e*!`I`!"L"`!#!J8"!3!N!!rcm!6VS2!JaI!!&QCR`")&3 J"m(m!!BQF!!J)&3J"m(m!!BD-!!N)&3J"e*!`I`!"L*8)JI$r!!')l!!)"!J)&3 J"e*!`I`!"L*8)JI$r!!'%l!!*"!N)&3J"e*!`I`!"L',!#!J9#!(8N$"r!!'%B8 !*&*(D3LqE[rfE`$r-'!!ra4-haMJ6PiZRdje6PErr#!Z!!KAJ#e!rr`JE[rm%"" )J'XBX(`!$fi53IS!*M)!jNP%33%`%2m+2!!%CJJGI!!"!!aJ"%)Z!!a1ALkI6R8 ""Nj@rpK)j`mB3UF[,J!)U4FQAbK6,#X!"#iV!!JpD`!Brq4+KfB),`a#CkPPB#3 J4b"3-"#`E[rNE4)[$#"()&!`%*!!E[rN2`#TC@!',`a#CkPP,`a#CkPM3QF[$+P L$&m!!@`+,``r2!$rU9eJ"Lm-3QHTA5m'3QHTBdU(CJ`["Mmm!2qTA@!!!)a#4#" ()&!`%&0!28$rf%*ZrpaJ*%*R)%FJ8$!Zrpc"r!!')(!!)#m3U)`k(lK&E!)i"9* ZrpaT#M!Zrpb`E[rBEp)[,J!)2c`!"dKZrrj)E[rk5'lrmUQ0-#lrq*!!E[rd@%! k!,T%E3S["Mmm!2qTA@!B,`BJ"*!!48M!JI`!#&4!2`#TC5m'3QHTA5mZ!!Jr2!! "5'lrrNKZrrT)E[rbUBd[,[rk2c`!rkPG3QX!&%*V!"C-haM`6PiZRdje6PErcNM R$aJQEJ!3+Li!$%*R2c`!J%KZrr#TF"!IC`!"%N+R,`ZT&bKI3QF[,[rbU'T+AfF Q2A`!H2rX2A`!P[rZ3QF[,[rX,blrmNkk$*T+AfB%I!&J"N)'B!*m!4!'C`!!d#m X!!K1Z[YJ3UG#CbmZrr+SDcmX!"T)E!!F,b`!%%kkq0iTA`!),b`!#%kkr0i[#dk krL4#4%+R6VS-%#"I,LJ!!NU(Ca4#Cbm(6VVpa"!IC`*54#"(,K"Jk!a%!!&[(#m ,2c`!"NKZrqK)E[rN5'lrh+Q0,blrj%*RU9d[#cmm!!9)E[rS5'lrj%KZrpbTM5m Zrq4#CkPG3UG1ZJZZ)&mTD!!#!!`JE!!-,`K#CbmZrr+SDc!I)&q`D!!'C`SJE!! -+9!!$'$H5'X!%+NS)%8-8!!"CQB[#cmm!!G)E[rS5'lrj%KZrpbTM5"&,@J!#[r B5'lrf+Ka3QF[,[rB5'lrh+LY%"pR0%+R)%8`+!!15-![!#mm!!!#!+KB5TpR#L" Z!!J`[!$rB!JJEJ!)-,`!"aem!!%!&'!!!2JJ46!3DaL`I!!(EK*"qJ%B-J$Q584 "!6!3r`Sm!!4A`'B!!0![!%+R)%8b+!!15-%[!5m!3UF[2!!!#!![2!!!!3#S@b) I)"m[!DKB)KmJ(dU"9m(!!@F!!*S[!%+R)%8[+!!#,c`!N!2rU&JL(b!I$%%!$9I "`!&RH#m,2c`!!8KZrqK)E[rN5'lrh+Q0-#lriY"Zrpj)`)(m!!)p32rD-#lri0" Zrpa)`)(m!!)p32rB3QF[,[rN,blrf+PQ-"pV',"m!2pZ%N(k!&Sb!1C*4%%"-"$ r#M`!"'B'3Li!&'!8)'i!#$#m!!%GI!!"!"4J"%)Z!"4-haM`6PiJAplm!!a1d%! !N"i"!#K19[lm51F2'#"Z!!j$l[r`5K!Lf#,B)YJLf$JZ!")YEJ!8riJp42q5$%6 rrfmN)!463$e!r[a#4f!5)!IP3#)(j8%YYK$`!*454fN'[Qlqr'rS3Ulr#N+R2cc `Ad+RF2m[!+Pm+&m[$$mZ!"Sr,J!B3QHT'dKZra5SG#m-U(-[$%(ZrhJ[#+NB,`` r2!!)5'lrk%KZrq4)E[rFUBe#Tbm-5'lrh%Kk#1JI2!!"3QG#Cd*R2c`!%%+RU93 YArpi,``r2!!,5'lrk%KZrq4)E[rFUBd[$$mm!!G)E[rS5'lrj%KZrp5TM8+R,`a )E[rF5(S)RKmm!!&#Cd*R-#lrfNM!JI`!#$m!2c`!%%+RU93YArpm,``r2!!-5'l rk%KZrq4)E[rFUBe#Tc!Z!!K)`#m!F!%[!+KB5TpQ##mZrq5T@'!H3UF`,J!)5-! [!(!#,`#S@%UICJS[,[rN2c`!rkPG3UF`,J!)5-![!(!%,`#S@%UICJ4#"@!#HJ% [,[rN5)8r"DPM3QHSKcmk#!+SL$mm!!'SL6mm!!bSLNKZrfUSLbm-2c`!"dKZrqK )E[rN5'lrh+Q0-#lri*!!E[rF9d!b,[pXdQlrDM3Zrh$838M!JF*53$e!rj!!3UG 1ZJJ1)&mYD!!#ri3JE[q%5T!!CQ`[$$mm!!C)E[rS5'lrj%KZrpbTM5mZrq5T@%* R,blrK%kkqD!3(fFH3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41Z[4b,9rrJ'!L3UlrJ#m -2c`!"8KZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA@!!!HK#4d+R6VS(LL"I,#J!!NU 'Ca4#Cbm'6VVj2K!IC`*54b"',""Jk%T(CN4#V[q!,``r2!!&5'lrk%KZrq4)E[r FUBd[,[rN2c`!rkPG,``r2!!'5'lrk%KZrq4)E[rFUBd[,[rN2c`!rkPGB!!"HJa (!!&QD%+R6VS('#"I,@J!![q%3QF[,[q%6VVic"!I#J!!!@F+)'lrK#e3ri4Jj%+ R)'lrK$mS!!Br"%KZrr![,J!86VVcN!!YArq!,``r2!!'5'lrk%KZrq4)E[rFUBd [,[rN2c`!rkPGB!!"$%+R6VS'ZL"I*QJ!!L"m!!!#&$i3)!G%3$i!)!Y@`'F1[QX !6PE"`!&R"#C6B1`J#fFd3UG1ZJCq)&mYD!!#ri4+V[q%9X"R(#"Zri3b+!!'XQX !5&E"`!&R#L"Zri3Y82q%B0aJ1L"m!!!$8LC33UG1ZJC#)&mYD!!#ri4+V[q%9X" R(#"Zri3b+!!'XQX!5&E"`!&R#L"Zri3Y82q%B0a+V[q%9m"R(#m!3QF[,[q%6VV hb")I)"m+!3!"J!%#3!!"CbT#Tdkk"H`JAbeS!!,rK%*R,blrK%kkpk!3(`S!!!& R#L"Zri3Y82q%B14#Tb"Zri3r+!!'2`4)E[r`,bi!&%kkmQ3YArq!5UlrJ'F),bl rJ%kkpPi[$$mm!!P)E[rS5'lrj%KZrpbTM8(kj)3Y52rN,``r2!!*2blrk#mZrq4 )E[rFUBi[$$mm!!G)E[rS5'lrj%KZrpbTM8(kkUJY52rN,``r2!!(2blrk#mZrq4 )E[rFUBi[$$mm!!4)E[rS5'lrj%KZrpbTM8(kj))Y52rN,``r2!!%2blrk#mZrq4 )E[rFUBi[$%kkpa3[$+N9,``r2!!)5'lrk%KZrq4)E[rFUBd`,[rJ8d!p32r82@l ri[rD,``r2!!,5'lrk%KZrq4)E[rFUBd`,[rL8d!p32r@2@lri2rB5'lre+LK5'l re$mm!!)r2!!#U+N`,[rDN!"ZrpB-3!!#E4C)E[r8U+&)E[r82c`!!Mmm!!+SU@$ F3IVhdLm)5'lrkUQ4-#lrkPY!CbT63'F!!BC63'F!!QT63'F!!`KA3'F!!`T63'F !!``%3!$cC`!#"'!!!c4#V[mX)'lrJ#"32@J!![m`3QG"l[mD,`K1ZJ4329rr'%T ZraKQ!!%f,blrJ%kkmc)JE[q%,9$rK%UZri4@`'FL,`"#CbmZri41Z[A!%KmJ(`S "!!(!!@F+)'lrK#e3ri4JeNUZri4Q1%+R6VS$f#"I,@J!![q%5UlrK&E!Cb)[!%* R,blrK%kkpB)5(b!I#J%!!F!"C`SJE[q%,9$rK'$@5UlrK'C#3UlrJ#m-2c`!"8K ZrqK)E[rN5'lrh+Q0,blrj$mm!2qTA5m-2c`!"NKZrqK)E[rN5'lrh+Q0,blrj$m m!2qTA@"S3UFJE[q%2bJ!"Mm%5'lrm#mZ!"41ZZrk,9rrJ#mZri"1Z[2kIJ%JE[q %,""+KQF83QF["Nkkp1`3(fF#8NFJ4L`3B1J-4`!#E"i[$$mm!!C)E[rS5'lrj%K ZrpbTM5mZrq3r2!$rU9d[$%kkp2a)E!!3U5KJ!!(8,blrJ%kkmISJE[q%,9$rK%U Zri4@`'FL,`"#CbmZri41Z[5)%KmJ(`S"!!(!!@F+)'lrK#e3ri4JeNUZri4Q+N+ R6VS#S#"I,@J!![q%3QF[,[q%6VVd9"!I#J!!!@F+)'lrK#e3ri4Jj%+R)'lrK$m S!!Br"%KZrr![,J!86VV['#eIri![,[q!6VVc'#m-6VVdANKX!"#T+'!!!6B3"@F 3,``[,[pi2blrN!"1ZZK-B$3[$$mm!!a)E[rS5'lrj%KZrpbTM5mZrq3r2!!"U@- [$#mZrhJr,[q3!%kkk"i[,[rN3QHTBf!!!1S3"@F5,``[,[pi2blrN!"1ZZJ!B!! !e%+RUA8J(j!!V[m+,`"#Tbki![!L(b!IXS"[9NKZr`+TFM!Zr`53!'lr#'S#4%! -3!!$AF"X(M)Zr`+5E[m'DJ*%33a"!!0G`F!"C`JpI!!"rqTJ1LeZr`,r"Lm-,bl rH$mZrj!!6VVVY%+RUA8YArm+B'*)E[m'UA)[$#mZrhJr,[q3!%kkkjC#TkPe,9r r#Q"%,`a1ZZ1-B$`[$%kkjJjJ0#m-2c`!$%KZrqK)E[rN5'lrh+Q0%!9R#LmZrq4 #CkPMB!S[,[rN2c`!!DPM)!8+!!!"'J!`,[rUDaL`I!!(EK*"qJ#S-J$Q584"!6! 3r`Sm!!4Q!2a`5UlrK'G!3UG1ZJ$`)&mQD!!#)!Y@`'F@)'lrK$)V!%LbD!!'9X( !!@F%*P0Jj#!,Ca3YI!!!!K6qrM!V!%j%3#"Zr[i`J%UZri"R(#mZri"1Z[!f$'i !!IrUC``[,[q!6VV[UN+Zri![,[piU98[,[pmU98[$+Q$,blr&+Kc,@lrJ!!F60m Bm%jH)&rHr!!86Y!!#J#3"#*I)"qJ6#k!6R&`!#m*-F!#)%jeF!"JpL*I)"qK)Lk )6[VrkL*I)&qJ)dlkrq!LAb!I)&qJ*%lkrp3L(b!I)PmJAk!Z)N&1q[r%)"mLAb" I,`#J1b+!6R8[I!!!!`J!"%je,h`!!!0@!!41G5*I%"mJAfB%S!aJ!U3-2S"1d5* I)&qJ&ck!6Y&d!L"I2`)[#+hTG!"1q[rd0$`!#Nlk!!Bd2!!-)&mr!Lm)VHd!!!1 B!!!$U!!!#m)!!!1)!*!$)!4N2c`!!DR`(CJr2!!"UI!&)$mm!!'Tm#j82c`!!DR `,P3r2!!"UI!Z9$mm!!'Tm",m2c`!!DR`%c3r2!!"UI!@EMmm!!'Tm#jL2c`!!DR `,&Sr2!!"UI!+MMmm!!'Tm!ZN2c`!!DR`"D3r2!!"UI!&"$mm!!'Tm!@82c`!!DR `"8`r2!!"UI!1*Mmm!!'Tm!fi2c`!!DR`&2`r2!!"UI!(IMmm!!'Tm"2X2c`!!DR `&+!r2!!"UI!'Z$mm!!'Tm!Cq2c`!!DR`"Y3r2!!"UI!84$mm!!'Tm#%B2c`!!DR `"b!r2!!"UI!+lMmm!!'Tm#Ab2c`!!DR`)83r2!!"UI!M6Mmm!!'Tm#SU2c`!!DR `*S)r2!!"UI!QB$mm!!'Tm#G12c`!!DR`*N`r2!!"UI!RCMmm!!'Tm#Gf2c`!!DR `+!3r2!!"UI!IX$mm!!'Tm#"i2c`!!DR`"-3r2!!"UI!0D$mm!!'Tm!8D2c`!!DR `#R)r2!!"UI!0)Mmm!!'Tm!812c`!!DR`"2Jr2!!"UI!6IMmm!!'Tm!J82c`!!DR `"FSr2!!"UI!'%Mmm!!'Tm!BN2c`!!DR`"J!r2!!"UI!'@Mmm!!'Tm!Bf2c`!!DR `"EJr2!!"UI!&h$mm!!'Tm!C)2c`!!DR`"Q`r2!!"UI!&lMmm!!'Tm#dL2c`!!DR `",Sr2!!"UI!(S$mm!!'Tm!IL2c`!!DR`"e`r2!!"UI!,a$mm!!'Tm!ZX2c`!!DR `#!Sr2!!"UI!CN!!r2!!"UI!ZA$mm!!'Tm"H82c`!!DR`'!ir2!!"UI!@d$mm!!' Tm"DS2c`!!DR`&V`r2!!"UI!DRMmm!!'Tm"TU2c`!!DR`'[Jr2!!"UI!D3$mm!!' Tm"R!2c`!!DR`#8`r2!!"UI!*"Mmm!!'Tm!S'2c`!!DR`#%ir2!!"UI!)XMmm!!' Tm"8k2c`!!DR`&9ir2!!"UI!$&$mm!!+Tm!Mk2c`!!UR`!d)r2!!#UI!$Z$mm!!+ Tm",-2c`!!UR`!!!r2!!$UI!$HMmm!!1Tm!&'2c`!!kR`!!Jr2!!$UI!#9Mmm!!1 Tm!!!2c`!"+R`!*3r2!!&UI!!,$mm!!DTm!%Z2c`!"UR`!@Sr2!!'UI!!'$mm!!D Tm!"H2c`!"UR`!!!r2!!'UI!!F$mm!!DTm!'f2c`!"UR`"%Jr2!!'UI!$0$mm!!D Tm!(d2c`!"UR`!!!ZLJ#3!eT19[rQ,`a#CbmZ!!LSDMYIrp"#CbmZ!!LSDcYIrp) `,Ir38d"R&J4!!2pR,P0!C`!"%&0!C`!"fQ!!!X3[,Ir`2bhrdNKYrX#T4N*R5'h q`+Qf1errfQ!!!UB`,Ir58d"R'&0!Ca463'GL8d"R!!$#8d"R!!$#B!!#KLmYrr3 r2!!$U6T+VIUSCL*#TbmYqN4"l3,k,`Jr2"J!5(S#YN+R6VS@,LYIqUK1ZKB5$'d !!Ir5CJJlI!!+qUCJ"MYm!![kTLmYqUK1ZKAJB("#CdkY!`S3(fGB'h`!!Ikr3UF [,IT%3Hd#mLm)2c`B!%Kk!Pa#Tdkk&G`VArUX,bhrp$mm!!-I2!!"U88[,Iri3QH T1LmYrr3r2!!"U6S[,Ird2c`!!UNk,bhrp$mm!!1T1Q!-6Ud$%Q!''h`!!IrYB!! "`$!Yrp*63'X!!-!-3!!&EJ!!Z10)-$X!"Nll!*!$$J!B!#)!,!!d!&3lI!!"qV* J!!#B1h`!![UbB!!!MMYm!!6kXQ!!!)3lI!!&qV*JHN*R5'hq`%kk'LB3(fF33Hh m[%2YrX"`3#$C8d"ZqQ"D3UFr2!!m3UG`rbm!UA`SAbm-2c`!!dKZrqj)E[rk5'l rjUQ0,blrqNKYrEbTMbm-2c`!!d*R2c`qJ+Pq3UG)E[rdUC%-EJ!"rr4Q#LmZrrT )EIfmUC!!,`bTJdkY!`*J!!$`-#hrdP0!Ca*63'F@8d"R1P0!C`!!eQ!!!0C1V31 5B!!!cN+R3UG1ZL8Z5'he0%kk+KT#TdKk!1j1ZL`53Hhe0#m)6VS,&'!!!+C#CdK ZrrC)E[ri6VS)L%TICJ!!M%(Yp64$qJ#f)0NJf6#4-#lrq%M!,`")EIl!6VS&LNK YrX")EI8d%#he0!*!!2p53$m!6VS'%%Kk!(T)EI8d%#he0!*!!2p53$m!6VS&q$! ZrrC)`#m!5'hq`%kk"8T)EIl!5'he0"!Yp63#3!$r8N!r!%kk"G"#TdKk!#*1ZLY `3Hhe0#m)6VS+FQ!%6VSSLN*RU6JSAdjH,Tp1G490H5""F("XCA4KE'XJB@4NFQ9 cFcS),#"1Ef4P1L!!#8jPG(G[FQXk)"40H5"*ER4PFQjPG#"KC'4bCA0c1J!'8f9 bGQ9b!!C%Ee9cCA)!6PErqUQd3QFr22rr5'hrh+P`%"pR!!$H3QG)EIrFUAm3(fG 13QG)EIrF5'lrr%KZrrUTJ"!I#J!!!@Ff$'d!!IrFCK*#CdKYrpa1ZJM3%"m+!!! "B"J-E3!)rpaQ%%*R5'hrh%kk#3B3(`S!!!&J!!#1-#hrh&0!C`T93'GZ98"RDQ" k3QF[,IrQ5'hre+NX1errf$!YrpK63'F58d"R(&0!Cc463'FJ8d"R,'"33UF[,Ir QU6e1Z[[ZB%*)EIrF,bhre+QcB$B[,Ir8,bhrjNKYrm#T*@!Q3UHT*#!Yrp5`RfF ',bhre+NIB")r2!!#UFKJ#NTYrpaQ"%kk%QB3,IrYC`$r!%jH6R91ZJ2!6PB!!#a I6P8!!*rY!""1ZJ2#6Ud$5N(Y!dS[#+Ra6VVqdNkk*U"1ZKF!6VS$XNjG6VS$RNj e6Pj1G5*I)"qJ6#k!6R&`!#m*-F!#)%jeF!"JpL*I)&qJ,8lkrqa+1!+1DLSJH!% `3rJ"&#!)N!#4G!b`JQ8@)P%Kb!%8)S"#%5*i!USLL###dDN!$%jeS'01G5*I)"q K(Lk)6[VrV#*I)&qJ(dlkrk)LAb!IS5)ZL%lkrjDJ0Nlkrj)L(b!I)PmJAk!Z)N& 1q[q!)PmJAbm*S$-r3!!%6R8LAb"I,`QJ0$p!!!41G8j@!!!JEJ!3)Qi!$(!!%"K )3"!C5Li!#'F15Li!#QF%T$aJ%+!mB!a+,J!+C`5Q2'!#SM`+!!!"(8!!&%jH)&r Ir!#3!`a1d#*[!!3JE`!)U@mJAe"26Y!LE`!%)'m!#+PZ2d!!$#"I8%p1d#*I%"m JAfB%S!"J!U3!2S"1d5*I%"mJAfB%S!&J!U3"2S"1d5*I%"mJAfB%S!*J!U3#2S" 1d5*I%"mJAfB%S!0J!U3$2S"1d5*I%"mJAfB%S"0J!U362S"1d5*I%"mJAfB%S!K J!U3)2S"1d5*I%"mJAfB%S!PJ!U3*2S"1d5*I%"mJAfB%S!TJ!U3+2S"1d5*I%"m JAfB%S!eJ!U302S"1d5*I%"mJAfB%S"&J!U342S"1d5*I%"mJAfB%S%4J!U4%2S" 1d8j@rmj"l[r1)@i!$J!5-@i!$!!@3LJ!'N)S!"Y#U!!FS!!LEJ!)-UJ!'$e!!"* 1AL"Ihr`!N!-+6Y"19[r13HlrcM&Z!!J!'+!"28!!#NjH)&p8Mdl38F&J!P$"6PE rcN(ZrmiKEJ!)!#!aEJ!3!"JLEJ!-)9%!*%*S!#a#U!!Z5J&Q"+!#B!+J!ce!!") LEJ!-)UJ!+%jH)PrIr!#3!`T1d8j@rl""l[q`)@i!$J!5-@i!$!!@3LJ!'N*S!"b J$$e!!"*"k!!J)Qi!#$!m!"#J,NjH)PrIr!#3!`T1d8j@rm""l[r!)@i!#J!5-@i !#!!@S"8p3!!16PiLAeb26Y&19[r!3Hlr`$&Z!!J!&L&Z!!S!%U!628!!$NjH)Pp FMdl46PErX%(Zrl!KEJ!1!")aEJ!-!"C#+!!D3QJ!(+!-3qJ!)#"Z!!J`2!!3S#j "l[q`S!dp3!!56PiLAprm!*!$#Nl46PErcN(ZrmiaEJ!1!"JaEJ!-!#`KEJ!)!#k J4$e!!""1AL*I8)p1dA3")&mr!Lm)VHSJE`!%)#m!#%*RUHiJAe"26Y"#V`!33IS !###[!!41G3#3"%je)&p1A5m)6VS"qNje)&p193!!6Y"19J!!,&p)jq$J-#m!(#" [!"j$l`!L0!$P5Y,#3N&5L'!3*'K3DdN*J!K$D8FVrr&()rqiJE`!H%)&$l`! L-#m!(19)dX!M,`!B,dN!'%cI"`FZAdje6Pj1G59I3d&8)*!$6PB!!#aI)Km`(b* I)&m[!8MR%#"63'dU3N%5'%*#&"'d3'dH0J(@3K,$*%R8`p,#P%"J!K8K8FVrr'! #%YK4bIrm60m%#%je6Pj1G59I58j6)*!$6PB!!#aI,`![!6![!"$"l`!1-Lm!$-2 [!",338K!3N!b,`!5`Zm!$Y#",d!!%#)I)"m[9`!%@)p1G8jH6R8P59p098`d)%j @!!![!#m")#m!&#)[!""1ZJ!8,d%!&#)I)"p1ALpA!!4BMdje6PB!!#aI51Fq!#S !DJ*%J#`"DJ*%J53"5%*+3QBF0J"#3%K!C`5!`63!5%)`!i$"0!!L!N*!5%"J(#3 !*J&#J%+"H"r8JY'!dS'`Jfd%N!#$8J&4c2r`5S9U!N5!ZiCU!N5"60m!I%je6Pj 1G8P%59C06d3J6PB!!#aI)(Vq4Nl36Pj1G59I5%&-9#!J51I!`()"B!C)jm$!3N% JE`!B)Qm!&%*!%"L`'@B1B!5c#'B)8d"Uq!T"!!%I33!D,fm!%!!@60m$!eb26R9 19J!!3UhkM%)YqSC#,IU(3UhkJ%+YqRa#VIT`6Pj1GD9*6NP85%9"!!"`!'!!!)T `!@!!!)4`!Q"qF!0JHR!%B(C`"@"bF!CJER!(B'T`#'"QF!PJBR!+B&j`#f"DF!a J9R!0B&*`$Q"1F!pJ5R!3B%C`%@"#F"*J2R!6B$T`&'!fF"9J-R!@B#j`&f!UF"K J*R!CB#*`'Q!HF"YJ'R!FB"C`(@!5F"jJ$R!IB!T`)'!'F#&J!R!L(`!`H!$JX2J #VQ3!!!iJH!$J)&!J8%U3!'BN98m[2'&dF'bTR$!ICb*C6bmmBA4`E$m!UCdJ(fF -)%!I2!!M,a"#&dje-$J+B'B%-$cr3(3!FJ!8(b*I3IS!'K)`)!$H`3a#!#*R#Ja #!!&Z!N*!2S"1d3#3"!B#"JB%#!))#!3!!!J#"T!&"!D3"!i'"!!!#!#3"JT64Ne (CA4'D@aP!&024P41ZJ!f6Y&1ZJ!`6ZN!"%kk!#K1k3!)6VS!)%lT!!a1ZJ!B6ZN !%%(krm3J8+!U3IVr[#"3S%P1G8(krl*+N!"R##"35T!!CLbJ)d+R,cVrX%Kkrk# TS5!ICJB`2!"MUFP"q[q-))![!+Q53IVrJL"3)P"1G3L3!!!'#0!!"b*36R919[r k51F"##KYp5`J$&E!Ca)L,!!'XUi!#&E"`!&R"#K8B1JJ$'FN3QF[$%(Yp5S[#%k kq93q(bmZ!!LTJbm-6VViTKem!!%!$'!%3Li!$%cI%)"1ALkI6R919[rU)'i!#%2 Zrr!Lf#,B)YJLf%*R,blrqNKZrqbT,$eIrqSJ,[rXX+hf0'B1,bhf0+N@(A`!!3! -B!j#CbmZrqa1Z[pL(9m!$%jH,Tp1G8j@rp`[$#"Z!!K$l[r`)YJLf#,B)YJSE[r bZHhf0'B)(A`!!3!-B#a#Tc!Zrrj)`#m!F!%[!+KB$*m!N!-"CJJGI!!"!!aJ$%* R,`a1Z[m)(9m!$#KI6PiZRdje6PErr#m-3UG`#Lm!6VVhb#KI3T4#,!!%+@i!#!! ',`a"lI8U,`K1Z[K++&p1ALkI6R919J!!,bhf0+N9,bhf0+NI6Pj1G8j@rr)[,IB d2c`!!8KZrrj)E[rk5'lrmUQ0,blrqLmZ!!bTMbmYpM3r2!!#5'lrrNKZrrT)E[r bUBd[,[rk,bi!#+Q26VVrTNjH)&p36dl36PErmLmYpM3r2!!"5'lrrNKZrrT)E[r bUBd[,[rk5(S!0+Q2,bhf0$mm!!*)E[rq5'lrqNKZrr+TM5mZrrS[,J!)UBmr2!! #UFK1Z[p36PiZRdje'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bhf0$m m!!&)E[rq5'lrqNKZrr+TM5mZrrT)HJ"+UBm[,IBd2c`!!NKZrrj)E[rk5'lrmUQ 0,blrqLmZ!!`[,J!)5'lqmMmm!!*1Z[RB5'lqmUQ22c`!!UR)6VVqcNjH)&p36dl 3'8%JEQ9dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[lb,bhf0$mm!!&)E[rq5'lrqNK Zrr+TM5mZrrT)HJ"3UBm[,IBd2c`!!NKZrrj)E[rk5'lrmUQ0,blrqLmZ!"![,J! -,bi!#%KZr[)r2!!$6VVj8%KZr[+TMcmm!!+Tb%kkrNC1AL"Ih[`!$%l3'8%JEQ9 dGfpbDb"PFR*[FL"SBA"`C@jPC#j19[m!-#i!#!4!rm0V!!(3$%!!('i!!FMM5$! l!!C1q`!!!CS"[J$k!+B"[J'q!Ei"[J'q!Ei"[J'q!8`!8J#k!BB!d!'X!13"FJ% N!@!!NJ'q!$`"%!%i!(`!D%(Zr`"$qJ0-F!BJf90!E[S`N@!!!@j"l[m!3rS$((! ')0P63'lk-*&J!!&B3Hlr!%2k!Zj`"L$C8d"ZqQ!!!84"l[m!3rS#b(!%)0P63'l k-*&J!!%Z3Hlr!%2k!U3Jf5$C)0N`N@!!!4T"l[m!3rS#G(!()0P63'lkB!!""N( Zr`"$qJ*1F!3Jf90!E[S`N@!!!2""l[m!3rS#*(!&)0P63'lkB!!!h%(Zr`"$qJ( fF!BJf90!E[S`N@!!!-C"l[m!3rS"aR!')0P63'lk-*&J!!#`3Hlr!%2k!D!Jf5$ C)0NJf@!!!*a"l[m!3rS"I#$C)0NJf5$CB!!!L%(Zr`"$qJ&1F!BJf90!E[S`N@" b3Hlr!%2k!44`#5$C8d"ZqM#4B&j"l[m!3rS!h(!*)0P63'lkB%a"l[m!3rS!V(! ()0P63'lk-*&J1%(Zr`"$qJ##F!8Jf90!E[S`N@!N3Hlr!%2k!%T`#5$C8d"ZqQ! 53Hlr!%2k!#*`"5$C8d"ZqM#4,bi!#N(Zr`![#%kkr3K1AL"IA%p1d"9)BA*NGf& bC5"fEfaeE@8JE'pMDbiL8'9bE@PcFfP[EL"NEf9c)'j[G#"KE'a[Gb"hFQPdD@j R,J!98fpQG(GKFQ8JGQpXG@eP)'a[BfXZ(&4[Eb"YB@jj)'CTE'9c)'&bC5"[F'9 Z)'j[Gbi!)e4SC5"RDACPEL"QD@aP)("[FfPdD@pZ)'Pc)'PZGQ&XD@3Z*94SC5" QD@aP)'Pc)'&XFQ9KC(NJEh"PEL"QEh)JGh*TG'PZCbiB9'KPFQ8JDA-JEQmJFh9 MD#"fEfaeE@8Z!!p0C@e[FRNJDA-JCR9XE#i24'PcDb"*,dmJCA*bEh)Z'84TCQC TBh9XG(NJGfPdD#"bC@jKE@PZCbiB9'KP)'CTE'8JC'pPFb"ZEh3JCAKTFh3Z!"0 8D'8JCQPXC5"TFb"XEf0VC@3Z%94SC5"QD@aP)'Pc)'*eFhNZ'd9iG'9bEQ&X)'C TE'8JFhPcG'9Y)'9bFQpb,Ja&EQ3JEfBJCQPXC5i!%94SC5"NDA0V)'Pc)'CeE'` Z&P4SC5"NDA*PBh4[FRNJDA-JCR9XE#i!'&4SC5"QD@aP)'&XFQ9KC(NJCAKTFh4 c,J!C9'KP)'CTE'8JEQ&YC5"TFb"TERCKE'PN,Nj@!!![,J!-3UG)HJ!@6VSE8#m Z!!K1Z[Z@6PiJAe"26Y!81L"*)'0KELGd)'GPG#"K)'jPGb!!6PB!!#mZ!!a#TdK k!"C1ZKXB,bi!#%kkqej1AL"I8%p1d#Bk)%NJBf&Z*h3JEh"PEL"K)'0[EQjPBh4 TEfiX)("bEh4[BfpX)!"19J!!,bi!#%+R5(S!1Nkk'Xj#TdKk!""1ZKV%6VVl$Nj H,Tp1G4j8D'8JEh"PFQ&dD@pZ)(GTE'`JBQ8JB@*[FR4PC#i!*cSJ9'KP)'C[FQ9 TCfiJD'pcG#"TFb"ZEh3JFQ9cF'pZC'PZCbiJ)%j@r`")HJ"#,bi!#NKk!$4)E[m !2c`!!dkkp%K"lI8d3qlr!("!)0P63'lk3Hhe0#m)2bi!#%kkqa"1AL"IA%p1d!3 L1b!J!!j*)'0KELGd)'p`C@iJ)J"19[m!5(S!3LmZ!!T)HJ!d5'lr!$mm!!01Z[2 `3Hhe0%2Zr`"`3#$C8d"ZqN(Yp63[#$mZ!!K1Z[Ui6PiJAea26Y!%)MXJ)!!555" MB@iRG#"hFQPdC5"dEb!L!%j@r`")HJ"#,bi!#NKk!$4)E[m!2c`!!dkkmj4"lI8 d3qlr!("!)0P63'lk3Hhe0#m)2bi!#%kkqPa1AL"IA%p1d!3L1b!J!"0*)'0KELG d)(*PB@3JCR*[E5!L6PErrLmZ!!T)HJ!b5(S!,NKk!#UTLd*R2c`"68+RUBBpArr q%#i!#'F'6VSC9'!%6VVdZ%jH)&pF6dl3!!"19[rq,`Gq!@!1)!G"lINZk8"#-!! 18NF-4`!3Eq`Z(djH6R919[rd,`FJEJ!)3qlrpL,B)YJbN!"q!3a(!""I`'i5)JG "lINZk8(!-"!1C`454f$Q$%F!%'ib)!G"lINZk8!KVJ!-!!!J"d(Yq5lT3%(`!!4 $l[rf)0NJf6#4)!G"lINZk8!4[!!"!!iZ(djH)&p36dl36PErrLm(IJ&J1L!(3Hh j,ZP!%M!!$QFU)!G"lINZk8!L-!!!XUi!#'BB3UFJ"d(Yq5lT3%K`!!41ZKK5,9m !$'!-8NF-4`!3Em"#VJ!-,Kp1ALkI6R919[rq,`Gq!@!`)!G"lINZk8!5-!!1Cb! J"d(Yq5lT3#)`!!#bVJ!)CJiJ"d(Yq5lT3%)`!!jJ#&*($%F!%'r+,Kp1ALkI6R9 19J!!3Hhj),(YqN"R+%+R6VS$1#!IX+hk3&r!)'hk3#)YqN#bU!!+9X'!!@F),bh k3%kk!M*1ANje6PB!!#"Z!!J4I!!"!!j1ALkI6R919J!!,bhk3%kkrq*1ZJ$+6Pj 1G8j@rqK)j`-B)'i!$%2ZrrBLf#,B-T!!-#i!%0"m!"!q!#!(!N!!!@F#8NFQEIN `3SDCc#!,9X!L$&I"`!&R+VjV!!KZ(#K,2LX!#%U'CJJVD`!%q6"J%#"')@X!"!! %B!BX#bCV!!4JbL!-CJT#Tcm(6VS#C#KI,`a)abm(,bi!%LmZ!!K1ZJ(d'A`!!3! 1)'i!&LPS!!3!"#"Z!"BK6!!%,`a)E[rf6VVpjMP(!!JT6!!+,8`!'NcI'-"1AL" Ih[`!%Nl36PB!!%kkrY3VEIT!q63JEINd+fJ!"2Nd)'hj0#!Yq65`U!!+C`J[,IT !6VS"&#"Yq64++!!1CpBJEINd3LJ!$L!Yq65`VIT!C``[,IT!,bhj0%kk!6`VEIN dqN!3,INpCa4#,INp)'hj1#&Yq6!!"#YYq6Mj-%jH6R919[rm,``SEIT!)#`!",# YqN"R"LKX!!4Jm#"YqN!TD!!%!!3EI!!"q6dVEIT!q6J[,IT!6VVpiNkkrda#TdK k!"*1ZKB+3QG1Z[bN+&p1ANje)%4TFf&cG'9b1L"dDepPH'Pd+#NJFQ9dGA*ZD@j R)C!$!%j@r`"#TdKk!%4#TbmYqN"1Z[dm5(S!)%KZr`!r2!!$6VV[UNKZr`"1ZK@ `3QG1Z[a+6Pj1G43JDA-JG(*jD@jR)(4[)(*PG(9bEJ!&9'&cDb"19[hq3UG1ZJ$ F5'lprNkklc![,IT!5'lqrNkklb4)E[hq5'lqrN+R,bi!#%kkr-j)HJ!FUBY#Ccm m!TT#TkQ'29rrrNkk&@*1ALkI6R8!!%(k!#!JRb*251FI2L"T!!3JMb"4,P"-hhc i8)mLHJ!%6Y%!N!4"qJ"!))iJE`!3dHm!$#%[!!4$q[mN)3NK,`!))3NX5*(m!*! $&#%*)3P)i"mq)Qm!%#+))&rIr!#3!a!XHJ!%6Y!!N!3J$b"I3S%b(j!!J82k!!B LJ%l3!*!%)&p#J$!I3rVrp*'4,VVrlNl3)&mL6bk*6Y"19[rq51F"##KZ!!K#Cbm -6VVVE$iI3T3jEJ!@!!T#E!!-)!ab%Y#"+8!!"MPm,`d!%MPm3IJ!&$Pm#33!&MP m+P!!'$Pm,c`!'LPZ!!`!($Pm6VN!)#PZ!"!!)MPm+Pm!*MPm6R8!+%*R,`a1ZZX #2Kp-ha#!6PiJAplm!""1d%j@!!![,J!8F$`[!%kklVJ[,J!3,bi!$#mZ!!K1Z[p L6PiJAplm!""1d%j@!!!JEJ!)5QJ!#QB'3Li!$'!D3QF[,J!)6VVUZ%TIC`C#,J! -B!BGI!!"!!a1ALkI6R919[rf51F$##KYqTBJ$'F53QF[$%(YqT3[#%kkkZSm(f! @ILT#Tbm(6VVU-#KI)!aQ"N+Z!!KJ'N+81A`!!3!%3Q`!#LPYqT!!!!iV62U3!#e -!!K-ha$!6Pj1G8j@rrC)j`FB*Qi!#%*R,`Y1ZZSd5PpQ%%*R,`Y1ZZSD1Kp#,J! -B'DhlIU3!'B)+fX!$[U3!'!S+'hkN!"q!8UX!!j@`#)(`J"R&VIX!!jQ#LPV!!i !$N)(B!3SE!!1B0j#4LKYqTBJ$'F'8NBS9'$f$%B!(Q`1,`Y"lIU8,`K1ZZSHB!B [#dkkkBBGI!!"!!a-haMJ6PiZRdje6PErqNMR!3JSEIU3!#!-Ca"#Cbm-6VVTQ$i I+'`!$Q$X3UhkN!"-ha#!6Pj1G8j@rq`[$%+R2c`!(d+RF2m[!+Pm+&m[$$mm!!0 )E[ri5'lrp%KZrqbTM5mZrr4)EIJBUBm[$$mm!!0#Ccmm2S#TIN+R5'lrrUQ4$'i !!IrqCJS[,[rd,bi!#+Q3!#m-UB--EJ!"rrjA`%3!(8!!$#KI6PiZRdje6PErr#m (%#hf4dL!28$rr%*(B#SJ"d(YpNM"r!!+)M!!",+Z!!aQ%L!(3Hhf5-(m!!SKVJ! )!!"JAP*(D3DqE[rmEp!3,IC'5)""lIC)`I`!#L'Z!!`!""!YpNC)J%(YpNM"r!! +)Di!#!!!%#hf4NL!8N!E32C'$#d!#[C'E`4#,IC'$#d!#[C(C``3,IC(5)"53"Y !pNFZ(djH)&p36dl36PErqNMR!3JSEIDf1,`!!cPm#!!!!R!%'8!!"(!%'8!!"6P Z!"!!"N*R5'lrqNKZrra1ZZhQ5PpR!Q"k'flrqrE!1flrr2DqF%JE32E"+@hf[J! )+@hj'!!-+@i!$!!3+@i!#!!8)'hhkL"3$'J!!3!#CJC1Z[PbB1`JEIIU+&!jI!! A!!J3,IE"!N!!rcP!!!STEJ!-!!`jI!!B!"!TEIDf!"4#CbmYpqT#Camm!!&1ZZd 32Kp-ha#!6PiJAplm!!T1d%j@!!![$%kk!Xa1Z[R`+'hfaJa8!!0R"P*YpMKJk!a X#!!!!QF'8Qhf1'$D)#`!&,#Yq4KR"P*YpMjJbLmX!!`[,!!)6VVq9$!X!!C63'F '8d"R''!`8Qhf3Mmm!!)[,!!),b`!$%kkrY4J(NUYpX*R#LmYpX*1Z[LBB!45EIB k3Uhf`Q!%8Qhf2'!!rhSSAdjH,Tp1G8j@rr`["a!YpNG)J$e!rra#4f!b)!G"lIC )`I`!#L)`!!5bVJ!-CKSJ"d(YpNM"r!!+)Qi!##+`!!!GI!!"!""J$P*(D3DqE[r mEmK#,J!3,Kp1AL"I8%p1d%j@!!"+VJ!)9X"R&L)Z!!LbVIT!9X(!!@F),bi!#%k kq!"1ALkI6R919[rk51F$##`Z!!a#Cbm',bi!#%kkrfJ3(fF%B!!!eLYYqN$f`Mm m!!%[,IDk,`C1Z[hb3UG1Z[Zf+&mJ$'BL)'i!#%*3)'i!#(!!%8!!!b"Z!!K`!"& !!!*#VIE#B!!!P#mm!!!$K%(krfi[##mYpX)[$%kkqTT1Z[KU5Uhf`QC'3QF[$%k kqc)H(d*R,`B[,J!)6VVqk"!IC`j#Cbm-6VVlTKiIB%aJ5#mm!!!$K%(krbB[##m YpX)[$%kkqP)VEIT!pX*J+N*R,`a1Z[Yk(KmJEJ!)3P!JEJ!)F!!43!!$)'i!#(! !%8!!!N+YpX*J!Q#%60m3`%jH)&p36dl3$!!!5'F%B!!!TNIU!!%-+`!"!!*@LfB '%#X!"'!%%#X!$!`!!"CQ8NIk!-a+NfB53N01V!!#6VS!pN(k!,K58%je+RS!YMY "!!if2!*B*RS!TNkX!!*"qJ#H3T!!3IS!R#m36VS!bNEU!"`JAd2i!@SK83!+6VS !T%je$!!!&fF#B#a(qJ"#5K0Q!Q!L0M`!'%Ik!1"1V!!#5S0R!Nje3IS!*N)34ZS !(%kk!'41G8*$6U`!!N(k!$j58%je3IS!#"#m!!&1G3!!3IS!1##[!"""qJ!i)+m !$%(k!#3JV`!)3IS!*##[!!4"qJ!+3P"1ZJ!q6R8!N"T"q[rk,a"1Z[AL6R9$q[r L)P'TEd(krpi[%%kkpFj1G5m-3IVre#*35UN!!QFF)'N!!LK)U@j+3'B33IVrX## -3IVrTL#X!!CJ$%(krk"#N!""q[q@3T!!+&p1G8(k!!J[5!!%6R8!N"K"q[pS2e! !"%je6PErh%MR$aJQEJ!13UF[#dkk"2BX(d+R,`C1ZJ8!+&mi,!!#3Q`!!K!Z!!d #3!!"Ca!Y62rH)'lrhM!Z!!a#-!!!3UG#Cbm-3S!b,J!-8N%`!H+)2`"1ZJc'-"p )`#m!U&SJ(ce!rqkiE[rZC`ij4!!#,`Y1ZJ@8B!!"JMP%!!)3&!*!!2pA3'F398" R!!#888"R!!%)B!!"AN+R3UF[#dkk"'C1ZJ4f,9rrm%*(B#iJ"d(YpZER3#*Zrr! L-!!!XUN!''B@)!G"lIEQjd"#X!!%,`Y1ZJ8dB!!")P*($%F!$fr-)'lrm$!YpZ4 $lIEQjd!MU!!B!!!`,IEN3HhfjZG!3V!!"!aY!!rfj'B'3Qhfj'!%8Qhfj#m,6VS %lQ!!!0`U$%*(B$!J"d(YpZER3#*&)M!!!,+T!"KQ'L"&)!G$lIEQjd!MU!!%!!3 [#dkk",TJ!!#S8NF-4`!2EmSJ46!YpZ4$lIEQjd!MU!!B!!!J46!YpZ4$lIEQjd! MU!!%!!3-E3!2pZ4Q"N*YpZ4J"&*YpZ3[#dkk"(*JB(!1')"#E!!#3UG#Cbm-3S! `2!!8iSJr!%kk#fJ`(dM!,`#S@L!I18!!!L"')NBMD!!3!!`J4L&Z!!J!%%*R,bh fi#m,2c`!&#mZ!!K1ZJ6@5Pm[#dkk""KJ"Lm,6VS%%%cI'2"1AL"Ih[`!#Nl36PE rlNMR"aK#EJ!53UFr2!)!3QG1ZJ-H*PmJ#fBJ3UG)HJ#i6VS+k%+R5(S!TNkk#Yj 1ZZpk3Qi!%Q!!!)K#Td+R,`Y1ZJ+i6VS#b#KIF!-BJ"PZ!!N!!8*(B"iX,J!+)!a 3J#S!)%8J"b*'%M&`!!*"!2m4J3!!8NF-4`!EEpa#E!!#3UG#Cbm-3S!`2!!NiSJ r!%kk#Ri`(dM!,`#S@L!I18!!!N*R,bhfi#m,2c`!*#mZ!!j1ZJ2q2Kp+4bm,6VS $2NcI'1"1AL"Ih[`!#Nl3"R"KBfYPG!!,58008&p%490898j19[ri,@i!$2ri,@i !#2rm3UF3,[ri!N!!rdM!,`![2!#3!i#S@%UICM33,[ri!N!!ra)Zrr`#33$rXN" A`")ZrrN#33$r&#lrr3*#!2qd39I"`!&%!"e!!""J!!$53UF3,[ri!N!!rdM!,`! [2!#3!m#S@!bI!*!$J'C)%#lrq!*!!2m5,[rm!N%!rl*!9m!5,[rj!N%!ra3Zrrd #3J$rY%&A`F!"%LlrqJ*"!2m8,[rq!N)!rl4"9m(!!83!(8!!%'"U3UF3,[ri!N! !rdM!,`![2!#3!q#S@!bI!*!$`'C)%#lrq!*!!2m5,[rm!N%!rl*!9m!5,[rj!N% !ra3Zrrd#3J$rY%&A`F!"%LlrqJ*"!2m8,[rq!N)!rl4"9m(!!83!(8!!%'!%3Li !%%jH)&p36dl36PErrNMR!`JSEJ!),#i!$%*(B%SJ"d(YpZER3%U`!!"Q"'!iB$B J"d(YpZER3,b`!!"Q+#!(3HhfjZG!+,!!"#!(3HhfjZG!5V!!"'B-)!G"lIEQjd" #X!!!B#"54`a(!!p[X%*R,bhj'#m'6VVqA"!IC`3SKQ!%++hj(%cI%-"1AL"I8%p 1d%j@rrK)j`%B,Li!$N+R,`G1ZJ")*Pp#Tbm,6VS!8LKI%"3#3!$r$%!!#'BDF!! BJ%*R,bhfcLm(2bi!$#mZ!!K1ZJ(35Pm["dkk!4*-haL!6PiJAplm!!T1d%j@!!! JEJ!),@J!"J!-6PiZRdje6PB!!%+!)'i!#")3!N%!$c!"jBM3VJ!),8!!$%jH,Tp 1G8j@rr4)j`FB2#i!#%+R)!C@3%M!,`"#Th!$,`#S@UKB)"mm!#!'d(`!&$)Z!!V 53&*"2J%J"`*!!!&R!P0($'i#3!!+E`C#VJ!-B'3SEII`)!aR'%*R,bhhm%(Ypqi [#%kkhX!k(dT&C`+Cc#!-CKj#TdKk!%j1ZJGL3UG)HJ!m6VS(@%kkkr4#VJ!-B#* #Tbm-6VVr0LCI)!C)`)(m!!4D3!*!!!m#%rr`J4-Y6!!-60mBi%jH,Tp1G3C`B@0 VCA3!#%P1Ad&-6%p$!%j@!!![,J!)3HhhlLm)6VVH0NjH,Tp1G8j@!!!YEINB!!a 1ALkI6R919[ri51F"#$!Ypk*63$e!rrK#4f!H)!G"lIHNj8!JF!!!-"#`EJ!-CJC #VJ!1B&*54fN'[Qlrq'rF$'d!#[HLCJ5Cc'!5-#hhSN(YpfE"r!!'3I!!!#K))!a Q"N+Z!!jJ)$LZ!!`TEJ!)!!)`,IHL3HhhT19!)B`!!&*Ypk)Y6!!160m3J%jH)&p F6dl36PErm%MR!aJ-EJ*B!!a[#Memrrm!&Q!!!6)[,J!)3Hlrr#m)6VVp3%UZrra Q#%*Z!"CJ!!%@3UF[,J!16VVq#LCI!P-2r`"63!!`2!$r&d!!#!*V(rm!"J*Vi!! !"MGYpm`!"&*Ypma#D`!+*fhj'!!-*fi!#!!33S!5%`*"!!m`!H@)-Li!$%M"dS! q!6G(!!*`!"G!!!%JEJ!5&fJ!!3!*3UG#Cbm,3S!5%`*"!!m`!H1)2`"1ZJ@Q-"p )`#m!U&SJ(cG!!!T5EII1,blrr%KZrr*1Z[A@%#lrp!*!!2p+3'B)2AcrrJ!@B&i JEIIU)&!-D!!"!!*Q"NkklEaJl#"YpqSS8$Pm!"B!#$Pm!%J!#LPZrr)!$$P(!"! JEJ!1+@J!"J!83QF[,IIU3QFI2!!"6VVKAM`I5NCQ#$em!!%!&Q!'2Acrr3!@60m B`%jH)&rHr!!16Y"19[rk51F"#%+R,bi!#%kkr1!SAb"Z!!J`+!!1X'`!!Q`-8Qh hi%)Z!!aJ!!#B%"6S5!*!!!m-3!!%C`T5EIIB3Li!$'"q2L`!#N*X!!T)ad+R3QF [$%+!%K3#33!2-!(ML$m!6VS%S$!I5-![!+KD[TpR$MP(!!T5EIIL3Li!$'"#)#` !%,#Yq4KR"N)Z!!aJ-M!X!!B#3"rr5N"@`")X!!EU53*"!!F#33!"J!%#3!!"C`T 5EII83Li!$'!'(A`!!3!-60m3J%jH,Tp1G8j@rqT)j`mB6VVX1NUYq!jQ"%kkl9B SEIJ1)!aQ"P*Ypp*Jk%*R,`a"lIJ-,`K1ZYY+5PpR!Q$88QhheN*R,`a1Z[l`%"m +!!!"C`a5EIIN,`a1Z[c3B,4#Tbm-6VVla#CI1#X!!N)&-#hhSP0!28$rkN*(B%i J"d(Ypk6P3#``!!!J4K!V!!N#3!$rX&"Q-#"'5UJ!!QB%B$"J*#m-)!53!(`!&$m !,bX!$#"',bJ!!Nkk!hK1ZZZ1HJ&1ZZ[@B!T54fN'[QlrkQqX)!8+!!!"Cb*#Cbm V!!`[#cmm!!*1Z[K%29rrp&*YppT5EIIN,`a1Z[`bB!$r&NcI'2"1ALkI6R919[r q3QFr2!")6VVI@MeIrrj1ANje6PB!!#m-+'i!#$!Z!!a)`#m!5'cr!%kkh,S[,2l m2bi!$NKXr[T)E2lf5'cqlUQ0,bcqpNKXr`#TMbKI6PiJAe"26Y"19[lS51F"#%+ R2c`!68+RF2m[!+Pm,9rqr$mm!!%r,II@,`j1Z[q@2c`!!MmYpmi[$NkkriJr2!! $2bhhj#m16VVrHMmm!!3r,IIL,`j1Z[pX2c`!"6mYppS[$Nkkreir2!!'2bhhf#m 16VVr8$mm!!Fr,IIJ,`j1Z[p#2c`!#$mYpp`[$Nkkrc3r2!!*2bhhe#m16VVr*LK Ypr"#4b!-C`C54bK8B2Br2!!+2`F[$Nkkr`Sr2!!,3QG1Z[6i,`j1Z[lk,blqr%k ki0*-ha#!6Pj1G8j@q[`YEJ!-rra#Ta!Zrr`#3!$r5-![!#mm!*!$rkKB5'llr%k kfj4#Ta!Zrrd#3!$r5-![!#mm!*!$rkKB5'lmr%kkfhC#Ta!Zrri#3!$r5-![!#m m!*!$rkKB5'lpr%kkfeK#Ta!Zrrm#3!$r5-![!#mm!*!$rkKB5'lqr%kkfcSJEJ! ),`K)E[[m5(S!1%KZr2a)HJ!`5'lpr%Kk!#K)E[lm5'lkr$mm!!G1ZYY')&p$l[V mF%!Jf90!E[T1AL"I8%p1d!%Z6PErr#m(3UF[,J!)6VS!+#iI5SGQ$%+R,bi!#%k Y!b)Z(d(Y!b)[#+Ra,8F!$#iI6PiZRdje6PErpNMR$`!JEJ!)%"!#3!$r2J"+4fB )3Ui!$'!!!-3JEJ!)F!%5-!!!!N%!rfXBXR`!2fi53IS!a$!"jNK%3!-`!2m+2!! %CPK#"!a(!!KX"$S(B!*k#$e&rrCm!Q!B)'i!#"!`B!!#3!$r$%!!,QB#H!&54QN '['lrpQrL%!4R%%+R,bi!#%kY!cSYA`!-B&"#TbmZ!!K1V3-U,9m!$'"!)'i!#(! "%M!!!!*"!2m-33!M9m!JEJ!)FJ%8-"!!!N)!r`a#!#4A`B!"Ca"#TbmZ!!K1V3- b,9m!$'!%3Ui!$%(Y!c)[#+Ra60m!m%jH,Tp1G32r!*!')'m!"#kI6Y!JAbkI6Y! LAd+"-KmJAe1"3S$3@'3#8S"4bIri2S"1d5"i!Ul3r!!+6Y!!!$)H!Y!!"8j@!!" "lI8d3rS"BL$C)0N`,IUi5-![!%KYrX"1V3'k5'hq`%KYp633,I8d!N!!re*!2`" 1V3,D8QhkZ%Kk!5C)EI8d%#he0!*!!2p53$m!6Ud#fLmZ!!j)EIl!6Ud!FNKYrX" )EI8d%#he0!*!!2p53$m!6Ud#fNKk!1C)EI8d%#he0!*!!2p53$m!6Ud#fJaZ!!S !#'BD5(S!ZNKYp633,I8d!N!!re*!2`"1V3,DB$S-EJ!,!!KQ'NKk!)j)EI8d%#h e0!*!!2p53$m!6Ud#fQ!B5(S!C%KYp633,I8d!N!!re*!2`"1V3,D,bi!#NKYp63 3,I8d!N!!re*!2`"1V3,D3UG)HJ!D6Ud#BN(Yp63[#%kY!B*1AL"Ih[`!#Nl3&e0 dBA*dD@jR)'CTE'8JG(*KER0QCA)Z$b"#383JC'PbC@0dD@pZ)!K6C@jND@jR)!! +8Q9MC@PfD@jR)!!#1L!!"b`JGfPdD#!'G'CdF#!M!%j@!!!["ciZ!!J-4`!,9m! 5,IrZ#J%!!F!"$%F!#PI"&#hrl`S#!!(#!S!"C`!!ZN(Yp64$qJ%')0N`N5mZ!!j )EIl!6Ud!FNKYrX")EI8d%#he0!*!!2p53$m!6Ud#fNKk!-j)EI8d%#he0!*!!2p 53$m!6Ud#fJa(!!TQ'NKk!+T)EI8d%#he0!*!!2p53$m!6Ud#fQ!H$%F!#fBB5(S !K%KYp633,I8d!N!!re*!2`"1V3,D,bi!#NKYp633,I8d!N!!re*!2`"1V3,D3UG )HJ!f6Ud#BN(Yp63[#%kY!B*#EJ!5B"3[,J!1,bi!#Mm(6VVpQ$em!!%!%LiI6Pi JAplm!!T1d"T5C@TPBh4PC#"dFQ&ZFfCPFL"bCA&eCA0d,J!%Cf9d)!!%F(9d)!! ,1L"8FQPPC#"dEb!&4R*[E5"19J!!5Qi!#'FN3Hhe0%2k!$T`"5$C8d"ZqN+R5(S !'%kY!Q*"lI8d,`K1V3'#6PiJAe426Y!64QPXC5"dFQ&ZFfCPFL"NEfjP,K06G@0 MCA0cCR9X)(4bB@jcCQ9b6PB!!%(krPS[#%(krjB[#%kk%'C1ZK"8%#hq[fF'6Ud #LQ$d6Ud#FNjH,Tp1G8j@rri["hi"B!`[,Iri2`G#CkP&8NF-4`!%Eqi`,IUb8d" R$P0!CaT93'FQ8d"R-Q!q,bhrq$mm!!%I2!!"U89J,LmYrrJr2!!#(c`!!DP&B"i [,Iri2c`!!amm!!'T4@!1,bhrq$mm!!3I2!!"U88Z(djH6R919[rX,``EI!!"rqj #,Ir[3UFr2!!b3UG`rbm!UA`SAbm-2c`!!dKZrrK)E[rd5'lrl+Q0,blrp$mm!!' TBbm-2c`!"%KZrrK)E[rd5'lrl+Q0,blrp%*RU@0#TdKZrrkTN6!ZrrjV',"m!!G Z%N(k!*Bb!1C*4%%"-"$r#M`!"'GN,``r,[rq5'lrq%KZrr4)E[rXUBd`,[rq9d" R"P0!CajJ0K!Yrqi+!!!"'d$rlLmZrr33,IrZ5)!r!+PMB"S3,Ir[#J!!!4Y!rqm [,[rd%#hrldL!2`#TBd+R5'lrrUQ4B!$rILm-UB--EJ!#rrjQ"N)Z!!KJ"Kem!!% !##KI6Pj1G3!'6PErrNMR!3JSEJ!)-#hkXP0!C`j63'F198"R$P0!C`jJ$Ri&B!T q"Q!'IJGJ!Ri),bcqqMm(5'cqq%KXr[4)E2lXUBd[,2ld2bi!$+PM60m3J%jH)&p F6dl36PEpl#"Z!""$l[m!F%!Lf&0!E[T)E[m!5(S"b%kY!XS3(fF85(S"eNKk!EK )HJ'd5(S"X+Q,B$SJEJ!-,`K)EIfm5'lr!%KZrH`r2!!#6Ud#dL"I3qlpl("!)0P 63'lk5(S"JNKk!Aa)HJ&i5(S"G+Q,3UFr2!!,3UG`rbm!UA`YArlk,blqqMmm!!4 )E[li5'lqp%KZrZbTM5mZr[4)EIbmUBm[,[lk2c`!!dKZr[K)E[ld5'lql+Q0,bl qp#mZ!!bTMbmZr[Sr2!!$3QFr2$k!UAir2!!",`j1Z[l+3UG)E[lqUC%`,[lqDaL `I!!(EK*"qJ$Z-J$Q584"!6!3r`Sm!!4RD%*R,`j1Z[kD-#lqrPY!C`j63'F58d" R&P0!CaTJ(MYm!!(kXQ!@1h`!![UbB!ilI!!%qV*J"MYm!!AkXLmZr[Sr,[lq5'l qq%KZr[4)E[lXUBd[,[ld2c`!!DPM3UG)E[lqUC&J!2pk$'i!![lqCK![,[lkUB0 1Z[bX3Li!&'"3,blqqMmm!!4)E[li5'lqp%KZrZbTM5mZr[3[,J!)UC!!,blqqMm m!!0)E[li5'lqp%KZrZbTM5mZr[3[,J!-UC!!,blqqUQ$6VVmA"em!!%!&%jH)&r Hr!!-6Y!!"J!!&djKE@8JEfBJ9'KP)&*PE@pdC5"'D@aP&8jKE@8JEfBJ9'KP)%4 TFQ9MG'pbH8j@!!![,IUS6Ud#JMYZ!!MkS%jH)&p86dl36PErqNMR!`"#TdKYr,a 1V3)D+erkY%UYqV4Q'%+R5(S"G%kY!Q*"lIbm,`K1V3#UB!!!rJbY!*!$!IUdCK* #TdKk!6"1V3*L6Ud!XQ!!!1)[,IUd3Hhk[#m)2bhkTNkkq&a#TkPe,Kp#TbmYqV4 "lIUm,`Jr,IUk3Hhl[#m)2bhkTMmYqV*"q[pH,`K1ZK*k5TpQ#%*YqU"J!!#@6Ud #DN+RUA8J(j!!Kbm!F$`[!%kY!VSZ(`aY!!VkTQB13QG#TcmYqVT1V3$#2"p+EIU JCf""lI8d3rS!KR!&)0P63'lk-*%["dKYrX"1V3'k5'hq`%KYp633,I8d!N!!re* !2`"1V3,D5(S!6%KYp633,I8d!N!!re*!2`"1V3,D3UG)HJ!B6Ud#BN(Yp63[#%k Y!B*-h`$!6Pj1G4P'D@aP)(4bB@jcCQ9b)(0eBf0PFh0QG@`Z#5"cC@0[EQ4c,K4 'D@aP)(4bB@jcCQ9bFQ9N)'PZ)!!K9%C88$SJ6Q&YC5"cCA*fCA*c)'j[G#"bCA0 `EfjND@jR)94'9&!k)%0[G@aNELGd)(*PFfpXGQ8JD'pcG#"ZB@eP)%j@rTT)j`% )2A`!C2rq2A`!@[rm3LhkT8kY!QSEI!!"qU8[,Ird2c`!!DNk,bhrp$mm!!+T1Lm Yrr3r2!!%U6Nr2!!,UANr2!!aUAP1V30#$'d!#[UQCQ![,[rm5(S"V%KYqVa#TdK Zrl*1V3*5%#lrXQG!3Hhk[%2Zrla`%#$C8d"ZqN(Yqla$l[qmF"!Jf90!E[SlE[q iqVT#CdKYqVa)EIZm5'hm[%kkqh33(fF%6VVpQ'!!!4T#TbmZrra#Tcmmrrp)E[q L3UG#Th!",`"`!Lm!U&XJ(cm!6Ud#5LKI)!aR!!$S)&3-8!!"CNiJ9#"S!#!J8%2 YqVa`3#,B8d"ZqL"8)'J!)#"33qhl[("!)YK63'lk)&3lD!!#qVT#CdKYqVa)EIZ m5'hm[%kkq[!3(fF%6VVp&'!!!)a#CdKk!-K)EIfm5'hm[%kkqY)3(fGd)&3`%&0 !28$qQN*(B'!J9#!(`I`!"L"`!#!J8%2YqVa`3#,B8d"ZqNKYrE`J9#!(`I`!"L" `!#![%%KZrT`r2!!#6Ud#dN(Yqla$l[kFF%!Jf90!E[SJ9$YS!!,kZNkkr*K+EIU JCJ*J#P*(D3DqE[kDEjS[$%kY!)*1V3*#2c`!#kPk2c`!-DPk,bhrp$mm!!'T15m Yrr3r2!!#U6N[,Ird2c`!"+NkB!$q'%cI%)"1ALkI6R8!!""-Ef0KE#"'D@aP)%j KE@8k!%j@!!"#Td+R3UF[,J!)6Ud"5NkY!hT1V30U,9m!$%jH,Tp1G8j@rrj#Cb" Z!!J[+!"+3QG1V3(L29rrrNTZrrjR#"em!!%!$'!8)'i!##"S!%T+U!!F9m"%!"e !!!a1ALkI6R919[rk51F"'#CZ!!JSEJ!-3NGJ)NS8CKS[,J!-)!Y5J#m!5-F["dk Y!$)S5aL(B!jJ!P+-8NF-4`$rEpK#%dcI')"1AL"I8%p1d%j@rra)j`%B*Qi!$#i Z!!JJ#e+!+%![$#m(%"-#3!$r5-![!%kY!$)3%`*!!2p)`0k!)%G#%%cI')"1AL" I8%p1d%j@!!")j`!B+'i!$#CZ!!K+8fC13QF[,!"+3QG1V3(#0Tp+8fF%B!!"IN( X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'N*R,b`!5N*R6Ud"dMDI5P0R"'! !!8Sf[2r9$&2re@B!!4T"l!"5)Q`!5L0)!")JE!"+-@`!8!!@)'`!5N)S!"T#Cbm X!%T#CdkY!FSfRdT6CJ!!N!""l!"5)Q`!5L0)!")JE!"+-@`!8!!@)'`!5N)S!"S JE!"+,`K#TkPe)"mJAb&!!%JJE!"+,`K#TkPe)"mJAb&!!%`JE!"+)A`rN!3!*#" X!%T#D!!S)'`!5N*S!#i-,!!"!%jQ$L"X!%SKI%&38%`!)'!-)'`!5L&m9%9B9!! J3QF[,!"+3QG1V3)#0Tp+8fC83H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T#+!! D)'`!5K&m!!-!'b"X!%T#U!!F5L`!6QB33QF[,!"+3QG1V3(b0TpJ$N*R,b`!5N* R6Ud"kMDI5P0Q)#"X!%SaI!!"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud##MDI60mB!%j H)&p36dl36PErr%TZ!!aR0N+R)'i!$NKS!&*1V3*L2bi!$%kY!-T#TkPe+erkDNU Z!!KR##mZ!!K1V30b8fhkEN)Z!"*J"Kem!!%!%NjH)&rHr!!+6Y"19[rm51F$##K Z!!SjEJ!)!"!-,!!"!%jQ!!$33QF[,!"+3QG1V3(#2Kp#,!"13H`!8L*X!%SM5!! 5)'`!5M&X!&!!&L"X!%T#+!!D)'`!5N+S!"`-E!!+!#CQ'L"X!%S4I!!$!"Y#Cbm X!%T#CdkY!I)q(f!m)'`!5K&m!!%!'d*R,b`!5N*R6Ud"mMiI5NGQ)#"X!%SaI!! "!#`JE!"+3UJ!,N*R,b`!5N*R6Ud##MiI3QF[$$m(3UG1Z[ld("mJ"JS!!!&R'Lm X!!B[,!!+2c`!!8+R5(S"#%kY!Q*1ZJ(X(8B!$Q!!!-S-,!!#!%jQ!!$!'A`!!3" 13H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T#+!!D)'`!5N+S!"`-E!!+!#CQ'L" X!%S4I!!$!"Y#CbmX!%T#CdkY!HSq(f!m)'`!5K&m!!%!'d*R,b`!5N*R6Ud"kMi I5NGQ)#"X!%SaI!!"!#`JE!"+3UJ!,N*R,b`!5N*R6Ud##MiI3QF[$$m(3UG1Z[i S("mJ"JS!!!&R'LmX!!B[,!!+2c`!!8+R5(S!'NkY!Q*1ZJ%J(8B!$NcI%-"1AL" IA%p1d#&$Eh9XC#"ZEh3JBfKKEQGP)(4[)(*PFfpeFQ0P)'C[FQXG3fpeE'3JEQp d)'0SB@jRC5"dEb"NBA4K)'C[FQY19[rL51F2'$JZ!!K#Cbmk!,UTR$`I3QG#Tcm %6Ud#1NTIC`4J!!#B3QF[,J!+UCFq(`a(rrpQ"'!!!)4#Cbmk!)UTR$SI[%9XF%+ R,cS!I$mm!!'TR5KI)!aA`'FH,`"#CbmZ!!Sr"%KZrr"1V3%#-KmJ(dT"9X'!!@F '2`HTQQ!m*P3Y8rrd3UF`,[ri5-![!#mm!!!J!+KE)"mp32ri3QF[,J!+2`4)E[r `6Ud#+NTIC`Br"kQDB!3r"kQD60mBm%jH)&pF6dl33Nj%6%j@rr4)j`mB2#i!$#S Z!!Kq"%+R,bi!$NkkqLJQAcDm!!8h4J!#5NCQ)#K&,`8J#eL!,`"1Z[Uk)%83%!* !!2r34e*!2J"J!!#q-!CV!!#8$%!!"fi!!)cM5$!l!!C1q`#3!a)!)!!Z!$`!5J" B!'B!G%+R5(S"1%kY!Q)SAf"Z3UG)HJ%D6Ud#BLKIB'"#TdKk!2T1V3*L+&pJ8N+ R5(S!iNkY!Q)SAf"%3UG)HJ#m6Ud#BLKIB$C#TdKk!*T1V3*L+&pJ+%+R5(S!H%k Y!Q)SAf!D3UG)HJ"F6Ud#BLKIB!a#TdKk!%"1V3*L+&m[$#!,@)![!%kkqIJ3&!* !!2r34e*!2J"#CbmZ!")[,J!12`G1V31D1"p-haM`6PiJAplm!!j1d!e9EQYZEhG Z)'9bFQpb$%j[)(0eBfJJGA0PFJ!64QPXC5"KE(*PB@4j)'9iDA0dFa09EQYZEhG Z)(4bB@jcCQ9b)%P%&NPXE'9RB@`J9%C88#"[F'9bBA4TEfi!#84TFfXJCR9XE"" "Bf0PFh-JGQP[E'&dD@pZ!!j'D@aP)'j[G#"QEh9ZC!!B9'KP)%T13b"0C@e[FQP KE#"#98G)38a8!%j@rrj)j`%)+'i!#%*R,b`!'%kY!TSH(cPm!!-!(#mX!"41V3+ #60m3J%jH,Tp1G8j@rrS[$#KYqP3J$'Fk$'`!!3!XE5![,!!',b`!#N*R3UG)HJ" B6Ud#BNkkrG`[$%kkrjjJ$N+R5(S!&%kY!Q*1V3#b+&4J`LKI6Pj1G5a6Eh*bH5` JH@pe)'0KELGd)'&LEh*d)'%JBfpZEQ9MG'P[EL"bCA&eCA0d,J!53fpZEQ9MG'P [EL"KBQpbG'9N!%j@rra#TbmZ!!K1Z[HZ,9rrr#!ZrraBJ#e!!!a1ALkI6R919J! !1h`!!ITF6Pj1G8j@!!"#EITF3UhkDN+R3UG#Ccmm!%9"qJ"1,`K#TdkY!i)VArT H5UhkAQB33UG)HJ!J6Ud#BN*R6Ud!ZLYZ!!MkCLYZ!!ckBNjH)&p36dl3&80KELG d)'p`C@iJ9843)(0[BfYPG%j@rG*)j`mB*Qi!%NkY!'*#Td+R,`Y1V3&+6Ud$HLe IrGj#TbmZ!!`JE[hH2a"1V30D5TpR#Lm,6Ud$FQ!!"@!J,ITUX+X!#Qm+,`Y1V30 bB!!&6!aY!!(kEQdX6Ud#LJaY!!(kEQdJ,bhkALm,3QG#TdKk"G"1V3*L6VSFM#m ,6Ud$FQ!!"4K5EITZ3UF[#dkkpT)Z(b"()NFbN!!J4`a3!!*[$Lm,6Ud$FP0YqQj J!!6Z)!G8J#m!3HlqmLm)6VVfb#!(9)!5,[lb!N%!rdM"dS"5J5m"3HlpmLm)6VV fUN)'3QG)E[hb5(S&9%*R(c`!!8kY!+)3(fF'HJ*m!@"m3QG)E[hb5(S&-%*R(c` !!8kY!+)3(fF'HJ*m!@"H3QG)E[hb5(S&#%*R(c`!!8kY!+)3(fF%HJ&J3N*R5'l pmNKk"1*#Camm!!&1V3#L%"pR"RS&I!*J*#mYqPi[#d*R3UG)HJ5f6Ud#BNkk'j` [#dkY!h*6EITZB!!%*%TYqPaQ*#mYqPi[#d*R3UG)HJ4U6Ud#BNkk'h)[#dkY!h* 6EITZB!!$qL"($&!!!@B%H!YJ!RJ+3QF[,J!-3HlqmLm)2`3[,ITL6Ud!3NTICL` [,ITH,`Y#Cd+R5(S%$%kY!Q*1ZKXQ3UHTG5YIqQS[#dkY!h*6EITZB!!$TN+R2c` !#cmm!!&1ZKAH+&mJ$'BQ3UG)HJ2#6Ud#BN+R5(S$[NkY!Q*1V3"5,`Y1V30b8fh kEQ!!!f`TEITQ!"j"l!"53qlqmR"!)0P63'lk'8B!6N*X!&!j43!N3UF[,J!-)'l phMm33QG1V31+3ISBkLm),`a1V31#+9m!"NUX!!CQ-N+R5(S$8NkY!Q*#TdKk!d4 1V3*L6Ud!@N+R,`a1ZKFf,9rpe#m,6Ud$FP0YqQjJ!!,`)%F-8!!"CJ!"JMPm!!X !*MPm!!%!%N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%S4I!!"!"X JE!"+3UJ!("!X!%j)J'F18d"R@&0!C`!!SQ!!!-4#CbmX!%T#CdkY!I)pArh55Ql pdQBf3QF[$%kkp#!3(fF)2AcrfIh5B#)JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T #CdkY!JSpArh5B(4#CbmX!%T#CdkY!HSpArh55QlpdQBf3QF[$%kkmp)3(fF)2Ac rfIh5B%SJE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!JSpArh5B#C#CbmX!%T #CdkY!I)pArh55QlpdQB33QF[,!"+3QG1V3(#29rpdN*R,``r,[h5,`Y1Z[AX%"m +!!!"Cc3[,!!',b`!#Mmm!!&#TdKk!Ej1V3*L6VVijN*R,b`!(NkY!$T#Tbm-6VS 9a#eIrG4J!!')3UF[,IT!3IS*a#m)2c`)!%Kk!D![$%kY!RSTA`!8B!!"B$Pm!!S !*MPm!!%!%N(X!&)LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%S4I!!$!"X JE!"+3UJ!("!X!%j)J'F-8d"R*&0!CcaJ!!#B3QF[,!"+3QG1V3(b29rpdLm-5'l pdNkkmh*JHN*R,b`!5N*R6Ud"kMeIrG)[$%KZrG*1Z[0@B&j#CbmX!%T#CdkY!I) pArh55QlpdQB53QF[,!"+3QG1V3(#29rpdQ!f$'lreIh5CLj"l!"5)Q`!5L0)!") JE!"+-@`!8!!@)'`!5N)S!"T#CbmX!%T#CdkY!FSpArh53QF[$$mZrG)[#dkkp*S 3(`S!!!&R-LmX!!B[,!!+2c`!!N+R5(S!E%kY!Q*1Z[H83QF[,!!H6Ud!1N+R,`a 1ZK4b,9rpe'!f3UF[,IT!3IS'YLm)2c`)!%Kk!$)[$%kY!RSTA`!86Ud#LMPm!!% !%#m-3QG1ZK&Z,`Y1V30b60mBm%jH)&rHr!!16Y!%G'ChFJ!A3fpeE'3JEQpd)'p `C@iJG'KP)'CTE'8%G'CbC!!$9843"&4'9&!!#Q0[EQjPBh4TEfi!%94bB@jcCQ9 b)(*PCR9cC@3Z)94bB@jcCQ9bFb"KFQ8JEQpd)'*PD@jR)'&MBf9`G'9N,JK#B@3 JE@pNC3!*E@&MD@jdEh0S#'jPG'&cBfPT!!9TE@&RC39[Bh4PG"98EfmJE@&ZH5" MEfjZC@0dD@pZFbj19[rU51F2#$`Z!!a#TcmZ!!ir"Nkk%A3SAb!-CL"#TdKk"Bj 1V3*L3UG)HJ@86Ud#BNkY!&*#VJ!HB!!&&P*YqQiTEJ!)!"iJEJ!@3q`!8R"!)YK 63'lk1@i!&!"318B!*!a'!!9Q#"Pm!!)!6Q!U-!CV',"m!!GZ%N(k"83b!1C*4%% "-"$r#M`!"'B)'A`!!3"1B!4#,!"1$'i!#J!1CJ!"p$!'DaL`I!!(EK*"qJ8)-J$ Q584"!6!3r`Sm!!4Q!!$H3H`!8L*X!%SM5!!5)'`!5M&X!&!!&L"X!%T#+!!D)'` !5K&m!!-!'b"X!%T#U!!F%#`!6NL!C`a63'FN8d"R2'!!!+a#CbmX!%T#CdkY!I) pArrU,`a)E[rU6VV`QQ"k3QF[,!"+3QG1V3(U29rrkLm-5'lrkNkkm(jJFN*R,b` !5N*R6Ud"mMeIrqT+E[rUCK*#CbmX!%T#CdkY!F)pArrUB%S-E[r9rqTQ,N(X!&) LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'N*R,b`!5N*R6Ud"bMeIrqTJ%N+R,`a 1ZK(5,Kp#VJ!HB!!$SNTZrqTR(LmZ!"Br,[rU6Ud!bN+R,`a1ZK'Z,Kp#VJ!HB!! $IN*R6Ud$LMSI3UF[,J!D2c`!46m&3IS6$Lm),`a1V31#+9m!"NUX!!CQ+N+R5(S $V%kY!Q*#TdKk!jj1V3*L6Ud!@N+R,`a1ZK&D,Kp#VJ!HB!!$+LPYqN!!&%*X!") jI!!"!""#TbmYqN""qJ1+,`Jr2!J!5(S$E#m-6Ud#HLPI!"4#Cbm-,bi!%%kk#Gi i(dT%E"j#TdKk!b"1V3*L6Ud!XN+R,`a1ZK$k,Kp#VJ!HB!C`!5e!!"jJ!!,#B!! #[JaZ!!X!$QB!!V3`"QXBX(`!"fi53IS$$$)!jNP%33%`%2m+2!!%CJ!"0%(X!&) LE!"+)dJ!%L"X!%SaE!"3!"BJE!"+3LJ!'L"X!%S4I!!"!"XJE!"+3UJ!("!X!%j )J'F18d"R@&0!C`!!SQ!!!-4#CbmX!%T#CdkY!I)pArrU5QlrkQBf3QF[$%kklF` 3(fF)2AcrfIrUB#)JE!"+-A`!!3!X)'`!5N+S!#j#CbmX!%T#CdkY!JSpArrUB(4 #CbmX!%T#CdkY!HSpArrU5QlrkQBf3QF[$%kklAi3(fF)2AcrfIrUB%SJE!"+-A` !!3!X)'`!5N+S!#j#CbmX!%T#CdkY!JSpArrUB#C#CbmX!%T#CdkY!I)pArrU5Ql rkQB33QF[,!"+3QG1V3(#29rrkN*R,``r,[rU3UG1ZZqB%"m+!!!"Ca*#Tbm-6VS 2P#iI3Ui!(Q!!!@4J%N+R,`a1ZJq!,Kp#VJ!HB!!"8%TZrqTR(LmZ!"Br,[rU6Ud !bN+R,`a1ZJpF,Kp#VJ!HB!!",%*R6Ud$LMSI3UF[,J!D2c`!46m&3IS3[#m),`a 1V31#+9m!"NUX!!CQ1N+R5(S"@NkY!Q*#TdKk!8a1V3*L6Ud!@N*R,b`!5N*R6Ud "`MeIrqT#Tbm-6VS1q#iI3Ui!(Q!!!-JTEIT!!"4#E!!33Q`!%NTX!#*R8%*R,`` [,J!36VS(PMJI5N4X)N+R5(S!f%kY!Q*1V3#b3UF[$%kk$V)Z(d+Z!"jJ!!##B!C `!5e!!"ijI!!#!"a1V3*U$'`!"!!FC`*J!Q#U5Q`!)QBJ3UG)HJ"X6Ud#BNkY!,* #Tbm-6VS1ELiI3Ui!(Q!qB"J-E!!$!"aQ%%+R,`a1ZJj8,Kp#VJ!HB#4#TbmYqN" "qJ*8,`Jr2!J!5(S!)#m-6Ud#HLPI!"4`!5e!!"j-ha$`6PiJAplm!"C1d!4dCR* N!#C84P431L"5CA&eCA0d)(4[)(0PEQ3JEQpd)'&MDfj[GfaPC'GPC!!G9%C88$S J4'9cG'PZBA4TEfiJG@jbC@&MD'&LE'8$9843"&4'9&!!!$B%G'ChFJ!!&!TMEfj ZC@0dD@pZ!%j@rrK)j`F)+'i!#$Pm!!%!(!aX!!%!('B'6Ud#DQ$b$'`!"!!FCJ! !LNTX!#*[1%*R,b`!"LmX!!Sr,!!16Ud$QMSI3QF[,!!B6Ud#QK`I,b`!1N(k"4J [##m-,b`!'%kY!V*JTQ"+3UG)HJ&-6Ud#BNkY!E)-E!)!!#aY'#mX!!B[,!!+3QG #TdKk!4"1V3*L6VV`+%*R,b`!(NkY!$T#Tbm-6VS0"LiI8fhkEQ!!!,S-E!!)!"a QIJaX!!8!*&I!5L`!6PE"`!&R-MPm!!F!(%*R,``r2!!"6VVY&"!I#J!!!@F@3QF [,!!H6Ud!1N+R,`a1ZJbf,KpJEQ!k3QF[,!!B6Ud#QK`I2c`!!5mX!"j1V3!k3UF [$%kk$*!!,Kp+KfB13UG)HJ"36Ud#BNkY!,*6EITZB$)-E!!(!"aR*N*R,b`!'%k Y!TSF(d*R,b`!(NkY!$T#Tbm-6VS-8#iI8fhkEQ!%B!$qT%kY!R*-ha$J6PiZRdj e)P4bD@9N)(4[)(4bB@jcCQ9b)(TPFQmYE'9ZCh4S)'CTE'8!'e4[Eb"YB@jj)(* PG(*TCA-X)'GTGQPZCb"eF!484P43!%j@rma)j`mB+'i!#%)Zrm`Z2!!!!J"#"%* X!"!YE!!+rqT#Tcmm!J"#CdkY!e)QAb!,CL"#TdKk!fC1V3*L3UG)HJ0L6Ud#BNk Y!&*6EITZ6Ud#FMPm!!)!("!X!%j)J'F18d"R#P0!C`!!kQ!!!43-E!!"!#4R4#" X!%S[#%+R,`Y1Z[%#)"mJAb&!!#!JE!"+)A`!!!)!!#3JE!"+3QJ!,%*R,b`!5N* R6Ud"qM`I)'`!5LiS!#KJ!!$),M`!!!)!3UF[#dkkm,iU(a!ZrmaR$N)Zrm`J44# m!!T6Ke+&)'`!5M&m$B!!,%U(EeSJE!"+)88!)#"X!%SK4`!N3QF[,!"+3QG1V3( k2"mJE!"+RUJ!+%T'C`*J,L"X!%SJ+!!S8i$DJ#"&$"!!$@BB5SGQ#"em!!(rc'! -8S8J44#m!!T6Ke+&B+)J2!!!!J#3!)FZ!'!Z5Q`!%'BN3UF[#dkkm#BYArrd3QG )E!"52b`!8#mZrr41V3%#2"pq%'!%3SGmf8T'9X!-4[rC9X(!!@F83UG)E!"56Ud #BMm'6Ud!dRcC3SFJ"p#X!#JT3!!S5Q`!%'B'1A`!"J!F$'`!!J!FCJC1V3*UB2) -E!!%!"aQ!!#D5Q`!)Qp#3QF[,!!',blrkMmX!!j1V31D29rrj%*R,b`!'%kY!TS GArr0,b`!1N(k!Di[##m-,b`!'%kY!V)jI!!#!"aJSQ"33UG)HJ'!6Ud#BNkY!E) [,!!',b`!#N*R3UG)HJ&)6Ud#BNkkl-!jI!!$!"a#CbmX!"j1V3!k3UF[$%kk#CJ YArrJ,`Y1V30b8fhkEQ!!!3J-E!!'!"aQ!!$+-#`!%&*!18!!%"!%C`!!JJaX!!8 !*&I!5L`!6PE"`!&R2%*R,`a#CdkkkC33(`S!!!&R)Mmm!!%[,!!H6Ud!1N+R,`a 1ZJNd,9rri#m,6Ud$FQ!!!+K#"'!!rBCJ-N*R,b`!'%kY!TSGArr02c`!!5mX!"j 1V3!k3UF[$%kk#2`YArrJ,`Y1V30b8fhkEQ"X,@lrk[rZ+8X!#Le,rqSQE[rZ3QF [$$m(6VS*Y$eIrq3-K`!!!J"G`!a'rpPA`B!"C`*i!@!!r4T#CbmX!"K1V3+D(9r rc8*R,b`!(NkY!$T#Tbm-6VS)P#eIrq![#dkY!h*6EITZB!4J!2cQ6Ud#FNcI'2" 1ALkI6R8I8Q9dFRNJE'PYDA3JCAKMC@9NC@3X)'GTGQPZCb"eF!484P43!!YQD@a X)("KBfYPG%j@!!![$#KZ!!J`,!!b8N!j3!!b-#`!)P0!18!!)M!X!$453$P!!$3 `,!!q8N!j3!!q1A`!"!!F,b`!&%kY!S)SAdjH,Tp1G8j@rra)j`%)+'i!#%+RUA8 Z(jkX!%B-E!!"!%"Q"MPm!!`!3JaX!!%!2QB@)#`!0Y#(,`"`!Lm!6Ud#ZLPI!$C J1!aX!!%!3&l!$'`!!3"#AX(!!@F+-#`!3P0!18!!3LmX!$B`,!"#5-![!%kY!VS J(p#X!$BT3!!f,b`!0R!$,`"1V3,#+9m!1JbX!!!$B!!kE`JTI!!!!f!!1JbX!!! #@!!kE!JTI!!!!PJ!1MPX!$i!3%cI%)"1ALkI6R919[rf51F('#KZ!!`U,J!)3UF [,!!+6VVNFLCI$'`!#J!QCJBf[!!"B"J-E!!,!#CQ"MDm!!*J#Memrrm!%'!!!-3 ["5!,9)![!%kkj1`J#e5!)%85%!*"!2p)`G+!8S%Z!3aX!!)!*&I!$'`!!`!N9m' !!@F83UG)HJ#`6Ud#BLm(6VVNY(`&B&`-E!!%!#4Q&%+R5(S!MNkY!Q)["dkkj*K m"@"!$'`!!3!NCK4#TdKk!'K1V3*L,`G1ZZ4mI!KJ*!aX!!8!*'B83UG)HJ"#6Ud #BLm(6VVNB(`*B!JpI2rr!""J(N*R,``J44!3!N!!re4!8N$34P*!2`"1ZJ1Z29m !%%cI'1"1AL"I8%p1d!PYB@0TER4[FfJ)EQ9dBA0MD@N!"@pMG'9d"@PYB@GP6PE rf%MR$aJSEJ!1*Qi!#MSZ!!K1V3"L$%8!"'`J,b`!"Lm,3QG#TdKk![T1V3*L6VS *$#m-6VVUU'!!!Ti-E!!%!"aQ#$Pm!!%!('!L$'`!!3!FCaS[,!!',`Y#Cd+R5(S #SNkY!Q*1ZJM8B!!#E%+R,`Y1ZZ,N,9rrq&P&)'lrq$!S!!+`E!!3C`i`,!!`8N! j3!!`B!!#3N*R,b`!'%kY!TSGArr[,`a1Z[eQ,``JE[ri2bJ!!Nkk!S")a5!&d+` !+#P!!#K#Tbm,6VVUdLeIrr``,!!NDaL`I!!(EK*"qJ)L-J$Q584"!6!3r`Sm!!4 Q!!'L%#`!6NL!C`j63'F+8d"R!!$iB!!"9JaX!!%!*'FZ)'`!5L&Zrr`!)%M&)'` !5L&&!#3JE!"+3QJ!,%*R,b`!5N*R6Ud#%M`IB!!")"!X!%4R#N)X!%4i!Ai"B!4 #4%*()'`!5N*S!#a#4VT(E`!!Q%M()!I3V[rm,8$rm#"Zrr!-%!!09X"R%,T(AX( !!@F)8NG5V[r`B15k4fB+)!963$i!8klrm#!(N!"%8N")`#"X!%SK3!!N5-3J"0# Zrr`JE!"+)8!!)%*R,b`!5N*R6Ud#%M`I5NCR!Q!X)!963,"(9m"Q&L"Zrr!-%!! 09m(!!@F)'A`!!3"%B(!J"e4!1!!q"'!!rfCJBN+R,`Y1ZZQF,9rrk%*R5'`!8Mm X!&")E[rB6Ud"!M`I)'lrk#e3rpJJE[rS,@J!"2rF3UF[22rrr[mJE[rS-#J!#%M !,`#S@#!I28$ri%*R5'`!8MmX!&")E[rB6Ud#+M`I5NCR-#mX!!B[#cmm!!0#TdK k!)C1V3*L6VS'a%+R5'`!8NkY!Q)r"NkY!2)[$%kkk&"J4Q!H,b`!"Lm,3QG#TdK k!%C1V3*L6VS'P#m-6VVS-'!Q$%8#!'B)1A`!"`!FB!BjI!!)!"``,!!38N!j3!! 3,b`!&%kY!S*-haM`6PiJAplm!!T1d!p*ER4PFQjKE#"&FR*[FLi*4'PcDb"'G@a X!$BH8Q9MC@PfC@3JG@jPH("PBh4PC#"NBA4K)'*XEf0V!"9CEh8JD'&fC5"$8e) JC'PcC@&cC5j19[rk51F"'#CZ!!T#TbmV!!T1ZZ!-+&mhI!!%!!ii[!!%1@i!#!! #3QF[#cmm!!41ZJ!32Kp-haL!6PiJAea26Y"19[rm51F!'#KZ!!T#TbmX!!T1ZYr +*Pm-8`!"9X!-8`!#9X(!!@F)1A`!#J!LB!BjI!!%!#)fNcPZ!!J!$M!X!#j53$P !!#j#CbmX!!B[,!!+2bi!#%kY!jT+Afi12Acrr`!11A`!"3!FB#B[,!!k3IVjh#m ),``[,!!B6Ud#XN+RUA8TA`"'1A`!!3!q3Qi!$NcI'!"1AL"IA%p1d%j@rrT)j`% )3UF[2!!!!9*1V3'U+&mJ$'BJ3UG)HJ'N6Ud#BN+R5(S"Y%kY!Q*1V3"53Ui!$'! !!A"#V!!'3Q`!,%*X!#j#E!!`3Q`!-N*X!$3jEJ!+!#BjEJ!)!#4#V!!S1A`!$!" #F#3T3!!f3Q`!8%)X!%3[,!!fF!-[!%kY!X)TA`!k$+`!!!0J!$T[##Pm!!!$B!! k$+`!!!*B!$TX##Pm!!!#@!!k3Q`!2MPm!!%!3$Pm!!3!)N+R6Ud#NLPI!"K+V!! BCLC#TdKk!241V3*L3UG)HJ$q6Ud#BNkY!&)[$%kY!**#VJ!-B!!!ZN+R2c`#!%* R6Ud$8LPI!!T+V!!+CM"#TdKk!,K1V3*L3UG)HJ#d6Ud#BNkY!&*#CbmX!"K1V3+ L(Km[$%kY!**#VJ!-B(*#Th"3,`"1V3'U+9m!5NUX!%TQ1%+R5(S!G%kY!Q*#TdK k!&T1V3*L6Ud!8N*R,b`!'%kY!U)H(bmX!!T1V30b,`a1V3#53Ui!$'!Q)'`!5M& mrrm!'#PYqN!!&%+83L`!"#m-3Hhk8Lm)6Ud!QLe-!!a-ha#!6PiZRdje$h"KFQ& YCA4PFL"LE'pMD`484P43!!e[GA4`GA3JF'&MDf9d"A4TE@9b%'0[EQjPBh4TEfi JBQa[BfX!6PErq%MR"`JSEJ!)6Ud#LJaX!!-!*'FZ3QF[,!"+3QG1V3(#2KmJE!" +3UJ!%L"X!%SaE!"3!"C#CbmX!%T#CdkY!GSq(`aX!!S!*PI!-L`!*'XBXR`!"fi 53IS!L$3"jNT%3J-`)2m+2!!%9m(!!@F53UG)E!"56Ud#BMmX!&"1ZZ(!,b`!"Nk Y!f*#CbmX!"K1V3+D("p#CbmX!"K1V3+L("m[,!"+6Ud!NLmX!!T1V30b1A`!!`! F+L`!+%*R,`a"lIT5,`K1V3#+2Km[$%kY!*)Y43!-60m3i%jH,Tp1G3!86PErr%M R!"JQEJ!+3UF[+`!+6VVF@LKI1,`!!cPV!"!!!N*R,`X`,J!)@%!r!%kkr'!pA`! 160mB!%jH)&pF6dl36PErqNMR!4JSEJ!13UF[,J!+6VVF'#CI-#X!!V"X!""R$$! X!$"53$P!!$"J)#m-6VVfXN*R,b`!'%kY!TSH(cPm!!B!(#mX!"41V3+#60mBJ%j H)&rHr!!+6Y"19[ri51F('#KZ!!JQEJ!51Li!%%kY!')`,!!X8N!j3!!X3UF[#dk kfkJZ(b"(2"!J4c#'-!CA3'F-8d"R*&0!CcTJ!!#'3QF[$#m,6VS!UK!IC`S[$#m ,2`91Z[JDB!!!JN*R,``[#dkk!)i3(fF+,``[#cm&6VVr,'"Q5Q`!%PI!)'`!"NM RJ)"#TdMRJ)"#Tbm,6Ud"5L)I60m"!5m"6Ud$HL)I60m"!5*"-LJ!"V*49m'!!@F 3,``[#cm&6VS"9Lm-6VVLN!"J'#mX!!B[#cmm!!4#TdKk!"j1V3*L6VS!e#m,6Ud $FNcI'1"1AL"Ih[`!$Nl3!5"19[rd51F$'#KZ!!`X,J!)3UF["NkkfX)QAcGV!!) !!N+R3UF["NkY!8T1V30k)&mq%%TX!"*Q1$!X!"#`D`!#CK!jI!!"!")JE!!'-8F !"Q"',b`!"Lm'3QG#TdKk!%j1V3*L6VS!9N)Z!""J-'!S)'`!"VjS!!CR(LmX!!B ["Mmm!!9#TdKk!#*1V3*L6VS!,%)Z!""J"Kem!!%!%%cI'-"1AL"I8%p1d!%J$Qp XC#"MEfjZC@0dD@pZ!%j@rrC)j`-B+'i!%LCZ!!iq,!!',#`!#%+R3UF[#dkY!8T 1V30k)&mj8!!'3UF[#dkY!8SJAbPS!!`!##m-,`Xr,J!-,bi!#%kkhj!!18F!"LP '!!K-haM!6PiJAplm!!j1d%j@r[K)j`%)3UF[,J!+6VVCULKI)!aBJ#m!3Hlqr#m )6VVCr"!Zr[`#3!$r28$qq(i"B$K"l[lm%$"`!!*!!2m-3!!09m""l[lm%M"`!!* "!2m-33!+9m'!!@F-)!G"l[lm%E`!)!!!8NGT"VjZr[K[`N+R5(S!+%kY!Q*"l[l m,`K1V3#U)'i!$M&m!!8!(%cI%)"1AL"Ih[`!#Nl3(e4'9&!k)%9bFQpb)'CbEfd JCQpbC@PREL"SEh0d1L!!!!AJ![J!"8j@!!"1ANje6PErq%MR!`JSEJ!)%"3#3!$ rDaL`I!!2EK*"qJ%L-J$Q584"!6!3r`Sm!!4R#%+Z!!aJ!!$S3SF3&!*!!2mp32r iI!*J!!$+%$4J!!*!!2pV',"m!$pZ%N(k!1)b!1C*4%%"-"$r#M`!"'BD)!ITJ") dB!!#33$rNR`!-%M"dS!Z!@!!!)J30'!!!N!!rfXBX(`!Efi53IS!R$)!jNP%33% `%2m+2!!%CK`J"qQ!%M4J!!*"!2q5I!"KdR`!#NM"dS!Z!@"'%$4J!!*!!2pV'," m!%pZ%N(k!&Jb!1C*4%%"-"$r#M`!"'BF)!ITJ")dB!!#33$rNR`!3G*m!!T)`G+ !,J&J"L!(kB!Z!&*'D3LmE[riE`$r-Le(!!a-ha$!6PiZRdje!(i!N!`$r`#3"J2 m6PErlNMR$`JSEJ!)F!%G[2q!!2&`!Kfm!!)!mA!$3MB!mA!%3MB!mAi"I!&J!!# '3N83&!*!!2q`4ec!%M4`!!*"!2pV',*m!$pZ%N(k!-)d!HC+4%)$-#$r#M`!"&I "`!&R*#!&jd!50(!!!N%!rp*!NR`!-$S"$%8!rfm'3Ui!$'"k8NGJUL!'(B8!qcJ '%"3#3!$rX%GY)K!dF!!#3!$r$%!!,'B%8NGJ"N+Z!!aJ6&*'$%B!"'m!rhB3&!* !!2q`4fd'3Ui!$'!b2A`!"2rkB"``,[rk%MC!q`*"!2mGJ3$a8d3-4!!"E3a6E[r k$'i!!IrkE0`YE[rb!!a-ha$`6PiZRdje!2m!N!C19[rZ51F2##KZ!!K`!4fmri! !mA!#(E`!!J$aF!0#0J$aF!4#0J$aIJ&m!@!!!)K#44!8!N!!rl"(A-!50(!!!N% !rfXBXR`!2fi53IS!a$3"jNT%3J-`)2m+2!!%9m(!!@FQ)!A"r!!+%M4`!!*"!2r 53**m!$!k!3a&!2p["N+Z!!aJHP*(B+JJ"Kf&!2Xi"K!8!N!!rl"(E5)30(!!!N! !r`a!!#jQ"&*(B!C#VJ!-B%a54Ja'!!4[!2pd%"3#3!$rX%GY"N+Z!!aJ-Mem!!6 rqQ!F-#lrqK)f32X#33$r(B%!m90%$%3!!@d-8flrqJaZ!!(rqQcF,@lrmJ!-60m 3m%jH,Tp1G32r!*!'6PB!!#mYp$j1V3+#6PiZRdje6PErl%MR$aJJEJ!),`K)HJ( 16Ud#bK!IC`SYEINB!!aJ!!'F)'i!#"!3!N!!rc`!3UFJ"PC!2`"#CdkY!e)QAb! ,CL"#TdKk!BC1V3*L3UG)HJ'%6Ud#BNkY!&*#VJ!-B!!"A%+R3UG#Tbm,6Ud"5Nk Y!hT1V30U+&mB[!!"8S`BKP+-28Erl(i"B"3JEJ!)%$"`!!*!!2mBJ&+-8NGT"Vj Zrqa[jN)8+fhk323q3Uhd3N*Yp&T#4f"X$%F!!@B#B'SJ"d(Yp%EP3%MRJ)"#Tb) (3qhi"Z9",c%3!$mm!#T)ji#!3QG1V31+-Kp-h`%"2`&$qJ$Z,`P#TdkY!i)L(dc I!3%KJ3!!3QFJ"d(Yp%EP3#m`!!![#b!'9N!r!%kY!jSi(e*($%F!!@q1,`Y1V30 b3UG1V3+5+Kp+K@BH3UG)HJ#-6Ud#BN+R5(S!I%kY!Q*1V3"53Ui!$'"LF!N[!%( krS`[#%+R,`91V3+U6Ud#DN*R,`91V3+D%"p)J$J!3QF["8kY!U)3(dL!1!"#4f! 5)!G"lI4'j8![-!!!6Ud$BP*($%F!!@rS5Qhd@QB)F!%Y3!!-B!BYEI4#!!a-haM `6PiZRdje"A4TE@9b"e9%8%j"688'F'&MDf9d!!*YC3"19[ri51F!'&*Yp&T#Td+ R3UF[,J!56Ud"5NkY!hT1V30U+&m3,!!"!N!!rdM!d)a8J#K!%"3#3!$r$%!!!PI !5Uhd3PI"`!&R%N+R6VVp[L!-9)!Q3#Y6p%*J$JaY!!(d@QB'3UG1Z[fN,bi!%Nk Y!h*-haJ!6PiJAplm!!j1d!#3!a!$)!!"6PB!!%kY!aT1ANje!!!(i!-S!!&19[r m,`HT-$em!5$rr(!"(E`!&!$m3UFr2!!"5'lrr+Na+errm#mYrr![1J"5U8e#Tcm m!3#T[bYIrr4#Tcmm!3'T[bYIrrK#Tcmm!3+T[bYIrraq!@!1)!IP3#me!1a#CkN e8NF-4`!%EqbT0bmYrr3r2!!%U6SZ(djH6R9%8PC56PB!!#"m!!!*##e3!!K1ANj e6PB!!$Ym%!$kSMYm%!$i#N+R6VVrfM!Yq!V3EIULd(`)!0"m#!$3I"J!d(`)!%M !)Kq5J#m"6Ud#)NkY!AT1V3'+6Ud"LNkY!BT1V3'+6Ud"LN(Yp5B[#+KZU2ir22r r3QFJ(k!bU4*1Z[m#3UHTHkK3UFa"lIr)3qhdXL$C)0P)EIr!2c`!"$mm!"J`,Ir 1@8!r!$!YrmaC3$m!U+G1ZJ$'3QG1V3'55PpR%%+R5(S!PNkY!Q*#CdkY!,T#Tcm YqU*1ZJ$'+erk4%kk!5j1ZJ216VS#qNkk!hj1ZJB%6VS'8"Ym!!(q["Ym!!(q[4Y m!!(q[N)YrVp#,IUm3Lhl[%)YrEa"lIbm3qhi'("!)0P63'lk1h`!!IUb,bhrq$m m!!%I2!!"U89#EIUi'h`!!IUa3UhkV%+YqUK#,IUP3Lhrl8jH6R8I3f&Z*h3JEh" PEL"0B@-J8(*[G'pMEf`J8'&MDf&RC8j@!!"#Tcmm!$&#Td+RUA`VArBd3Qhe+N+ Yp5a#VI8`6Pj1G8j@rrT)j`%)3Qhk2N)Yq6e#VIN`-#i!#0"m!"!q!#!(!N!!!@F #8NFr"dkY!PT"lINJ+%JV62T!3L`!$NkY!Z)[$%Kk!#C1V3,U18F!#%(Yq5!T5!! ++8`!"#e-!!T-ha#!6PiJAe426Y!%6@&TEJ"19J!!3LhkRd*YqT4#VIU@3UhkQN+ YqT!!6Pj1G8j@rr4)j`%)+'i!#%*R5'lrp%KZrrC1V3%+5PpR"%*Zrr3`,[rd5-$ 3[)!#!!!SJ#PmJ!)!3!!%+Ab!!J"!!!JTI)!#!%!!$%(X!""$qJ#`)0NJf6#43H` "%N2k!*SJf5$C'A`!!3%33QG)HJ"d3QG)E[rm6Ud!iMiI5NGQ@#em!!!#%[ri3QF r,[rm2c`!!8+R6Ud#-MiI5NGQ%N*R2blrr%KZrrJ[$%kY!1Sq(d*R2blrr%kY!0S q(a!X!4"R&N+R,a3[22q3!`#S@$!Zrr4)`0#I+)"-ha#!6PiZRdje&%0eFh4[E@P kBA4TEfiJ9Q&XG@9c!!G9EQYZEhGZ#%&`F'aP68&$!%j@rra#EIC%3Qhf3N*YpN" #EIBq3Qhf2%*YpMT#EIBi3QG)E[rm5'lrrNkY!3T+AfF#B'iEE[rppX!lE[rqpVj `5"Y!pX&#EIDk-$`!raY!pVa`5"Y!pVdEI2rrpNG#,IC'3Uhf`N+RF"J[!%kY!DS VArDf3UG1V3$k+erfaN+R,bhk3%(Y!#S[#$mm#!")HJ!@3UG1V3*k+erfbLeYpXS !#%jH6R8)39*3)&*PBhB!6PErrLm(3NGJ'L!(3HhfjZG!3V!!!#!(3HhfjZG!3V! !"&*($%F!$frJ3Qhfj$Ym!!2fhN+YpYSlI!!"pY*#VIE@3Qhfe%+R2c`!!8(Y!4S [#%kY!9SVArEJ5Uhfi'BB3UG)HJ!H6Ud#BN+R5(S!%%kY!Q*1V3"D,Kp1ANje!NP 3!!4*3de3!%j@!!"#Tcmm!!0"l3%5,`K1V3&D+erfcNUYpXjQ'%+R5(S!)NkY!Q* #TdKk!!j1V3*L6Ud!@NjH6R8)5@jdCA*1CA3!!dG(8%j@rGK)j`m)3Hlpk#m)6VV pD#YZrHMj'#YZrHcj(#YZrI$i"LYZrI6i!N(YprK$l[hi)0NJf6#4'flqq2JA3Hh i'%2Zr[T`3#$C8d"ZqN*Ypk*#EII11h`!!II-3Qhhj%*Ypq*#EIIJ3QhhhN*Yppa #EIID3Qhhf%*YppC#EII83QhhdN*Ypp!S2!!!J!"#Tbki!USJAbm)3UFZZ!+U)"m JAb)3NS![!5m%6Ud#ZLSIA)8m2!*B3Qhi$%+Yq!j#VIJ53QhhlN+Ypr"#VIId)!9 6J$e!rGK#4f"J3UG`%#m!6Ud"ULKI)!aQ%%+R5(S"0%kY!Q*#CdkY!,T#,!!%3U` !#N*X!!j#TdM',`C1V3'U+9m!"NUX!!CQ%%+R5(S!fNkY!Q*#CdkY!,S[$%(Ypqi [#%kY!*T54fN'[Qlpf'qD3UG`'Lm!6Ud"SLYIpqSJEIIU)&"#D!!#3UF[,IT!3Hd "+Lm)2bhi#NKk!)C#TdkY!RSVArIQ,bhhjN+R6VVp!N(Yq!`[#%(Ypqi[#%kY!A) pI!")rHC#CdKZrHC"l3&U,`K1V3"k5PpR'%+R5(S!0%kY!Q*#TdKk!"*1V3*L6Ud !@NcI%2"1ANje&R0[BfYPG#"QEh)J3A"`E'9LGA-J59!!$84%8%p`C@j6Ef0VCA3 (59"%C@eeH#K5B@iJEh9d)'pQ)("KBfYPG#"cG'pbB@GP)'4eFQPZCb"TEPpTEQP d!"jTEPpTEQPd1L"MB@iRG#"YB@YP)'CbC@8JFA9PG@8!6PB!!%+YqNa#EIT33UF r2!!43Hd$SLm)6Ud"@LYIqNK+VIT)CKK#TdKk!#*1V3*L3UG)HJ!16Ud#BNkY!&T 1ANje#%PZG'9b6Q9d!!094&"19J!!3QhkEN*YqP*#VIT83Uhk@%jH6R8!!!6d!c! !#dj@!!"#TbmZ!!K1V3%k,9m!$%jH,Tp1G8j@!!!J,J!)8)!Y3!!-6PiZRdje6PE rrLm(-#i!#P"!8N!q!#!(!N!!!@F#8dG#Tcm(2bi!#%kY!6)YA`!-,Kp1ALkI6R9 19J!!,bi!#%kY!8*1ALkI6R919[rf51F!'#KYqN`J$'F`-#`!","Z!""A`$)X!!D bEJ!59m(!!5)X!!LbVJ!89m(!!@F'3Ui!''"Q*N`S9'$-3UG`&#m!6Ud"ULKI)!a Q(N+R5(S!CNkY!Q*#TdKk!&"1V3*L6Ud!8N+Z!"KJ-NUYqNaR"#D-B!3V62T-3T3 jEJ!3!!3jEJ!5!!BTEJ!8!!JTEJ!-!!`TEJ!)!"!Y6!!B60mB!%jH)&rHr!!36Y! +BfpZEQ9MG'P[EJ!$98436PErr#m-+'hk6#!-Cb!`,!!'X'i!#&I!)L`!#,+Z!!T A`F!"C`4J"Q!#+&4Jh#e-!!iSAdjH)&pF6dl36PErr%MR!"JQEJ!))!YQ!Q!`Yqh k6'B+)'hk6#Y3qNaJ'LKYqN`J$'F+Yp4Q!Q!%+&4JmL!-CJ*J##L6,`Y1V3#560m B!%jH,Tp1G8j@!!"+EIT3C``pEIT3!!K5EIT3B#4#Cd+RUA@SDcYIqP!-E32SqP" X"JCY!qMk8$eYqP!!#&*YqP"1ANje6PErf%MR$aJX,J!16Ud!BN+R,`C1V3&++Kp #Tbm&6VVpl#KI1#`!",KZ!!a[#Lm'6Ud"3Q!!!2`q,!!'5NGR!!#3!"!Z!!d#3!! "Ca)`,J!-5-$3M#e!rpSJE[rD3K!YEJ!)rq`J45eS!"$rm(!!(8$rp(!4(8$rp6e X!!6rpN*R3Hlrl#m)F!a)`)(m!!)r!%kY!'SjA`!'3UG#Cbm--#i!$&*!5-#"r!! #2`"1V3"U-"p)`#m!U&SJ(ce!rqBj4`!'[QlrjQF),`C1V3&#B')QEIT-)!YR1NT V!!4@`$)V!!5bE!!#9X(!!@F%*P0J)%UV!!aR'#m')!443$m!,bi!##mV!"![+`! -6Ud!5Q!LB-*#CbmZ!!K#Tbm'6Ud"5Mmm!!01V3%L29rrf#m'6VVp1NcI'2"1AL" Ih[`!#Nl36PErjNMR"aJU,J!13UG#TbmZ!!T1V3&+6VVmXLKI-#i!#&"!2J!J"`* !!!&R#NM()!c3KbC!3K-J45`S!!Jj4`!%)%8iU!!%)%8jD!!'!!*#Tbm'6Ud"8Le Irr!Y4[rdF!!G32riF"%G32rj2@`!"2rk3QG"l[r`,`K`$%M!JI`!!Mm!6Ud!DMP I!!C#Td*R,``J"e*!5-#"r!!#2`"1V3"U-"p)`#m!U&SJ(cP!!!C#CbmYqNJ[,J! +2`F["NkY!@)pA`!560mBi%jH)&rHr!!+6Y"19J!!,``SEJ!),bi!$%KXr[a1V3' k,bcqpMmXr[T)E2ld5'cqm%KXrZLTM5mXr[")E2lmUBmSAdjH)&p36dl36PEqk#m -3UFr2!"G3UG`rbm!UA`YArlf+'hk6$em!!EqqL!-Ch3`,!!%5-![!#m16VVrMP* Zr[S`,!!'5-![!#m16VVrI&*Zr[S[,!!)5'lqr%kY!()[,[lf2blqqNKZr[4)E[l `5'lqk+Q0,blqm%KZr[bTMe*Zr[S[,!!-,`j1Z[mq8QlqqLmX!"![$Nkkrc"5E[l k+&4JL#mZr[C1V3'D+&p1ANje!*$r!*"j(!)5!!P84P43!*!$8NC548B!N!0H3Nj %6!#3!fT"6&*8!!%!GN4*9%`!#3#14%a24`!(!3C048j9!!)"CNP$6L-!!!'+8dp '9!!!!CC$6d4&!!B"SJ!!rrm!N!Q!rrm!N!-S!*!&J2rr!*!$-`#3"!&0rrm!N!0 6!*!%!TVrr`#3!f-!N!84rrm!N!0c!*!&02rr!!!"C`#3"62rr`!!!HN!N!8prrm !!!)e!*!%m&rrr`!!!UN!N!8qrrm!!!1R!*!%!Ecrr`!!"%F!N!3$#Irr!!!%U`# 3"8lrr`!!"@-!N!9Hrrm!!!Gj!*!&#rrr!!!)E3#3"6,rr`!!#)B!N!8arrm!!!L I!*!&22rr!!!)`J#3"2"Irrm!!!MY!*!&(rrr!!!*"J#3"8hrr`!!#6%!N!9Grrm !!!PB!*!%!3$rr`!!#Am!N!3"!Irr!!!*cJ#3"!%#rrm!!!T9!*!&J2rr!!!+``# 3"3%!!"!!#mF!N!Errb!!,S%!N!8"rrmd!$)G!*!&![rr-!"JU`#3"32rrc!!NXd !N!8%rrm`!*La!*!&"Irr-!#Ba3#3"3Errc!!S+N!N!3+8dC04f9d4QPXC3#3r`# 3j#k'!: !E!O!F! exit -=- Tim Maroney, Professional Heretic, CMU Center for Art and Technology tim@k.cs.cmu.edu | uucp: {seismo,decwrl,ucbvax,etc.}!k.cs.cmu.edu!tim CompuServe: 74176,1360 | God is not dead; he just smells funny.