diff --git a/mystic/bbs_database.pas b/mystic/bbs_database.pas index 1d194d1..10b16ff 100644 --- a/mystic/bbs_database.pas +++ b/mystic/bbs_database.pas @@ -25,13 +25,16 @@ Const Function GetBaseConfiguration (UseEnv: Boolean; Var TempCfg: RecConfig) : Byte; Function PutBaseConfiguration (Var TempCfg: RecConfig) : Boolean; Function ShellDOS (ExecPath: String; Command: String) : LongInt; +Function Addr2Str (Addr : RecEchoMailAddr) : String; // MESSAGE BASE Function MBaseOpenCreate (Var Msg: PMsgBaseABS; Var Area: RecMessageBase; TP: String) : Boolean; +Function GetOriginLine (Var mArea: RecMessageBase) : String; Function GetMBaseByIndex (Num: LongInt; Var TempBase: RecMessageBase) : Boolean; Procedure GetMessageScan (UN: Cardinal; TempBase: RecMessageBase; Var TempScan: MScanRec); Procedure PutMessageScan (UN: Cardinal; TempBase: RecMessageBase; TempScan: MScanRec); +Procedure MBaseAssignData (Var User: RecUser; Var Msg: PMsgBaseABS; Var TempBase: RecMessageBase); // FILE BASE @@ -47,8 +50,87 @@ Implementation Uses DOS, m_FileIO, + m_DateTime, m_Strings; +Function Addr2Str (Addr : RecEchoMailAddr) : String; +Var + Temp : String[20]; +Begin + Temp := strI2S(Addr.Zone) + ':' + strI2S(Addr.Net) + '/' + + strI2S(Addr.Node); + + If Addr.Point <> 0 Then Temp := Temp + '.' + strI2S(Addr.Point); + + Result := Temp; +End; + +Function GetOriginLine (Var mArea: RecMessageBase) : String; +Var + Loc : Byte; + FN : String; + TF : Text; + Buf : Array[1..2048] of Char; + Str : String; + Count : LongInt; + Pick : LongInt; +Begin + Result := ''; + Loc := Pos('@RANDOM=', strUpper(mArea.Origin)); + + If Loc > 0 Then Begin + FN := strStripB(Copy(mArea.Origin, Loc + 8, 255), ' '); + + If Pos(PathChar, FN) = 0 Then FN := bbsCfg.DataPath + FN; + + FileMode := 66; + + Assign (TF, FN); + SetTextBuf (TF, Buf, SizeOf(Buf)); + + {$I-} Reset (TF); {$I+} + + If IoResult <> 0 Then Exit; + + Count := 0; + + While Not Eof(TF) Do Begin + ReadLn (TF, Str); + + If strStripB(Str, ' ') = '' Then Continue; + + Inc (Count); + End; + + If Count = 0 Then Begin + Close (TF); + Exit; + End; + + Pick := Random(Count) + 1; + + Reset (TF); + + Count := 0; + + While Not Eof(TF) Do Begin + ReadLn (TF, Str); + + If strStripB(Str, ' ') = '' Then Continue; + + Inc (Count); + + If Count = Pick Then Begin + Result := Str; + Break; + End; + End; + + Close (TF); + End Else + Result := mArea.Origin; +End; + Function GetBaseConfiguration (UseEnv: Boolean; Var TempCfg: RecConfig) : Byte; Var TempFile : File; @@ -220,6 +302,43 @@ Begin Result := True; End; +Procedure MBaseAssignData (Var User: RecUser; Var Msg: PMsgBaseABS; Var TempBase: RecMessageBase); +Var + SemFile : Text; +Begin + Msg^.StartNewMsg; + + If TempBase.Flags And MBRealNames <> 0 Then + Msg^.SetFrom(User.RealName) + Else + Msg^.SetFrom(User.Handle); + + Msg^.SetLocal (True); + + If TempBase.NetType > 0 Then Begin + If TempBase.NetType = 3 Then + Msg^.SetMailType(mmtNetMail) + Else + Msg^.SetMailType(mmtEchoMail); + + Msg^.SetOrig(bbsCfg.NetAddress[TempBase.NetAddr]); + + Case TempBase.NetType of + 1 : Assign (SemFile, bbsCfg.SemaPath + fn_SemFileEcho); + 2 : Assign (SemFile, bbsCfg.SemaPath + fn_SemFileNews); + 3 : Assign (SemFile, bbsCfg.SemaPath + fn_SemFileNet); + End; + + ReWrite (SemFile); + Close (SemFile); + End Else + Msg^.SetMailType(mmtNormal); + + Msg^.SetPriv (TempBase.Flags and MBPrivate <> 0); + Msg^.SetDate (DateDos2Str(CurDateDos, 1)); + Msg^.SetTime (TimeDos2Str(CurDateDos, 0)); +End; + Function GetTotalFiles (Var TempBase: RecFileBase) : LongInt; Begin Result := 0; diff --git a/mystic/bbs_msgbase_qwk.pas b/mystic/bbs_msgbase_qwk.pas index 648bf83..fb13c44 100644 --- a/mystic/bbs_msgbase_qwk.pas +++ b/mystic/bbs_msgbase_qwk.pas @@ -13,7 +13,8 @@ Uses BBS_MsgBase_Squish; Const - QWK_EOL = #13#10; + QWK_EOL = #13#10; + QWK_CONTROL = 'MYSTICQWK'; Type BSingle = Array [0..3] of Byte; @@ -140,7 +141,7 @@ Begin Write (TempFile, 'DOOR = ' + mysSoftwareID + QWK_EOL); Write (TempFile, 'VERSION = ' + mysVersion + QWK_EOL); Write (TempFile, 'SYSTEM = ' + mysSoftwareID + ' ' + mysVersion + QWK_EOL); - Write (TempFile, 'CONTROLNAME = MYSTICQWK' + QWK_EOL); + Write (TempFile, 'CONTROLNAME = ' + QWK_CONTROL + QWK_EOL); Write (TempFile, 'CONTROLTYPE = ADD' + QWK_EOL); Write (TempFile, 'CONTROLTYPE = DROP' + QWK_EOL); Close (TempFile); @@ -286,7 +287,7 @@ Begin While MsgBase^.SeekFound Do Begin If Not IsNetworked Then - If ((bbsCfg.QwkMaxBase > 0) and (MsgAdded = bbsCfg.QwkMaxBase)) or + If ((bbsCfg.QwkMaxBase > 0) and (MsgAdded = bbsCfg.QwkMaxBase)) or ((bbsCfg.QwkMaxPacket > 0) and (TotalMessages = bbsCfg.QwkMaxPacket)) Then Break; MsgBase^.MsgStartUp; @@ -453,13 +454,13 @@ Begin If HasAccess(Self, MBase.ReadACS) Then Begin - GetMessageScan (UserNumber, MBase, MScan); + GetMessageScan (UserNumber, MBase, MScan); - If MScan.QwkScan > 0 Then Begin - Inc (TotalBases); + If MScan.QwkScan > 0 Then Begin + Inc (TotalBases); - QwkLR.Base := FilePos(MBaseFile); - QwkLR.Pos := WriteMSGDAT; + QwkLR.Base := FilePos(MBaseFile); + QwkLR.Pos := WriteMSGDAT; Write (QwkLRFile, QwkLR); End; @@ -481,8 +482,190 @@ Begin End; Function TQWKEngine.ProcessReply : Boolean; + + Procedure QwkControl (Idx: LongInt; Mode: Byte); + Var + TempBase : RecMessageBase; + TempScan : MScanRec; + Begin + If GetMBaseByIndex(Idx, TempBase) Then Begin + GetMessageScan (UserNumber, TempBase, TempScan); + + TempScan.QwkScan := Mode; + + If Mode = 0 Then Inc (RepBaseDel); + If Mode = 1 Then Inc (RepBaseAdd); + + PutMessageScan (UserNumber, TempBase, TempScan); + End; + End; + +Var + QwkBlock : String[128]; + QwkHeader : QwkDATHdr; + Chunks : SmallInt; + Line : String; + LineCount : SmallInt; + IsControl : Boolean; + GotControl : Boolean; + ExtFile : Text; + Count1 : SmallInt; + Count2 : SmallInt; Begin Result := False; + + DataFile := TFileBuffer.Create(4 * 1024); + + If Not DataFile.OpenStream (FileFind(WorkPath + PacketID + '.msg'), 1, fmOpen, fmRWDN) Then Begin + DataFile.Free; + + DirClean (WorkPath, ''); + + Exit; + End; + + DataFile.ReadBlock(QwkBlock[1], 128); + QwkBlock[0] := #128; + + If Pos(strUpper(PacketID), strUpper(QwkBlock)) = 0 Then Begin + DataFile.Free; + + DirClean(WorkPath, ''); + + Exit; + End; + + While Not DataFile.EOF Do Begin + DataFile.ReadBlock(QwkHeader, SizeOf(QwkHeader)); + + Move (QwkHeader.MsgNum, QwkBlock[1], 7); + + QwkBlock[0] := #7; + + If GetMBaseByIndex(strS2I(QwkBlock), MBase) Then Begin + + If MBaseOpenCreate(MsgBase, MBase, WorkPath) Then Begin + + MBaseAssignData(UserRecord, MsgBase, MBase); + + QwkBlock[0] := #25; + Move (QwkHeader.UpTo, QwkBlock[1], 25); + MsgBase^.SetTo(strStripR(QwkBlock, ' ')); + + Move (QwkHeader.Subject, QwkBlock[1], 25); + MsgBase^.SetSubj(strStripR(QwkBlock, ' ')); + + Move (QwkHeader.ReferNum, QwkBlock[1], 6); + QwkBlock[0] := #6; + + MsgBase^.SetRefer(strS2I(strStripR(QwkBlock, ' '))); + + Move (QwkHeader.NumChunk, QwkBlock[1], 6); + + Chunks := strS2I(QwkBlock) - 1; + Line := ''; + LineCount := 0; + IsControl := MsgBase^.GetTo = QWK_CONTROL; + GotControl := False; + + // disable control in network packets (for now?) + + If IsNetworked Then + IsControl := False; + + If IsControl And ((MsgBase^.GetSubj = 'ADD') or (MsgBase^.GetSubj = 'DROP')) Then + QwkControl (MBase.Index, Ord(MsgBase^.GetSubj = 'ADD')); + + For Count1 := 1 to Chunks Do Begin + DataFile.ReadBlock (QwkBlock[1], 128); + + QwkBlock[0] := #128; + QwkBlock := strStripR(QwkBlock, ' '); + + For Count2 := 1 to Length(QwkBlock) Do Begin + If QwkBlock[Count2] = #227 Then Begin + Inc (LineCount); + + If (LineCount < 4) and (Copy(Line, 1, 5) = 'From:') Then Begin + GotControl := True; + + // ignore from name unless its networked + + If IsNetworked Then + MsgBase^.SetTo(strStripB(Copy(Line, 6, Length(Line)), ' ')); + End Else + If (LineCount < 4) and (Copy(Line, 1, 3) = 'To:') Then Begin + MsgBase^.SetTo(strStripB(Copy(Line, 4, Length(Line)), ' ')); + GotControl := True; + End Else + If (LineCount < 4) and (Copy(Line, 1, 8) = 'Subject:') Then Begin + MsgBase^.SetSubj(strStripB(Copy(Line, 9, Length(Line)), ' ')); + GotControl := True; + End Else + If GotControl And (Line = '') Then + GotControl := False + Else + MsgBase^.DoStringLn(Line); + + Line := ''; + End Else + Line := Line + QwkBlock[Count2]; + End; + End; + + If Line <> '' Then + MsgBase^.DoStringLn(Line); + + If MBase.NetType > 0 Then Begin + If IsNetworked Then Begin + MsgBase^.DoStringLn (#13 + '--- ' + mysSoftwareID + '/QWK v' + mysVersion + ' (' + OSID + ')'); + MsgBase^.DoStringLn (' * Origin: ' + GetOriginLine(MBase)); + End Else Begin + MsgBase^.DoStringLn (#13 + '--- ' + mysSoftwareID + '/QWK v' + mysVersion + ' (' + OSID + ')'); + MsgBase^.DoStringLn (' * Origin: ' + GetOriginLine(MBase) + ' (' + Addr2Str(MsgBase^.GetOrigAddr) + ')'); + End; + End; + + If Not IsControl Then Begin + If HasAccess(Self, MBase.PostACS) Then Begin + MsgBase^.WriteMsg; + Inc (RepOK); // must increase user and history posts by repOK + End Else + Inc (RepFailed); + End; + + MsgBase^.CloseMsgBase; + + Dispose (MsgBase, Done); + End Else + Inc (RepFailed); + End Else + Inc (RepFailed); + End; + + DataFile.Free; + + Assign (ExtFile, FileFind(WorkPath + 'todoor.ext')); + {$I-} Reset (ExtFile); {$I+} + + If IoResult = 0 Then Begin + While Not Eof(ExtFile) Do Begin + ReadLn (ExtFile, Line); + + If strWordGet(1, Line, ' ') = 'AREA' Then Begin + QwkBlock := strWordGet(3, Line, ' '); + + If Pos('a', QwkBlock) > 0 Then QwkControl(strS2I(strWordGet(2, Line, ' ')), 1); + If Pos('D', QwkBlock) > 0 Then QwkControl(strS2I(strWordGet(2, Line, ' ')), 0); + End; + End; + + Close (ExtFile); + End; + + DirClean (WorkPath, ''); + + Result := True; End; End. diff --git a/mystic/mis_client_ftp.pas b/mystic/mis_client_ftp.pas index 7efadcb..b65b0ed 100644 --- a/mystic/mis_client_ftp.pas +++ b/mystic/mis_client_ftp.pas @@ -4,10 +4,6 @@ Unit MIS_Client_FTP; {.$DEFINE FTPDEBUG} -// does not send file/directory datestamps -// does not support uploading (need to make bbs functions generic for this -// and for mbbsutil -fupload command) - Interface Uses @@ -54,15 +50,17 @@ Type Function OpenDataSession : Boolean; Procedure CloseDataSession; Procedure ResetSession; - Procedure UpdateUserStats (TFBase: RecFileBase; FDir: RecFileList; DirPos: LongInt); + Procedure UpdateUserStats (TFBase: RecFileBase; FDir: RecFileList; DirPos: LongInt; IsUpload: Boolean); Function CheckFileLimits (TempFBase: RecFileBase; FDir: RecFileList) : Byte; Function ValidDirectory (TempBase: RecFileBase) : Boolean; Function FindDirectory (Var TempBase: RecFileBase) : LongInt; Function GetQWKName : String; Function GetFTPDate (DD: LongInt) : String; Procedure SendFile (Str: String); + Function RecvFile (Str: String; IsAppend: Boolean) : Boolean; Function QWKCreatePacket : Boolean; + Procedure QWKProcessREP; Procedure cmdUSER; Procedure cmdPASS; @@ -75,7 +73,7 @@ Type Procedure cmdLIST; Procedure cmdPWD; Procedure cmdRETR; - Procedure cmdSTOR; + Procedure cmdSTOR (IsAppend: Boolean); Procedure cmdSTRU; Procedure cmdMODE; Procedure cmdSYST; @@ -161,17 +159,16 @@ Begin InTransfer := False; End; -Procedure TFTPServer.UpdateUserStats (TFBase: RecFileBase; FDir: RecFileList; DirPos: LongInt); +Procedure TFTPServer.UpdateUserStats (TFBase: RecFileBase; FDir: RecFileList; DirPos: LongInt; IsUpload: Boolean); Var - HistFile: File of RecHistory; - History : RecHistory; - FDirFile: File of RecFileList; - UserFile: File of RecUser; + HistFile : File of RecHistory; + History : RecHistory; + FDirFile : File of RecFileList; + UserFile : File of RecUser; Begin - Inc (FDir.Downloads); - + // change to getuserbypos Assign (UserFile, bbsConfig.DataPath + 'users.dat'); - ioReset (UserFile, SizeOf(RecUser), fmReadWrite + fmDenyWrite); + ioReset (UserFile, SizeOf(RecUser), fmRWDW); ioSeek (UserFile, UserPos - 1); ioRead (UserFile, User); @@ -179,28 +176,33 @@ Begin User.CallsToday := 0; User.DLsToday := 0; User.DLkToday := 0; - User.TimeLeft := SecLevel.Time + User.TimeLeft := SecLevel.Time; + User.LastOn := CurDateDos; End; - // need to check if it were an upload and do things accordingly + If IsUpload Then Begin + Inc (User.ULs); + Inc (User.ULk, FDir.Size DIV 1024); + End Else Begin + Inc (FDir.Downloads); + Inc (User.DLs); + Inc (User.DLsToday); + Inc (User.DLk, FDir.Size DIV 1024); + Inc (User.DLkToday, FDir.Size DIV 1024); - Inc (User.DLs); - Inc (User.DLsToday); - Inc (User.DLk, FDir.Size DIV 1024); - Inc (User.DLkToday, FDir.Size DIV 1024); + Assign (FDirFile, bbsConfig.DataPath + TFBase.FileName + '.dir'); + ioReset (FDirFile, SizeOf(RecFileList), fmRWDW); + ioSeek (FDirFile, DirPos - 1); + ioWrite (FDirFile, FDir); + Close (FDirFile); + End; ioSeek (UserFile, UserPos - 1); ioWrite (UserFile, User); Close (UserFile); - Assign (FDirFile, bbsConfig.DataPath + TFBase.FileName + '.dir'); - ioReset (FDirFile, SizeOf(RecFileList), fmReadWrite + fmDenyWrite); - ioSeek (FDirFile, DirPos - 1); - ioWrite (FDirFile, FDir); - Close (FDirFile); - Assign (HistFile, bbsConfig.DataPath + 'history.dat'); - ioReset (HistFile, SizeOf(RecHistory), fmReadWrite + fmDenyWrite); + ioReset (HistFile, SizeOf(RecHistory), fmRWDW); If IoResult <> 0 Then ReWrite(HistFile); @@ -217,11 +219,17 @@ Begin If Eof(HistFile) Then Begin FillChar(History, SizeOf(History), 0); + History.Date := CurDateDos; End; - Inc (History.Downloads, 1); - Inc (History.DownloadKB, FDir.Size DIV 1024); + If IsUpload Then Begin + Inc (History.Uploads); + Inc (History.UploadKB, FDir.Size DIV 1024); + End Else Begin + Inc (History.Downloads); + Inc (History.DownloadKB, FDir.Size DIV 1024); + End; ioWrite (HistFile, History); Close (HistFile); @@ -282,6 +290,7 @@ Begin If DataSocket <> NIL Then Begin Client.WriteLine(re_DataOpen); + Result := True; Exit; End; @@ -301,6 +310,7 @@ Begin If Not Assigned(DataSocket) Then Begin WaitSock.Free; Client.WriteLine(re_NoData); + Exit; End; @@ -312,6 +322,7 @@ Begin Client.WriteLine(re_NoData); DataSocket.Free; DataSocket := NIL; + Exit; End; End; @@ -420,6 +431,57 @@ Begin End; End; +Function TFTPServer.RecvFile (Str: String; IsAppend: Boolean) : Boolean; +Var + F : File; + Buf : Array[1..FileXferSize] of Byte; + Res : LongInt; +Begin + Result := False; + + If FileExist(Str) And Not IsAppend Then Begin + Client.WriteLine(re_BadFile); + + Exit; + End; + + If Not OpenDataSession Then Exit; + + Server.Status ('Receiving: ' + Str); + + InTransfer := True; + Result := True; + + Assign (F, Str); + + If FileExist(Str) And IsAppend Then Begin + Reset (F, 1); + Seek (F, FileSize(F)); + End Else Begin + ReWrite (F, 1); + + IsAppend := False; + End; + + Repeat + Res := DataSocket.ReadBuf(Buf[1], SizeOf(Buf)); + + If Res > 0 Then + BlockWrite (F, Buf[1], Res) + Else + Break; + Until False; + + Close (F); + + If Result Then + Client.WriteLine (re_XferOK); + + CloseDataSession; + + InTransfer := False; +End; + Procedure TFTPServer.SendFile (Str: String); Var F : File; @@ -434,11 +496,14 @@ Begin OpenDataSession; + Server.Status('Sending: ' + Str); + While Not Eof(F) Do Begin BlockRead (F, Buf, SizeOf(Buf), Res); Repeat Tmp := DataSocket.WriteBuf(Buf, Res); + Dec (Res, Tmp); Until Res <= 0; End; @@ -475,10 +540,33 @@ Begin QWK.UpdateLastReadPointers; QWK.Free; - Server.Status ('Created packet in ' + TempPath); - ExecuteArchive (TempPath, TempPath + GetQWKName + '.qwk', User.Archive, TempPath + '*', 1); SendFile (TempPath + GetQWKName + '.qwk'); + + DirClean (TempPath, ''); +End; + +Procedure TFTPServer.QWKProcessREP; +Var + QWK : TQwkEngine; +Begin + // need to change temppath to a unique directory created for this + // ftp instance. before that we need to push a unique ID to this + // session. + + RecvFile (TempPath + GetQWKName + '.rep', False); + ExecuteArchive (TempPath, TempPath + GetQWKName + '.rep', User.Archive, '*', 2); + + QWK := TQwkEngine.Create(TempPath, GetQWKName, UserPos, User); + + QWK.HasAccess := @QWKHasAccess; + QWK.IsNetworked := User.Flags AND UserQWKNetwork <> 0; + QWK.IsExtended := User.QwkExtended; + + QWK.ProcessReply; + QWK.Free; + + // update user stats posts and bbs history if not networked End; Procedure TFTPServer.cmdUSER; @@ -737,7 +825,7 @@ Begin If (Dir.Flags And FDirDeleted <> 0) Then Continue; If (Dir.Flags and FDirOffline <> 0) And (Not CheckAccess(User, True, bbsConfig.AcsSeeOffline)) Then Continue; If (Dir.Flags And FDirInvalid <> 0) And (Not CheckAccess(User, True, bbsConfig.AcsSeeUnvalid)) Then Continue; - If (Dir.Flags And FDirFailed <> 0) And (Not CheckAccess(User, True, bbsConfig.AcsSeeFailed)) Then Continue; + If (Dir.Flags And FDirFailed <> 0) And (Not CheckAccess(User, True, bbsConfig.AcsSeeFailed)) Then Continue; If WildMatch(FileMask, Dir.FileName, False) Then DataSocket.WriteLine('-rw-r--r-- 1 ftp ftp ' + strPadL(strI2S(Dir.Size), 13, ' ') + ' ' + GetFTPDate(Dir.DateTime) + ' ' + Dir.FileName) @@ -753,7 +841,7 @@ Begin Client.WriteLine(re_BadCommand); End; -Procedure TFTPServer.cmdSTOR; +Procedure TFTPServer.cmdSTOR (IsAppend: Boolean); Var TempPos : LongInt; TempBase : RecFileBase; @@ -764,6 +852,12 @@ Begin Exit; End; + If strUpper(Data) = strUpper(GetQWKName + '.rep') Then Begin + QWKProcessREP; + + Exit; + End; + TempPos := FindDirectory(TempBase); If (TempPos = -1) Or Not ValidDirectory(TempBase) Then Begin @@ -772,17 +866,27 @@ Begin Exit; End; - Client.WriteLine(re_BadFile); + server.status('calling recvfile'); - //reasons why i haven't finished this (todo): + RecvFile ('d:\code\mystic1\temp0\infile.tmp', IsAppend); - // ratios - // diskspace +// Client.WriteLine(re_BadFile); + + // dreadful things required to do for upload process: + + // find upload base + // check diskspace + // check slowmedia + // check access + // check filename length + // duplicate file checking + // get file + // update user statistics + // update history statistics // archive testing // file_id.diz importing - // forcing uploads to upload base (if non-zero) - // duplicate file checking - // upload statistic tracking + + // other things: add no desc and ftp test batch to configuration? End; Procedure TFTPServer.cmdRETR; @@ -818,6 +922,7 @@ Begin If WildMatch(FileMask, Dir.FileName, False) Then Begin Found := DirFile.FilePosRecord; + Break; End; End; @@ -833,7 +938,7 @@ Begin Case CheckFileLimits(TempBase, Dir) of 0 : Begin SendFile (TempBase.Path + Dir.FileName); - UpdateUserStats (TempBase, Dir, Found); + UpdateUserStats (TempBase, Dir, Found, False); End; 1 : Client.WriteLine(re_NoAccess); 2 : Client.WriteLine(re_DLLimit); @@ -956,6 +1061,7 @@ Begin Server.Status ('Cmd: ' + Cmd + ' Data: ' + Data); {$ENDIF} + //If Cmd = 'APPE' Then cmdSTOR(True) Else If Cmd = 'CDUP' Then cmdCDUP Else If Cmd = 'CWD' Then cmdCWD Else If Cmd = 'DELE' Then Client.WriteLine(re_NoAccess) Else @@ -974,7 +1080,8 @@ Begin If Cmd = 'RETR' Then cmdRETR Else If Cmd = 'RMD' Then Client.WriteLine(re_NoAccess) Else If Cmd = 'SIZE' Then cmdSIZE Else - If Cmd = 'STOR' Then cmdSTOR Else + If Cmd = 'STOR' Then cmdSTOR(False) Else + // implement STOU which in turn calls cmdSTOR after getting filename If Cmd = 'STRU' Then cmdSTRU Else If Cmd = 'SYST' Then cmdSYST Else If Cmd = 'TYPE' Then cmdTYPE Else diff --git a/mystic/whatsnew.txt b/mystic/whatsnew.txt index ec328a6..fa9a20e 100644 --- a/mystic/whatsnew.txt +++ b/mystic/whatsnew.txt @@ -3605,6 +3605,12 @@ the file base editor for their names to be automatically changed. + Users can now download QWK packets using the FTP server. A QWK packet - filename will be shown in all FTP listings. + filename will be shown in all FTP listings. If the user is flagged as a + FTP network account, their handle will be used as the BBSID for the QWK + packet (handle.qwk). + + + Users can now upload QWK reply packets using the FTP server. If the user + is flagged as a FTP network account, their reply packet will need to be + "handle.rep".