* PROGRAM NAME: PHONECOM * AUTHOR: RICHARD CONN * DATE: 26 JUN 81 * VERSION: 1.1 * PREVIOUS VERSIONS: 1.0 (3 JAN 81) VERS EQU 11 ; VERSION NUMBER * THE FOLLOWING IS THE STANDARD (ORG 0) BASE ADDRESS FOR CP/M * IF YOU HAVE A NON-STANDARD CP/M, YOU WILL HAVE TO MODIFY THIS EQUATE CPM$BASE EQU 0 ; BASE ADDRESS * * PHONECOM -- Telephone Answering System * PHONECOM enables an external user to dial into the target CP/M * system, identify his authorization to use the system (log in), and * transfer control to the user through CP/M's redirectable I/O drivers. * PHONECOM redirects CON: I/O to the BAT: device. For the purpose * of PHONECOM, this device should be defined to be -- * * Input: CRT and Modem in Parallel * Output: CRT and Modem in Parallel * * PHONECOM runs in two basic modes: * 1. Command Mode, in which an authorized user can create up to * ten accounts with passwords for external users, and * 2. Active Mode, in which PHONECOM monitors the telephone line * for a ring and the BAT: device for an interrupt (^C). If an interrupt * is detected, PHONECOM resets the redirectable I/O to CON:=CRT: and * returns to CP/M. If a ring is detected, PHONECOM starts the login * procedure, answering the telephone in answer mode and waiting for * the user to type an 'e' (^E, e, or E). If he hangs up, PHONECOM returns * to active mode. If he types the 'e', PHONECOM asks him for his account * and password (up to three tries) and returns to CP/M if successful or * hangs up if not. * * ** STRUCTURE OF THE PROGRAM ** * The structure of PHONECOM is as follows: * * Part 1: Mainline * The mainline of PHONECOM is structured into the following main sections -- * ACCOUNTS Login Account Data; this buffer is structured as * follows: * 0 * 0$ * ... * 0 * START Initialization section; HELP info included * ACTIVE Active Mode * ACTIVE$ENTRY Abort entry location * ACTIVE$LOOP Ring/Abort Test Loop * LOGIN Login User; wait for ^E, E, or e * LOGIN$ACCT Get and verify account name * LOGIN$PW Get and verify account password * COMMAND Command Mode; get Control Password * COMMAND$CONTROL * Print prompt, control password, account info * COMMAND$INPUT Get command and execute routine * COMMAND$ABORT Invalid Control Password; give user 2nd chance * CMD$ADD Add Entry * CMD$CHG Change Password of Entry * CMD$DEL Delete Entry * CMD$PW Change Control Password * CMD$RUN Define the Default CP/M Command Line to be * executed upon validation of caller * CMD$QUIT Abort to CP/M without altering accounts on disk * CMD$EXIT Exit to CP/M and update accounts on disk * * Part 2: Support Utilities * This part contains the following utility programs -- * PRINT$ID Print ID of Program * CPM Return to CP/M with I/O set to CON:=CRT: * CPM$RETURN Return to CP/M with I/O unchanged * GET$NUMBER Get ASCII Digit 0-9 from CON:; abort to * COMMAND$CONTROL if not valid * PAUSE 2 Second Delay * WRITE$BLOCK Write 128-byte block pted to by HL to disk; * on return, HL pts to the byte after the last * byte written * CHECK$NUMBER Check binary 0-9 in A against number of accounts * stored; if <= number of accts, OK (value unchanged); * otherwise, abort to COMMAND$CONTROL with msg * FIND$ENTRY Find entry number in A (binary); return with DE pting * to it * DELETE$ENTRY Delete entry pted to by HL * COPY$ACCOUNTS Copy rest of accts pted to by HL to DE * MOVE$ENTRY Move entry pointed to by DE to location pted to by HL; * when done, HL pts to byte after string () * DISPLAY$ACCOUNTS * Display account names on CON: * PRINT$ACCOUNT$ENTRY * Print entry pted to by HL; at end, HL pts to next entry * PAE Print part of acct entry pted to by HL (ends in * or '$') * SKIP$CTRL$PW On return, HL pts to byte after Control Password * SCAN$ACCTS Scan accts by text of INBUF (INBUF+2,...); return w/Z * if match and HL pts to Password entry * SCAN$ENTRY Scan entry pted to by HL for match in INBUF+2; return * with HL pting to Password and Z if complete match * SCAN$PW Scan for Password; on entry, HL pts to beginning of * Password; return with Z if match against contents of * INBUF * SET$IO CON:=BAT: * RESET$IO CON:=CRT: * CON$IN Input char from CON: with or without echo * CON$OUT Output char to CON: * CRLF Output to CON: * SCREEN$CLEAR Clear CRT if supported * SCREEN$MESSAGE Print message pted to by return address; 1st 2 bytes * give row number, col number; message ends in 0 * PRINT$MESSAGE Print message pted to by ret adr; ends in 0 * READLN Input line from CON: into INBUF+2; result ends in 0 * READLN$DIRECT Same as READLN, but cannot be interrupted and is done * with or without echo; editing functions are defined * below with the related routines (entry points) * RD$LOOP Main loop for READLN$DIRECT * RD$DONE Terminate READLN$DIRECT () * RD$BS Soft Copy Char Delete (^H) * RD$DEL Hard Copy Char Delete () * RD$ERX Soft Copy Line Delete (^X) * RD$ERU Hard Copy Line Delete (^U) * RD$OMIT Error indicator -- Beeps * BS$OUT Soft Copy Delete on Screen (,' ',) * BS$OUT1 Soft Copy Delete when already echoed (' ',) * DEC$TRY$CNT Count down tries and abort ACTIVE Mode if it goes to 0 * ECHO$ON Turn On Echo for CON$IN and READLN$DIRECT * ECHO$OFF Turn Off Echo for CON$IN and READLN$DIRECT * ABORT Check for CTRL-C and abort to CP/M if found * * Part 3: CP/M Equates, ASCII Equates, and Buffers * * Part 4: Support Package Interface * * * * Part 1: Mainline * ORG CPM$BASE+100H ; CP/M START ADDRESS JMP START ; JUMP TO START OF PROGRAM DEFAULT$COMMAND: DB 0 ; SET COMMAND OFF DS 252 ; REST OF PAGE FOR COMMAND LINE (WASTE) NUM$ACCOUNTS: DB 0 ; NUMBER OF ACCOUNT ENTRIES ACCOUNTS: DB 0 ; CLEAR CONTROL PASSWORD DB 0 ; CLEAR ACCOUNT/PASSWORD LIST DS 506 ; REST OF TWO PAGES FOR ACCOUNT DATA * START OF PROGRAM START: LXI H,0 ; SAVE STACK PTR DAD SP SHLD STACK LXI SP,STACK ; RESET STACK PTR CALL ECHO$ON ; TURN ON ECHO CALL SP$STAT ; GET STATUS ANI 1000$0000B ; TELEPHONE INTERFACE AVAILABLE? JNZ START1 CALL PRINT$MESSAGE DB 'PHONECOM Support Package Telephone Interface Not Available',0 JMP CPM * SCAN COMMAND LINE FOR OPTIONS START1: LXI H,BUFF ; PT TO COMMAND LINE MOV A,M ; GET CHAR COUNT INX H ; PT TO 1ST CHAR PUSH H ; SAVE PTR TO 1ST CHAR ADD L ; PT TO END OF COMMAND LINE MOV L,A MVI M,0 ; PLACE ENDING 0 POP H ; GET PTR TO 1ST CHAR * SCAN FOR OPTION START2: MOV A,M ; GET CHAR ORA A ; 0=DONE JZ ACTIVE ; ACTIVE MODE IF NO OPTION INX H ; PT TO NEXT CHAR CPI OPT$CH ; OPTION CHAR? JNZ START2 MOV A,M ; GET OPTION CPI 'C' ; COMMAND MODE? JZ COMMAND * PRINT HELP MESSAGE CALL PRINT$MESSAGE DB 'PHONECOM Help Information --',CR,LF DB ' PHONECOM is invoked by the following command:',CR,LF DB ' PHONECOM [/C]',CR,LF DB ' where /C is optional.',CR,LF DB CR,LF DB ' If /C is specified, Command Mode is entered; if no option' DB CR,LF DB ' is specified, Active Mode is entered. Any other option' DB CR,LF DB ' gives this HELP Information.',0 JMP CPM * * ACTIVE MODE -- WAIT FOR TELEPHONE TO RING OR INTERRUPT FROM CONSOLE * ACTIVE: LXI SP,STACK ; RESET STACK PTR CALL PRINT$ID CALL PRINT$MESSAGE DB 'PHONECOM Active Mode Engaged',CR,LF,0 * ENTRY POINT FOR RESTART ACTIVE$ENTRY: LXI SP,STACK ; RESET STACK IN CASE OF ATTEMPTED ABORT CALL PRINT$MESSAGE DB 'Hanging Up Telephone',CR,LF,0 CALL TI$INI ; INITIALIZE TELEPHONE SUBSYSTEM CALL TI$PREPANS ; PREPARE ANSWER MODE ACTIVE$ENTRY1: CALL PRINT$MESSAGE DB ' Type ^C to Abort and Return to CP/M',CR,LF,0 CALL SET$IO ; SET CON:=BAT: * WAIT FOR RING OR INTERRUPT ACTIVE$LOOP: CALL TI$RST ; RING? JNZ LOGIN CALL ABORT ; INTERRUPT CHECK JMP ACTIVE$LOOP * * LOGIN EXTERNAL USER * LOGIN: CALL TI$ANS ; GOTO ANSWER MODE CALL ECHO$OFF ; DON'T ECHO LOGIN1: CALL CON$IN ; GET INPUT ANI 1FH ; MASK FOR ^E CPI CTRLE JNZ LOGIN1 CALL ECHO$ON ; ECHO MVI A,3 ; 3 TRIES STA NUM$TRIES CALL CRLF ; NEW LINE CALL CRLF CALL PRINT$ID ; PRINT PROGRAM ID CALL PRINT$MESSAGE DB CR,LF,' Editing Characters are: =Done,' DB CR,LF,'^U=Hard Copy Line Delete, ^X=Soft Copy Line Delete,' DB CR,LF,'=Hard Copy Char Delete, ^H ()=Soft Copy Char ' DB 'Delete',0 LOGIN$ACCT: CALL PRINT$MESSAGE DB CR,LF,LF,LF,'Account Name? ',0 CALL READLN$DIRECT ; READ RESPONSE CALL SCAN$ACCTS ; SCAN ACCOUNT INFORMATION JZ LOGIN$PW ; OK -- READ PASSWORD CALL DEC$TRY$CNT ; DECREMENT TRY COUNT CALL PRINT$MESSAGE DB 'Invalid Account -- Try Again',0 JMP LOGIN$ACCT LOGIN$PW: CALL PRINT$MESSAGE DB CR,LF,'Password? ',0 CALL ECHO$OFF ; DON'T ECHO PW CALL READLN$DIRECT ; READ RESPONSE CALL ECHO$ON ; ECHO AGAIN CALL SCAN$PW ; SCAN PASSWORD JZ LOGIN$DONE ; OK -- CONTINUE CALL DEC$TRY$CNT ; DECREMENT TRY COUNT CALL PRINT$MESSAGE DB 'Invalid Password for this Account -- Try Again',0 JMP LOGIN$ACCT LOGIN$DONE: CALL PRINT$MESSAGE DB CR,LF,'Access Validated -- Returning to CP/M',0 LOGIN$DONE1: LXI H,DEFAULT$COMMAND ; DEFAULT COMMAND PRESENT? MOV A,M ; 0 IF NO ORA A JZ CPM$RETURN PUSH H ; SAVE PTR LXI D,SUBFIL ; OPEN $$$.SUB CALL CLR$FCB ; CLEAR FCB BYTES MVI C,OPENF CALL BDOS POP H ; COPY TO DEFAULT BUFFER LXI D,BUFF LOGIN$DONE2: MOV A,M ; COPY TO BUFF STAX D INX H INX D MOV A,D ; DONE? CPI 1 JNZ LOGIN$DONE2 LXI D,SUBFIL ; CLOSE FILE MVI C,CLOSEF CALL BDOS JMP CPM$RETURN ; EXECUTE COMMAND VIA CP/M * * COMMAND MODE -- VERIFY ACCESS RIGHTS AND EDIT ACCOUNTS * COMMAND: CALL SCREEN$CLEAR CALL PRINT$ID CALL PRINT$MESSAGE DB CR,LF,LF DB 'PHONECOM COMMAND Mode',CR,LF DB ' Enter Control Password -- ',0 CALL READLN ; GET RESPONSE LXI H,ACCOUNTS ; COMPARE PW'S LXI D,INBUF+2 ; PT TO RESPONSE COMMAND$PW: LDAX D ; GET CHAR CMP M ; MATCH? JNZ COMMAND$ABORT INX H ; PT TO NEXT INX D ORA A ; DONE? JNZ COMMAND$PW * PASSWORD VERIFIED -- CONTINUE COMMAND$CONTROL: LXI SP,STACK ; RESET STACK POINTER CALL SCREEN$CLEAR ; CLEAR SCREEN CALL PRINT$ID CALL DISPLAY$ACCOUNTS ; DISPLAY ACCOUNT INFORMATION CALL SCREEN$MESSAGE DB 3,5 DB 'Control Password: ',0 LXI H,ACCOUNTS CALL PAE ; PRINT CONTROL PASSWORD CALL PRINT$DEFAULT$COMMAND ; PRINT DEFAULT COMMAND LINE CALL SCREEN$MESSAGE ; PRINT PROMPT DB 5,1 DB 'Cmd Function Cmd Function Cmd Function',0 CALL SCREEN$MESSAGE DB 6,1 DB ' A Add Account C Change Account PW D Delete ' DB 'Account',0 CALL SCREEN$MESSAGE DB 7,1 DB ' P Set Control PW Q Quit without Change R Define ' DB 'Program to Run',0 CALL SCREEN$MESSAGE DB 8,1 DB ' X Exit and Update',0 * GET EDITOR COMMAND COMMAND$INPUT: CALL SCREEN$MESSAGE DB 3,50 DB 'PHONECOM Command? ',0 MVI A,' ' ; WRITE OVER PREVIOUS COMMAND CALL CON$OUT MVI A,BS CALL CON$OUT CALL CON$IN ; GET RESPONSE ANI 0DFH ; CAPITALIZE PUSH PSW ; SAVE COMMAND CALL VALID$CMD ; ERASE OLD ERROR MSG POP PSW ; GET COMMAND CPI 'A' ; ADD? JZ CMD$ADD CPI 'C' ; CHANGE? JZ CMD$CHG CPI 'D' ; DELETE? JZ CMD$DEL CPI 'P' ; CONTROL PW? JZ CMD$PW CPI 'Q' ; QUIT? JZ CMD$QUIT CPI 'R' ; DEFINE PROGRAM TO RUN? JZ CMD$RUN CPI 'X' ; EXIT? JZ CMD$EXIT * INVALID COMMAND CALL SCREEN$MESSAGE DB 4,60 DB 'Invalid Command',0 JMP COMMAND$INPUT * VALID COMMAND ROUTINE VALID$CMD: CALL SCREEN$MESSAGE DB 4,60 DB ' ',0 CALL VC1 ; CLEAR LINE CALL VC2 RET VC1: CALL SCREEN$MESSAGE DB 9,5,0 JMP VC3 VC2: CALL SCREEN$MESSAGE DB 10,5,0 VC3: MVI B,40 ; 40 MVI A,' ' JMP PD1 * POSITION CURSOR AND PRINT MESSAGE PTED TO BY RET ADR PRINT$ERROR$MESSAGE: CALL VC1 ; CLEAR LINE CALL SCREEN$MESSAGE DB 9,5,0 ; POSITION CURSOR JMP PRINT$MESSAGE ; CONTINUE PRINT2$ERROR$MESSAGE: CALL VC2 ; CLEAR LINE CALL SCREEN$MESSAGE DB 10,5,0 ; POSITION CURSOR JMP PRINT$MESSAGE ; CONTINUE * PRINT DOTS FOR INPUT TEXT AND POSITION CURSOR PRINT$DOTS: PUSH B ; SAVE BC MVI B,40 ; 40 DOTS MVI A,'.' ; PRINT DOT CALL PD1 ; PRINT DOTS MVI B,40 ; BACKSPACE TO 1ST POSITION MVI A,BS ; CALL PD1 POP B ; RESTORE BC RET PD1: CALL CON$OUT ; PRINT IT DCR B JNZ PD1 RET * * ASK USER IF HE WISHES TO TRY ENTERING THE CONTROL PASSWORD AGAIN * COMMAND$ABORT: LDA TRY$FLAG ; 2ND TRY? ORA A ; 0=YES JZ CPM ; REBOOT CMA ; COMPLEMENT FLAG STA TRY$FLAG CALL PRINT$ERROR$MESSAGE DB 'Invalid Control Password -- Try Again',CR,LF,0 CALL PAUSE JMP COMMAND * * ADD ACCOUNT * CMD$ADD: LDA NUM$ACCOUNTS ; HOW MANY ENTRIES SO FAR? CPI 10 ; 10 MAX JNZ CMD$ADD1 CALL PRINT$ERROR$MESSAGE DB 'Account Directory is Full',0 CALL PAUSE ; DELAY JMP COMMAND$CONTROL * ABORT ADD COMMAND CMD$ADD$ABORT: CALL PRINT$ERROR$MESSAGE DB 'Account Name Already Exists',0 CALL PAUSE JMP COMMAND$CONTROL * GET NEW ACCOUNT NAME CMD$ADD1: INR A ; INCREMENT NUMBER OF ACCOUNTS STA NUM$ACCOUNTS CALL PRINT$ERROR$MESSAGE DB 'Account Name? ',0 CALL READLN ; GET NAME OF ACCOUNT LDA INBUF+2 ; GO BACK TO COMMAND CONTROL IF NULL ORA A JZ COMMAND$CONTROL CALL SCAN$ACCTS ; ALREADY THERE? JZ CMD$ADD$ABORT * SKIP TO END OF ACCOUNTS CALL SKIP$CTRL$PW ; SKIP OVER CONTROL PASSWORD CMD$ADD$SKIP: MOV A,M ; DONE? ORA A ; 0 IF DONE JZ CMD$ADD$SKIP1 CALL SCAN$ENTRY$SKIP ; SKIP OVER ENTRY JMP CMD$ADD$SKIP CMD$ADD$SKIP1: LXI D,INBUF+2 ; ADD ACCOUNT NAME CALL MOVE$ENTRY ; MOVE TO ACCOUNTS MVI M,0 ; END OF NAME INX H ; PT TO PW ENTRY PUSH H ; SAVE PTR CALL PRINT2$ERROR$MESSAGE DB 'Account Password? ',0 CALL READLN ; GET PASSWORD OF ACCOUNT POP H ; GET PTR TO PASSWORD LOCATION LXI D,INBUF+2 CALL MOVE$ENTRY ; ADD PASSWORD MVI M,'$' ; END OF ENTRY INX H MVI M,0 ; END OF ACCOUNTS JMP COMMAND$CONTROL ; GET NEXT COMMAND * * CHANGE PASSWORD OF GIVEN ACCOUNT * CMD$CHG: CALL PRINT$ERROR$MESSAGE DB 'Number of Account Entry to Change? ',0 CALL GET$NUMBER ; GET VALID NUMBER RESPONSE CALL CHECK$NUMBER ; THAT MANY ENTRIES? CALL FIND$ENTRY ; FIND THAT ENTRY PUSH D ; SAVE PTR TO ENTRY LXI H,INBUF+2 ; COPY ACCOUNT NAME TO INBUF CALL MOVE$ENTRY MVI M,0 ; SET ENDING POP H ; GET PTR TO ENTRY TO DELETE CALL DELETE$ENTRY ; DELETE ENTRY PTED TO BY HL LDA NUM$ACCOUNTS ; CORRECT COUNT OF NUMBER OF ENTRIES INR A STA NUM$ACCOUNTS * SET HL TO END OF ACCOUNT INFO CALL SKIP$CTRL$PW ; SKIP OVER CTRL PW CMD$CHG1: MOV A,M ; PTING TO END? ORA A ; 0 MEANS YES JZ CMD$ADD$SKIP1 ; FOLLOW THRU W/ADD CALL SCAN$ENTRY$SKIP ; SKIP CURRENT ENTRY JMP CMD$CHG1 ; SKIP TO END * * DELETE ENTRY * CMD$DEL: CALL PRINT$ERROR$MESSAGE DB 'Number of Account Entry to Delete? ',0 CALL GET$NUMBER ; GET ENTRY NUMBER CALL CHECK$NUMBER ; THAT MANY ENTRIES? CALL FIND$ENTRY ; FIND THAT ENTRY XCHG ; HL PTS TO IT CALL DELETE$ENTRY ; DELETE THAT ENTRY JMP COMMAND$CONTROL * * CHANGE CONTROL PASSWORD * CMD$PW: CALL PRINT$ERROR$MESSAGE DB 'New Control Password? ',0 CALL READLN ; GET RESPONSE CALL SKIP$CTRL$PW ; SKIP OVER CURRENT CONTROL PASSWORD PUSH H ; SAVE PTR TO 1ST ACCOUNT ENTRY CALL SP$END ; GET BUFFER ADDRESS FROM SUPPORT PACKAGE XCHG ; BUFFER ADDRESS IN DE POP H ; GET PTR TO 1ST ACCOUNT ENTRY CALL COPY$ACCOUNTS LXI D,INBUF+2 ; STORE NEW PASSWORD LXI H,ACCOUNTS CALL MOVE$ENTRY ; COPY IT IN MVI M,0 ; PLACE ENDING INX H ; PT TO NEXT XCHG ; DE PTS TO ENTRY AFTER CONTROL PASSWORD CALL SP$END ; HL PTS TO ACCOUNT ENTRIES CALL COPY$ACCOUNTS JMP COMMAND$CONTROL * * SET DEFAULT COMMAND * CMD$RUN: CALL PRINT$ERROR$MESSAGE DB 'Default Command (=None)? ',0 CALL READLN LXI H,INBUF+1 ; COPY TO DEFAULT COMMAND LXI D,DEFAULT$COMMAND CMD$RUN$COPY: MOV A,M ; GET BYTE STAX D ; PUT BYTE INX H ; PT TO NEXT INX D ORA A ; 0=DONE JNZ CMD$RUN$COPY JMP COMMAND$CONTROL * * ABORT AND THROW AWAY CHANGES * CMD$QUIT: CALL PRINT$ERROR$MESSAGE DB 'Are you sure you want to abort (Y/N/=N)? ',0 CALL CON$IN ; GET RESPONSE ANI 0DFH ; CAPITALIZE CPI 'Y' JNZ COMMAND$CONTROL CALL SCREEN$CLEAR JMP CPM * * EXIT AND SAVE ACCOUNTS TO DISK * CMD$EXIT: CALL PRINT$ERROR$MESSAGE DB 'Logging Accounts to Disk',CR,LF,0 LXI D,FILENAME ; ERASE FILE CALL CLR$FCB MVI C,ERASEF CALL BDOS LXI D,FILENAME ; OPEN FILE CALL CLR$FCB ; CLEAR FILE FCB FIELDS MVI C,MAKEF CALL BDOS CALL SP$END ; GET END ADDRESS XCHG ; ... IN DE LXI H,CPM$BASE+100H ; PT TO BEGINNING OF PROGRAM MOV B,D ; BLOCK COUNT IN B DCR B ; DECREMENT FOR 1ST PAGE CMD$EXIT$WRITE: PUSH B ; SAVE COUNT CALL WRITE$BLOCK ; WRITE 2 BLOCKS (256 BYTES) CALL WRITE$BLOCK POP B ; GET COUNT DCR B ; COUNT DOWN JNZ CMD$EXIT$WRITE LXI D,FILENAME MVI C,CLOSEF ; CLOSE FILE CALL BDOS CALL SCREEN$CLEAR JMP CPM * * * Part 2: PHONECOM SUPPORT UTILITIES * * * PRINT NAME OF PROGRAM AND VERSION NUMBER * PRINT$ID: CALL PRINT$MESSAGE DB 'PHONECOM, Version ' DB VERS/10+'0','.',(VERS MOD 10)+'0' DB CR,LF DB '++ CP/M Remote Telephone Interface System ++' DB CR,LF,0 RET * * RETURN TO CP/M WITH I/O RESET * CPM: CALL RESET$IO ; RESTORE CON:=CRT: LHLD STACK ; RESTORE SP SPHL RET ; RETURN TO CP/M * * RETURN TO CP/M WITH I/O UNCHANGED * CPM$RETURN: JMP 0 ; WARM BOOT CP/M * * CLEAR KEY FIELDS OF FCB PTED TO BY DE; DON'T CHANGE DE * CLR$FCB: LXI H,12 ; OFFSET INTO FCB DAD D MVI B,24 ; 24 BYTES CFCB1: MVI M,0 ; STORE ZEROES INX H ; PT TO NEXT DCR B ; COUNT DOWN JNZ CFCB1 RET * * GET NUMBER VALUE OR RETURN TO COMMAND$CONTROL IF NOT ENTERED * GET$NUMBER: CALL CON$IN ; GET RESPONSE SUI '0' ; CONVERT TO BINARY JC GET$NUMBER1 CPI 10 JNC GET$NUMBER1 RET GET$NUMBER1: POP H ; CLEAR STACK JMP COMMAND$CONTROL ; GOTO COMMAND$CONTROL * * PAUSE -- BRIEF DELAY * PAUSE: MVI B,20 ; 2-SEC DELAY CONSTANT PAUSE$LOOP: CALL TI$TIME ; DELAY 0.1 SEC DCR B ; COUNT DOWN JNZ PAUSE$LOOP RET * * WRITE BLOCK PTED TO BY HL TO DISK * WRITE$BLOCK: LXI D,BUFF ; PT TO BUFFER WRITE$BLOCK$LOOP: MOV A,M ; GET BYTE STAX D ; PUT BYTE INX H ; PT TO NEXT INX D MOV A,D ; DONE? CPI 1 JNZ WRITE$BLOCK$LOOP PUSH H ; SAVE PTR TO NEXT BLOCK LXI D,FILENAME ; WRITE BLOCK MVI C,WRITEF CALL BDOS POP H ; GET PTR TO NEXT BLOCK RET * * CHECK NUMBER ASSURES THAT BINARY 0-9 IS WITHIN RANGE OF ENTERED ACCOUNTS * CHECK$NUMBER: PUSH PSW ; SAVE NUMBER MOV B,A ; IN B ALSO INR B ; ADD 1 FOR OFFSET LDA NUM$ACCOUNTS ; GET NUMBER OF ENTRIES CMP B ; ERROR IF CARRY JC CHECK$FAILED POP PSW ; GET NUMBER -- CHECK OK RET CHECK$FAILED: CALL PRINT2$ERROR$MESSAGE DB 'Invalid Entry Selection',0 CALL PAUSE POP PSW ; CLEAR STACK POP H JMP COMMAND$CONTROL * * FIND ENTRY WHOSE BINARY NUMBER (0-9) IS IN A; RETURN PTR IN DE * FIND$ENTRY: PUSH PSW ; SAVE NUMBER MOV B,A ; IN B ALSO CALL SKIP$CTRL$PW FIND$ENTRY$LOOP: MOV A,B ; DONE? ORA A JZ FIND$ENTRY$DONE DCR B ; COUNT DOWN CALL SCAN$ENTRY$SKIP ; SKIP OVER ENTRY JMP FIND$ENTRY$LOOP FIND$ENTRY$DONE: XCHG ; DE PTS TO ENTRY POP PSW ; COUNT IN A RET * * DELETE ENTRY PTED TO BY HL * DELETE$ENTRY: LDA NUM$ACCOUNTS ; DECREMENT NUMBER OF ACCOUNTS DCR A STA NUM$ACCOUNTS PUSH H ; SAVE PTR CALL SCAN$ENTRY$SKIP ; SKIP TO NEXT ENTRY POP D ; HL PTS TO NEXT ENTRY, DE PTS TO DEST * * COPY ACCOUNTS COPIES THE REST OF THE ACCOUNTS FROM HL TO BUFFER AT DE * COPY$ACCOUNTS: MOV A,M ; GET 1ST BYTE OF NEXT ENTRY STAX D ; STORE IT IN CASE IT IS ZERO (END OF ACCOUNTS) ORA A ; ZERO? RZ ; DONE IF SO COPY$ACCTS$LOOP: MOV A,M ; COPY ENTRY TO '$' STAX D INX H ; PT TO NEXT INX D CPI '$' ; DONE? JNZ COPY$ACCTS$LOOP JMP COPY$ACCOUNTS * * MOVE STRING PTED TO BY DE TO LOC PTED TO BY HL; FILTER OUT '$' AS MOVE * PROGRESSES; WHEN DONE, HL PTS TO BYTE AFTER STRING (AT NULL) * MOVE$ENTRY: LDAX D ; GET BYTE INX D ; PT TO NEXT ORA A ; DONE IF ZERO RZ CPI '$' ; FILTER '$' JZ MOVE$ENTRY MOV M,A ; PUT BYTE INX H ; PT TO NEXT JMP MOVE$ENTRY * * DISPLAY ACCOUNT DATA ON CRT * DISPLAY$ACCOUNTS: CALL SCREEN$MESSAGE DB 12,5 DB '-- ACCOUNT Data Display --',CR,LF DB '# Account Name Account Password',CR,LF,0 CALL SKIP$CTRL$PW ; SKIP OVER CONTROL PASSWORD MVI B,'0' ; INIT ACCOUNT NUMBERING DA$LOOP: MOV A,M ; DONE? ORA A ; DONE IF ZERO RZ MOV A,B ; PRINT ENTRY NUMBER CALL CON$OUT MVI A,' ' ; PRINT 2 'S CALL CON$OUT CALL CON$OUT PUSH B ; SAVE B CALL PRINT$ACCOUNT$ENTRY POP B INR B ; NEXT NUMBER JMP DA$LOOP * * PRINT ACCOUNT ENTRY PTED TO BY HL; AT END, HL PTS TO NEXT ENTRY * PRINT$ACCOUNT$ENTRY: CALL PAE ; PRINT ENTRY FOR ACCOUNT NAME MVI A,' ' ; PRINT SEPARATOR CALL CON$OUT CALL CON$OUT CALL CON$OUT CALL PAE ; PRINT ENTRY FOR PASSWORD JMP CRLF ; NEW LINE * * PRINT PART OF ACCOUNT ENTRY PTED TO BY HL; ON RET, HL PTS TO NEXT PART * PAE: MVI C,22 ; 22 CHARS PAE1: MOV A,M ; GET CHAR INX H ; PT TO NEXT ORA A ; DONE IF ZERO JZ PAE2 CPI '$' ; DONE IF '$' JZ PAE2 CALL CON$OUT ; PRINT IT DCR C ; COUNT DOWN CHARS IN ENTRY JNZ PAE1 PAE1$SKIP: MOV A,M ; SKIP TO END OF ENTRY INX H ; PT TO NEXT CHAR ORA A ; DONE IF ZERO RZ CPI '$' ; DONE IF '$' JNZ PAE1$SKIP RET * FILL OUT REST OF ENTRY WITH PAE2: MVI A,' ' ; WRITE CALL CON$OUT DCR C ; COUNT DOWN JNZ PAE2 RET * * PRINT DEFAULT COMMAND * PRINT$DEFAULT$COMMAND: CALL SCREEN$MESSAGE DB 4,5 DB 'Default Command: ',0 LXI H,DEFAULT$COMMAND MOV A,M ; DEFINED? ORA A ; 0=NO JNZ PDC1 CALL PRINT$MESSAGE DB '- None -',0 RET PDC1: INX H ; PT TO 1ST CHAR MOV A,M ; GET IT ORA A ; 0=DONE RZ CALL CON$OUT ; PRINT JMP PDC1 * * SKIP CONTROL PASSWORD IN ACCOUNTS; WHEN DONE, HL PTS TO BYTE AFTER * SKIP$CTRL$PW: LXI H,ACCOUNTS ; PT TO 1ST BYTE SCP1: MOV A,M ; LOOK FOR ZERO INX H ; PT TO NEXT ORA A ; 0=DONE JNZ SCP1 RET * * SCAN ACCOUNTS FOR TEXT OF INBUF; RETURN W/ZERO SET IF MATCH * SCAN$ACCTS: CALL SKIP$CTRL$PW ; SKIP OVER CONTROL PASSWORD MOV A,M ; DONE? ORA A ; SET FLAGS JZ SCAN$ACCTS$NF ; OK IF EMPTY SCAN$ACCTS$LOOP: CALL SCAN$ENTRY ; CHECK FOR MATCH RZ ; MATCH; HL PTS TO PW MOV A,M ; END OF ACCOUNTS? ORA A ; NOT ZERO MEANS NO JNZ SCAN$ACCTS$LOOP * ENTRY NOT FOUND SCAN$ACCTS$NF: MVI A,0FFH ; SET NON-ZERO ORA A RET * * SCAN ENTRY PTED TO BY HL FOR MATCH IN INBUF+2; RET W/HL PTING TO PW * AND ZERO SET IF COMPLETE MATCH * SCAN$ENTRY: LXI D,INBUF+2 ; PT TO INPUT BUFFER SCAN$ENTRY$LOOP: LDAX D ; GET BYTE CMP M ; MATCH? JNZ SCAN$ENTRY$SKIP ; NO MATCH SO SKIP INX H ; PT TO NEXT INX D ORA A ; DONE IF ZERO RZ JMP SCAN$ENTRY$LOOP ; CONTINUE SCAN$ENTRY$SKIP: INX H ; PT TO NEXT MOV A,M ; GET CHAR CPI '$' ; END OF ENTRY JNZ SCAN$ENTRY$SKIP INX H ; PT TO NEXT ENTRY MVI A,0FFH ; SET NOT ZERO ORA A RET * * SCAN FOR PASSWORD; ON ENTRY, HL PTS TO BEGINNING OF PW * RET W/ZERO SET IF FOUND * SCAN$PW: LXI D,INBUF+2 ; PT TO GIVEN PASSWORD SCAN$PW$LOOP: LDAX D ; GET BYTE ORA A ; DONE? JZ SCAN$PW$TEST CMP M ; MATCH? JNZ SCAN$PW$FAIL INX H ; PT TO NEXT INX D JMP SCAN$PW$LOOP * END OF INPUT PASSWORD -- TEST FOR END OF STORED PASSWORD ($) SCAN$PW$TEST: MOV A,M ; GET BYTE CPI '$' ; '$' IS OK RZ * SCAN FAILED SCAN$PW$FAIL: MVI A,0FFH ; SET FLAGS ORA A RET * * SET I/O TO BAT: * SET$IO: LDA IOBYTE ; GET I/O BYTE ANI 0FCH ; MASK OUT OLD CON: VALUE ORI 2 ; MASK IN BAT: VALUE STA IOBYTE ; PUT I/O BYTE RET * * RESET I/O TO CRT: * RESET$IO: LDA IOBYTE ; GET I/O BYTE ANI 0FCH ; MASK OUT CON: VALUE ORI 1 ; MASK IN CRT: VALUE STA IOBYTE ; PUT I/O BYTE RET * * READ CHAR FROM CON: * CON$IN: PUSH H ; SAVE ALL REGS PUSH D PUSH B LHLD WBADR ; GET ADDRESS OF BIOS MVI L,9 ; CONSOLE CHAR IN OFFSET LXI D,CON$IN$RET PUSH D ; RET ADR ON STACK PCHL ; "CALL" INPUT ROUTINE CON$IN$RET: POP B POP D POP H ANI 7FH ; MASK OUT MSB * CHECK FOR ECHO PUSH PSW ; SAVE CHAR LDA ECHO$FLAG ; ECHO? ORA A ; 0=NO JZ CON$IN$DONE * ECHO CHAR POP PSW ; GET CHAR CALL CON$OUT ; PRINT CHAR RET * DON'T ECHO CHAR CON$IN$DONE: POP PSW ; GET CHAR RET * * WRITE CHAR TO CON: * CON$OUT: PUSH H ; SAVE ALL REGS PUSH D PUSH B PUSH PSW MVI C,2 ; WRITE MOV E,A ; CHAR IN E CALL BDOS POP PSW POP B POP D POP H RET * * OUTPUT * CRLF: PUSH PSW ; SAVE A MVI A,CR ; CALL CON$OUT MVI A,LF ; CALL CON$OUT POP PSW ; GET A RET * * CLEAR CRT SCREEN * SCREEN$CLEAR: CALL SP$STAT ; CRT AVAILABLE? ANI 1 RZ ; DO NOTHING IF NOT JMP CRT$CLS ; CLEAR SCREEN IF SO * * PRINT MESSAGE PTED TO BY RET ADR ENDING IN 0 ON CON:; 1ST TWO BYTES * GIVE ROW NUMBER OF COL NUMBER AT WHICH TO POSITION CURSOR * SCREEN$MESSAGE: XTHL ; GET PTR TO ROW, COL MOV D,M ; ROW IN D INX H MOV E,M ; COL IN E INX H ; PT TO STRING XTHL ; PREPARE FOR PRINT$MESSAGE CALL SP$STAT ; CRT AVAILABLE? ANI 1 JZ PRINT$MESSAGE ; JUST PRINT MESSAGE IF NOT AVAILABLE XCHG ; H=ROW, L=COL CALL CRT$GXY ; GOTO XY AND FALL THRU TO PRINT$MESSAGE * * PRINT MESSAGE PTED TO BY RET ADR ENDING IN 0 ON CON: * PRINT$MESSAGE: XTHL ; SAVE HL ON STACK MVI B,1 ; SET COL COUNT FOR TAB PM$LOOP: MOV A,M ; GET BYTE INX H ; PT TO NEXT BYTE (OR RET ADR) ORA A ; 0=DONE JZ PM$DONE CPI TAB ; TAB? JZ PM$TAB INR B ; ADD 1 TO COL COUNT CALL CON$OUT JMP PM$LOOP PM$TAB: MVI A,' ' ; PRINT CALL CON$OUT INR B ; ADD 1 MOV A,B ; GET COL COUNT ANI 7 ; MASK FOR TAB JNZ PM$TAB JMP PM$LOOP PM$DONE: XTHL ; RET ADR ON STACK, HL RESTORED RET * * READ LINE INTO INPUT BUFFER * READLN: PUSH H ; SAVE REGS PUSH D PUSH B CALL PRINT$DOTS ; PRINT DOTS FOR TEXT MVI C,10 ; READ LXI D,INBUF ; PT TO BUFFER CALL BDOS LXI H,INBUF+1 ; PT TO CHAR CNT MOV A,M ; GET COUNT IN A INX H ; PT TO 1ST BYTE ADD L ; PT TO LAST BYTE MOV L,A MOV A,H ACI 0 MOV H,A ; HL PTS TO LAST BYTE MVI M,0 ; PLACE ENDING 0 POP B ; RESTORE REGS POP D POP H RET * * READ LINE INTO INPUT BUFFER WITH OR WITHOUT ECHO (ECHO$FLAG); * THIS ROUTINE CANNOT BE ABORTED BY ^C SINCE IT USES DIRECT I/O * EDITING CONTROLS ARE ^U, ^X, , ^H * REGISTER CONVENTIONS: * HL = PTR TO NEXT BUFFER LOC TO PLACE NEXT CHAR * B = COUNT OF NUMBER OF CHARS IN BUFFER * C = COUNT OF NUMBER OF CHARS ON OUTPUT LINE * READLN$DIRECT: PUSH H ; SAVE REGS PUSH D PUSH B MVI B,0 ; SET CHAR COUNT MVI C,0 ; SET POSITION COUNTER LXI H,INBUF+2 ; PT TO 1ST CHAR POSITION * MAIN READ LOOP RD$LOOP: CALL CON$IN ; GET INPUT CHAR CPI CR ; EOL? JZ RD$DONE ; DONE IF SO CPI BS ; ^H? JZ RD$BS ; BACKSPACE IF SO CPI DEL ; ? JZ RD$DEL ; DELETE IF SO CPI CTRLX ; ^X? JZ RD$ERX ; ERASE LINE W/ CPI CTRLU ; ^U? JZ RD$ERU ; ERASE LINE W/# CPI ' ' ; OMIT IF < JC RD$OMIT CPI 7EH ; OMIT IF > 7EH JNC RD$OMIT * VALID CHAR -- SAVE IT MOV M,A ; SAVE CHAR INX H ; PT TO NEXT INR B ; INCR CHAR CNT INR C ; INCR POS CTR JMP RD$LOOP ; CONTINUE * RECEIVED -- DONE RD$DONE: MVI M,0 ; STORE ENDING CALL CRLF ; NEW LINE POP B ; RESTORE REGS POP D POP H RET * RECEIVED -- DELETE AND BACK UP IF ECHO ON RD$BS: MOV A,B ; NO CHARS? ORA A JZ RD$OMIT ; BEEP IF SO DCR B ; COUNT BACK DCX H ; BACK UP PTR LDA ECHO$FLAG ; ECHO? ORA A ; 0=NO JZ RD$LOOP CALL BS$OUT1 ; ECHO JMP RD$LOOP * RECEIVED -- DELETE AND ECHO CHAR DELETED IF ECHO ON RD$DEL: MOV A,B ; NO CHARS? ORA A JZ RD$OMIT DCR B ; COUNT BACK DCX H ; BACK UP PTR LDA ECHO$FLAG ; ECHO? ORA A ; 0=NO JZ RD$LOOP MOV A,M ; GET CHAR CALL CON$OUT ; ECHO IT INR C ; INCR POS CTR JMP RD$LOOP * ^X RECEIVED -- DELETE LINE AND ECHO 'S RD$ERX: LXI H,INBUF+2 ; RESET PTR MVI B,0 ; RESET CHAR COUNT MOV A,C ; ANY CHARS? ORA A JZ RD$LOOP LDA ECHO$FLAG ; ECHO? ORA A ; 0=NO JZ RD$LOOP RD$ERX$LOOP: CALL BS$OUT ; ECHO JNZ RD$ERX$LOOP JMP RD$LOOP ; C=0 NOW * ^U RECEIVED -- DELETE LINE AND ECHO # RD$ERU: LXI H,INBUF+2 ; RESET PTR MVI B,0 ; RESET CHAR COUNT MVI C,0 ; RESET POS COUNT MVI A,'#' ; ECHO '#' CALL CON$OUT CALL CRLF ; NEW LINE JMP RD$LOOP ; CONTINUE * RING BELL TO SIGNAL ERROR RD$OMIT: MVI A,BEL ; BEEP CALL CON$OUT JMP RD$LOOP * OUTPUT AND TO DELETE PREVIOUS CHARS BS$OUT: MVI A,BS ; CALL CON$OUT * ENTER HERE IF ALREADY ECHOED BS$OUT1: MVI A,' ' ; ERASE CHAR CALL CON$OUT MVI A,BS CALL CON$OUT DCR C ; COUNT CHAR POS DOWN RET * * DECREMENT TRY COUNT AND RESTART PHONECOM PROGRAM IF REACHES ZERO * DEC$TRY$CNT: LDA NUM$TRIES ; GET TRY COUNT DCR A ; COUNT DOWN STA NUM$TRIES RNZ ; RETURN IF NOT DONE YET POP H ; CLEAR STACK CALL PRINT$MESSAGE DB 'You have had 3 tries -- get help',CR,LF,0 JMP ACTIVE$ENTRY ; HANG UP PHONE * * TURN ON ECHO * ECHO$ON: MVI A,0FFH STA ECHO$FLAG RET * * TURN OFF ECHO * ECHO$OFF: MVI A,0 STA ECHO$FLAG RET * * CHECK CON: FOR ABORT CHAR AND ABORT IF SO * ABORT: PUSH H ; SAVE REGS PUSH D PUSH B MVI C,11 ; CONSOLE STATUS CALL BDOS POP B ; RESTORE REGS POP D POP H ORA A ; SET FLAGS RZ ; NO CHAR CALL CON$IN ; GET CHAR ANI 3FH ; MASK FOR ^C CPI CTRLC JNZ ABORT$NO ; DON'T ABORT POP H ; CLEAR STACK CALL RESET$IO ; RESET CON:=CRT: CALL PRINT$MESSAGE DB 'Abort Received -- Returning to CP/M',0 JMP CPM$RETURN ABORT$NO: XRA A ; RETURN ZERO TO INDICATE NO ABORT RET * * * Part 3: CP/M AND ASCII EQUATES, BUFFERS * WBADR EQU CPM$BASE+1 ; WARM BOOT ADDRESS IOBYTE EQU CPM$BASE+3 ; I/O BYTE ADDRESS BDOS EQU CPM$BASE+5 ; BDOS ENTRY POINT BUFF EQU CPM$BASE+80H ; COMMAND LINE BUFFER OPENF EQU 15 ; OPEN DISK FILE CLOSEF EQU 16 ; CLOSE DISK FILE ERASEF EQU 19 ; ERASE DISK FILE WRITEF EQU 21 ; WRITE BLOCK TO DISK FILE MAKEF EQU 22 ; CREATE DISK FILE CR EQU 13 ; LF EQU 10 ; TAB EQU 9 ; BS EQU 8 ; BEL EQU 7 ; CTRLC EQU 'C'-'@' ; CTRL-C CTRLE EQU 'E'-'@' ; CTRL-E CTRLU EQU 'U'-'@' ; CTRL-U CTRLX EQU 'X'-'@' ; CTRL-X DEL EQU 7FH ; OPT$CH EQU '/' ; OPTION DELIMITER FILENAME: DB 0,'PHONECOMCOM',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0 SUBFIL: DB 0,'$$$ SUB',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0 NUM$TRIES: DS 1 ; NUMBER OF TRIES TO LOG IN TRY$FLAG: DB 0FFH ; TRY FLAG FOR CONTROL PW; 0=2ND TIME ECHO$FLAG: DS 1 ; ECHO FLAG; 0=NO ECHO, 0FFH=ECHO INBUF: DB 40 ; NUMBER OF BYTES IN INPUT LINE BUFFER DS 41 ; INPUT LINE BUFFER DS 80 ; STACK AREA STACK: DS 2 ; STACK SAVE AREA * * * Part 4: SUPPORT PACKAGE BASE * BASE EQU $/256*256+256 ; PLACE SUPPORT PACKAGE ON EVEN BOUNDARY ORG BASE * SET DEFAULT STATUS TO NOT AVAILABLE XRA A ; A=0 RET SP$STAT EQU BASE ; SUPPORT PACKAGE STATUS SP$END EQU BASE+3 ; SUPPORT PACKAGE LAST ADDRESS ROUTINE CRT$CLS EQU BASE+24 ; CRT CLEAR SCREEN CRT$GXY EQU BASE+27 ; CRT GOTO XY TI$INI EQU BASE+126 ; TELEPHONE INTERFACE INITIALIZATION TI$ANS EQU BASE+132 ; SET ANSWER MODE TI$PICK EQU BASE+135 ; PICK UP TELEPHONE TI$HANG EQU BASE+138 ; HANG UP TELEPHONE TI$RST EQU BASE+147 ; RING STATUS DETECT TI$DST EQU BASE+150 ; DIAL TONE STATUS DETECT TI$CST EQU BASE+153 ; CARRIER STATUS DETECT TI$DSTOP EQU BASE+159 ; INITIATE COMMUNICATIONS TI$TIME EQU BASE+165 ; 0.1 SEC TIME DELAY TI$PREPANS EQU BASE+168 ; PREPARE FOR ANSWER MODE END