; 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.3 or later is required. ; ; Copyright (C) 1996, 1997, 1998, 1999 ; 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 def _VERSION 1.04 ; 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 ; FAIL handles fatal errors ; define FAIL hostlog {In \v(cmdfile) at line \v(_line)...},- hostlog {Fatal error \v(errno) \v(errstring) - session closed},- beep, exit ; 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't 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(startup)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(startup)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's a server. ; telopt will echo ; I must echo if failure do fail ; Make sure this doesn't 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 do fail ; Clear screen define cur output \27[\%1;\%2H, if failure do fail ; Position the cursor define atp cur \%1 \%2, out \%3, if failure do fail ; Print text at cursor pos define cleol out \27[K, if failure do fail ; 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, - :INLOOP, - clear input, - cur \%L \%p, - cleol, - input \m(_inactivity),- if failure do fail,- if eq "" "\v(inchar)" goto inloop, - asg \%9 \fcode(\v(inchar)), - if = \%9 9 asg \%9 32, - if = \%9 127 asg \%9 8, - if = \%9 8 { bs, goto inloop }, - if = \%9 21 { cur \%L \%q, cleol, asg \%p \%q, asg \%n, goto inloop },- if = \%9 3 goto main, - if = \%9 12 goto \%1, - if not < \%9 32 forward graphic, - if not def \%n goto inloop, - forward gotit, - :GRAPHIC, - atp \%L \%p \v(inchar), - incr \%p, - if eq \%9 32 { if def \%n forward GOTIT, else goto inloop },- 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 inloop, - :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)},- :INLOOP,- cur \%L \%p, - cleol,- input \%4,- if failure do fail,- if eq "\v(inchar)" "" goto inloop,- 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 inloop },- if = \%8 127 { bs, goto inloop },- if = \%8 21 { - cur \%L \%q, cleol, asg \%p \%q, asg \%n, goto inloop },- 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 inloop - },- if eq "" "\%n" goto inloop,- 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} do fail :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 do fail 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 do fail 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} do fail :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 "__