; File HOST.KSC - "Host mode" script for K-95. ; ; Assumes client is ANSI or VT100 terminal with 24 lines. ; Protocol operations use APC for automatic up/download, ; but don't require it. ; ; Works on serial and TCP/IP Telnet connections. ; Assumes the connection is already made. Designed to be ; started from HOSTMODE.KSC, which waits for the desired ; type of connection to come in. ; ; Kermit 95 1.1.17 or later is required. ; ; Copyright (C) 1996, 1997, 1998, 1999, 2001 ; Trustees of Columbia University in the City of New York. ; All rights reserved. Authors: F. da Cruz, C. Gianone, J. Altman. ; ; Version 1.00: February 1996 for 1.1.3. ; Version 1.01: 8 June 1997 for 1.1.12: ; . Add "set transmit echo off". ; . Change "clear device-and-input" to "clear input" in GETMENUITEM. ; Version 1.02: ; . Fix problem with requests to send file groups ; Version 1.03: 9 July 1998 for 1.1.18 ; . Fix conflict with FAIL command added in 1.1.17 ; . Fix potential error if _mypriv is non-numeric ; . Make sure Autodownload is OFF ; . Add SET TELNET REMOTE-ECHO commands ; Version 1.04 2 September 1999 for 1.1.18 ; . Disable TELOPT commands ; . Add support for Telnet Authentication ; Version 1.05 6 April 2000 for 1.1.20 ; . Change default locations to reference \v(exedir); not \v(startup) ; Version 1.06 11 Oct 2001 for 1.1.20 ; . Fixed FAIL to STOP rathr than EXIT and changed its name to BAIL. ; . Modernized command continuation and macro definition syntax. def _VERSION 1.06 ; Version of this script ; MACRO DEFINITIONS ; ; HOSTLOG writes actions to the screen and to the transaction log. ; See subsequent redefinitions below. ; def HOSTLOG echo \v(time) - \fcontents(\%1) ; BOXMSG prints an attention-getting message on the console screen. ; def BOXMSG { asg \%9 \frepeat(=,\flen(\%1)) ec \%9, ec \%1, ec \%9 beep } ; LOCK and UNLOCK are for use when updating the user database file, ; to prevent people from writing over each other's changes. ; def UNLOCK { if not def _locked end 0 if exist \m(_lockfile) delete \m(_lockfile) undef _locked hostlog {Userfile UNLOCKED} end 0 } def LOCK { if def _locked end 1 if exist \m(_lockfile) end 1 open write \m(_lockfile) if failure end 1 writeln file \m(_username) if failure end 1 close write if failure end 1 def _locked 1 hostlog {Userfile LOCKED} end 0 } ; SPLIT and GETFIELDS are for parsing user database records. ; def SPLIT { asg \%9 \findex(_,\%1) asg _LEFT \fbreak(\%1,_) asg _RIGHT \fsubstr(\%1,\%9+1) } def GETFIELDS { split {\%1} asg U_ID \m(_LEFT) split {\m(_RIGHT)} asg U_PW \m(_LEFT) split {\m(_RIGHT)} asg U_PR \m(_LEFT) split {\m(_RIGHT)} asg U_NM \m(_LEFT) split {\m(_RIGHT)} asg U_AD \m(_LEFT) split {\m(_RIGHT)} asg U_TP \m(_LEFT) split {\m(_RIGHT)} asg U_EM \m(_LEFT) } ; Make a variable: name is first arg, value is second. ; def MAKEVAR2 if def \%2 _assign \%1 \%2, else _assign \%1 ; Make a variable from single argument NAME=VALUE ; Creates variable called "_NAME" with definition "VALUE" ; def MAKEVAR { if = \findex(=,\%1,1) 0 end asg \%9 _\freplace(\%1,=,\32) makevar2 \%9 } ; BAIL handles fatal errors ; define BAIL { hostlog {In \v(cmdfile) at line \v(_line)...} hostlog {Fatal error \v(errno) \v(errstring) - session closed} beep goto badend } ; SAVEUSERDB saves the user database ; define SAVEUSERDB { if exist \fdef(_userbak) del \fdef(_userbak) rename \fdef(_userfile) \fdef(_userbak) if failure hostlog {Warning - Failure to back up user database} open write \fdef(_userfile) xif failure { hostlog {Can not open \fdef(_userfile)} UNLOCK end 1 } for \%i 1 \&u[0] 1 { writeln file \&u[\%i] xif failure { hostlog {Error writing record \%i to \fdef(_userfile)} hostlog {Old version preserved as \fdef(_userbak)} break } } close write asg \%9 \v(status) if not = \%9 0 hostlog {WARNING - Failed to close \fdef(_userfile)} else hostlog {\fdef(_userfile) saved: \&u[0] records} UNLOCK end \%9 } ; CONFIGURATION: Defaults in case the config file (HOST.CFG) gets lost... ; asg _maxusers 100 ; Maximum number of users in database asg _inactivity 1800 ; Logged-in inactivity limit (seconds) asg _logintime 300 ; Inactivity limit while logging in asg _anonok 1 ; Anonymous logins OK (0 = not OK) asg _logging 1 ; Logging enabled (0 = skip logging) asg _dlincoming 0 ; OK to download from INCOMING dir asg _msgmax 200 ; Longest message size (lines) asg _protocol kermit ; Default file transfer protocol asg _xfermode binary ; Default file transfer mode asg _owner THE PROPRIETOR ; PC owner's name or company asg _herald Welcome to K-95 Host Mode ; Main screen title asg _public \v(startup)PUBLIC ; Directory users can get files from asg _incoming \v(startup)INCOMING ; Directory that users can send file to asg _logdir \v(startup)LOGS ; Directory for host-mode logs asg _usertree \v(startup)USERS ; Root of user directory tree asg _tmpdir \v(tmpdir) ; Directory for temp files if not def _tmpdir asg _tmpdir \v(exedir)TMP asg _userfile \m(_usertree)/USERS.DAT ; User database file asg _greeting \m(_usertree)/GREETING.TXT ; Message/greeting text filename asg _helpfile \m(_usertree)/HOSTMODE.TXT ; Host-mode help file asg _msgfile \m(_usertree)/MESSAGES.TXT ; Messages for proprietor ; Now read the configuration file. ; Note that the name and subdirectory are hardwired. ; asg _configfile \freplace(\v(exedir)scripts/host.cfg,/,\\) asg _mypriv 0 if not exist \m(_configfile) forward noconfig open read \m(_configfile) if failure forward noconfig while true { read \%a, if failure break, makevar \%a } :NOCONFIG ; END OF CONFIGURATION SECTION dcl \&u[\m(_maxusers)] if not def _lockfile asg _lockfile \m(_usertree)/USERS.LCK if not exist \m(_userfile) - if not eq "\m(_anonok)" "1" - stop 1 Fatal - User database not found and guest logins are disabled. asg _userbak \freplace(\m(_userfile),.DAT,.BAK) ; Name of backup file ; CD to where the user directories are. ; cd \m(_usertree) if failure stop 1 Fatal - Can't change directory to "\m(_usertree)" ; And then CD to its parent. ; cd .. if failure stop 1 Fatal - Can't change directory to "\m(_usertree)/.." asg _startdir \v(dir) ; Host-mode "home" directory. ; Create needed directories if they don't exist. ; if not directory \m(_incoming) mkdir \m(_incoming) if not directory \m(_incoming) stop 1 Fatal - no INCOMING directory if not directory \m(_public) mkdir \m(_public) if not directory \m(_public) stop 1 Fatal - no PUBLIC directory if not directory \m(_usertree) mkdir \m(_usertree) if not directory \m(_usertree) stop 1 Fatal - no USERS directory if not directory \m(_tmpdir) mkdir \m(_tmpdir) if not directory \m(_tmpdir) stop 1 Fatal - no TMP directory if eq "\m(_logging)" "1" - if not dir \m(_logdir) mkdir \m(_logdir) ; Not fatal if this fails if exist \m(_msgfile) boxmsg {You have messages in \m(_msgfile)!} ; SETTINGS... ; set input echo off ; Keep host PC screen clean set exit warning off ; ... set file display quiet ; ... set case off ; Ignore case in string comparisons set delay 1 ; Delay in starting file transfers set file type binary ; Transfer mode is binary by default set transmit prompt 0 ; No line turnaround on TRANSMIT set transmit linefeed on ; Keep linefeeds when transmitting set transmit echo off ; No echo during TRANSMIT set file char cp437 ; For PC-format text files set file names converted ; No weird stuff in filenames set receive pathnames off ; Strip pathnames from incoming files set send pathnames off ; and outbound pathnames too set file collision overwrite ; Overwrite incoming files by default set input autodownload off set terminal autodownload off if >= \v(xversion) 1118 { set telnet remote-echo off } if < \v(xversion) 1118 { ; If we are a Telnet server we need to control the echoing ourselves. ; if not = \findex(tcp,\v(connection),1) 1 forward NOTELOPT ; ; Ex-post-facto Telnet "negotiations" to undo whatever might have been ; negotiated already. K95 normally is a client, but now it is a server. ; telopt will echo ; I must echo if failure bail ; Make sure this does not fail telopt dont echo ; You must not echo telopt will sga ; Suppress Go-Ahead telopt wont ttype ; No terminal type negotiations telopt wont naws ; No screen-size negotiations } :NOTELOPT def ERROR msg {\%1}, sleep 2, def _status 1, goto main def FATAL msg {Fatal - {\%1}}, sleep 2, fail hostlog {\v(date) - Start host script \v(cmdfil)} hostlog {Current directory: \freplace(\v(dir),\\,/)} ; Macros for screen formatting using VT100/ANSI escape sequences define clr output \27[H\27[2J, if failure bail ; Clear screen define cur output \27[\%1;\%2H, if failure bail ; Position the cursor define atp cur \%1 \%2, out \%3, if failure bail ; Print text at cursor pos define cleol out \27[K, if failure bail ; Clear to end of line ; Returns basename of DOS/Windows-format filename argument \%1; ; that is, the filename stripped of disk letter and/or directory path, if any. ; define BASENAME { asg \%9 \frindex(:,\%1) asg \%8 \frindex(/,\%1) asg \%7 \frindex(\\,\%1) asg \%6 \fmax(\%9,\%8) asg \%6 \fmax(\%7,\%6) return \fsubstr(\%1,\%6+1) } ; Print message in message area - line \%M column \%C define MSG cur \%M \%C, cleol, if def \%1 out \fcont(\%1) ; The next two are used with "Leave a message". ; \%k is the message line number, global, don't use for anything else. ; def ADDLINE incr \%k, asg \&a[\%k] \fcontents(\%1) def NEWSCREEN def \%L 1, clr ; Erases a character on the input line and from the input variable \%n. ; define BS { if not > \flen(\%n) 0 end 0 asg \%n \fsubstr(\%n,1,\flen(\%n)-1) decr \%p, out \27[D, cleol, end 0 } ; GETMENUITEM reads a number into \%n, using the echo line, \%L. ; The argument (\%1) is the label to jump to if the menu needs to be repainted. ; The minimum value is 1, the maximum value is the second argument, \%2. ; The prompt is "Enter choice: ". ; define GETMENUITEM { atp \%L \%C {Enter choice: } :NEW asg \%p \feval(\%C+14) asg \%q \%p def \%n :INLOOP1 clear input cur \%L \%p cleol input \m(_inactivity) if failure bail if eq "" "\v(inchar)" goto inloop1 asg \%9 \fcode(\v(inchar)) if = \%9 9 asg \%9 32 if = \%9 127 asg \%9 8 if = \%9 8 { bs, goto inloop1 } if = \%9 21 { cur \%L \%q, cleol, asg \%p \%q, asg \%n, goto inloop1 } if = \%9 3 goto main if = \%9 12 goto \%1 if not < \%9 32 forward graphic if not def \%n goto inloop1 forward gotit :GRAPHIC atp \%L \%p \v(inchar) incr \%p if eq \%9 32 { if def \%n forward GOTIT, else goto inloop1 } if not numeric \v(inchar) { msg {"\v(inchar)" - Not a number}, goto new } if not def \%n asg \%n 0 asg \%n \feval(10 * \%n + \v(inchar)) msg goto inloop1 :GOTIT msg {Your choice: \%n} if not def \%n goto \%1 if not numeric \%n goto \%1 if > \%n 0 if not > \%n \%2 end 0 msg {\%n - Out of range} goto new } ; INTEXT reads a line of text from user on the input line, \%L, allows editing ; with Backspace or Del (erase characters), Ctrl-U (erase line), etc. ; Can be interrupted with Ctrl-C if _ctrlc is defined (it should be defined ; as a help string to be printed). ; ; Terminates on space or any control character except BS, Del, or Ctrl-U. ; The techniques used for reading and echoing characters are designed to work ; on both serial and Telnet connections, even whe Telnet echoing is ; misnegotiated. ; ; Argument 1 is the input (echo) line number. ; Argument 2 is the prompt. ; Argument 3 is 32 to break on space or less, 31 to break only on control ; chars (use 32 to read a word, use 31 to read a line of text). ; Argument 4, if included, is the timeout value. ; Argument 5, if included, is a char to echo in place of what was typed. ; ; Returns: ; 0 on success with \%n set to the text that was input. ; 1 if Ctrl-C was typed ; define INTEXT { if not def \%3 asg \%3 32 if not def \%4 asg \%4 \m(_inactivity) if def \%5 if > \flen(\%5) 1 asg \%5 \fsubstr(\%5,1,1) def \%n asg \%L \%1 asg \%p \feval(\flen(\%2)+\%C) asg \%q \%p atp \%L \%C {\%2} if def _ctrlc atp \feval(\%L+1) \%C {\m(_ctrlc)} :INLOOP2 cur \%L \%p cleol input \%4 if failure bail if eq "\v(inchar)" "" goto inloop2 asg \%9 \v(inchar) asg \%8 \fcode(\v(inchar)) if = \%8 3 if def _ctrlc end 1 if = \%8 9 { asg \%8 32, asg \%9 \32 } if = \%8 8 { bs, goto inloop2 } if = \%8 127 { bs, goto inloop2 } if = \%8 21 { cur \%L \%q, cleol, asg \%p \%q, asg \%n, goto inloop2 } if > \%8 \%3 { asg \%n \fcontents(\%n)\fcontents(\%9) if def \%5 atp \%L \%p \%5 else atp \%L \%p \fcont(\%9) incr \%p goto inloop2 } if eq "" "\%n" goto inloop2 end 0 } ; Displays a text file on the user's screen ; def DISPLAY { hostlog {Typing \%1} asg \%9 \v(ftype) set file type text output \13\10\10 transmit \%1 asg _status \v(status) set file type \%9 out \10\13Use scrollback to view any text that scrolled off the screen. out \10\13Press any key to continue... input \m(_inactivity) end 0 } ;+------------------------------------------------------------------------ ; LOGIN procedure ; asg \%C 1 ; Left margin column for login procedure. asg \%M 6 ; Row for messages. undef _ctrlc ; Ctrl-C disabled during login process! undef _locked hostlog {Connection from \v(line)} clr atp 1 1 {K-95 Login - Initializing...} if < \v(xversion) 1118 { msleep 2000 ; Wait for TELNET option replies clear device-buffer ; and then clear them out } hostlog {Auth State = [\v(authstate)]} hostlog {Auth Name = [\v(authname)]} hostlog {Auth Type = [\v(authtype)]} hostlog {User name = [\v(user)]} if eq "\v(authstate)" "valid" { asg _username \v(user) def ok 0 if not exist \m(_userfile) forward AUTHBAD open read \m(_userfile) if failure forward AUTHBAD :AUTHLOOP read \%a if failure forward AUTHDONE getfields {\%a} if not eq "\m(U_ID)" "\m(_username)" goto AUTHLOOP def ok 1 :AUTHDONE close read if > \m(ok) 0 forward AUTHGOOD :AUTHBAD hostlog {Access denied "\m(_username)"} undef _password if count goto again msg {Access denied - hanging up} incr \%M msg hostlog {Invalid user - access denied} bail :AUTHGOOD undef _password asg _myname \fdef(U_NM) asg _mypriv \m(U_PR) if not numeric \m(_mypriv) asg _mypriv 0 msg {\m(_username) authenticated by \v(authtype)} beep if >= \v(xversion) 1118 { set telnet remote-echo on } undef noecho forward LOGGEDIN } if eq "\v(authstate)" "user" { asg _username \v(user) forward GETPASSWD } set count 3 ; Allow three tries to log in :AGAIN ; Login retry loop clr atp 1 1 {K-95 Login} if < \v(count) 3 { msg {Access denied}, sleep 3 } def \%L 1 define noecho if >= \v(xversion) 1118 { set telnet remote-echo on } clear device-buffer ; Don't allow typeahead intext 3 {Username: } 32 90 if failure bail asg _username \%n if not eq "\m(_anonok)" "1" forward GETPASSWD if not eq "\m(_username)" "guest" forward GETPASSWD msg ; GUEST user asg _myname {Anonymous Guest} asg _mypriv 0 msg beep if >= \v(xversion) 1118 { set telnet remote-echo on } undef noecho forward LOGGEDIN :GETPASSWD def noecho on if >= \v(xversion) 1118 { set telnet remote-echo off } clear device-buffer intext 4 {Password: } 32 90 * if failure bail asg _password \f.oox(\%n) asg \%n :CHECKPASSWD def ok 0 if not exist \m(_userfile) forward BAD open read \m(_userfile) if failure forward BAD :PWLOOP read \%a if failure forward PWDONE getfields {\%a} if not eq "\m(U_ID)" "\m(_username)" goto PWLOOP if not eq "\m(U_PW)" "\m(_password)" goto PWLOOP def ok 1 :PWDONE close read if > \m(ok) 0 forward good :BAD hostlog {Access denied "\m(_username)"} undef _password if count goto again msg {Access denied - hanging up} incr \%M msg hostlog {Incorrect password - access denied} bail :GOOD undef _password asg _myname \fdef(U_NM) asg _mypriv \m(U_PR) if not numeric \m(_mypriv) asg _mypriv 0 msg beep if >= \v(xversion) 1118 { set telnet remote-echo on } undef noecho ;+------------------------------------------------------------------------+ ; Get here when logged in. :LOGGEDIN ; Create Transaction log with unique name "__