一歩進んだ Rexx

Classic REXX (REstructured eXtended eXecutor)
File #05

FTP Server (そのほか) -- '99.2.18

--- つづき --- ≪ 前回へもどる

さて, FTP サーバ最終回。
これらを組み立てて,「ちょっと FTPサーバ動かしてみたー」っつー人は出てくるだろうか。 それとも「これを元にして C言語で新たに作成したー」とかゆーひとは現れるだろうか。 そんな人が現れたら面白いだろうな。わくわく。(←勝手なことゆーな)

それはさておき解説しよー。

CutStart:
   /*Rc = SockGetPeerName(sk, 'remaddr.')*/
   /*say PLog('FTP logoff from' remaddr.addr 'at' date() time())*/
   if SockShutdown(sk, 2) < 0 then say 'Shutdown: err='errno 'h='h_errno
   if SockSoClose(sk) < 0 then say 'Close: err='errno 'h='h_errno

   n = wordpos(sk, sklist)
   if n > 0 then sklist = space(delword(sklist, n, 1))
   say 'SkList='sklist
return

これは (↑), メインから呼び出される通信の切断の部分。 クライアントからの切断を受けてここに入ってくるんだども, とりあえず Shutdown()は行っている。
こんなもんかな, 解説は。

ReplyMesg: procedure expose nl
   parse arg sk, code msg, ext
   code = right(code, 3, 0)
   if msg = '' then
   select
      when code == 200 then msg = 'Command okay.'
      when code == 500 then msg = 'Syntax error, command unrecognized.'
      when code == 501 then msg = 'Syntax error in parameters or arguments.'
      when code == 202 then msg = 'Command not implemented, superfluous at this site.'
      when code == 502 then msg = 'Command not implemented.'
      when code == 503 then msg = 'Bad sequence of commands.'
      when code == 504 then msg = 'Command not implemented for that parameter.'

      when code == 221 then msg = 'Goodbye.'
      when code == 425 then msg = 'Can''t open data connection.'
      when code == 226 then msg = 'Closing data connection.'

      when code == 230 then msg = 'User logged in, proceed.'
      when code == 530 then msg = 'Not logged in.'
      when code == 331 then msg = 'User name okay, need password.'

      when code == 150 then msg = 'File status okay; about to open data connection.'
      when code == 250 then msg = 'Requested file action okay, completed.'
      when code == 550 then msg = '%s Not found'
   otherwise
      msg = '"'code'"'
   end
   n = pos('%s', msg)
   if n > 0 then msg = left(msg, n -1)ext''substr(msg, n +2)

   do while pos(nl, msg) > 0
      parse var msg txt (nl) msg
      if msg = '' then do; msg = txt; leave; end;
      if SockSend(sk, code'-'strip(txt)nl) < 0 then
         say 'Send: err='errno 'h='h_errno
   end
   if SockSend(sk, code msg nl) < 0 then say 'Send: err='errno 'h='h_errno
return ''

(↑)クライアントに送り返したいメッセージを作り出して, そして送信するってゆー部分。 幾つかの 3桁の数字は, FTP のプロトコルで決まっているもの (File Transfer Protocol プロトコル っつったら重複していて何だか変な気がしないでもない)。

複数行のメッセージの場合は, 数字のすぐ後に "-" がつく。 例えは, "220-こんにちは"CRLF, "220-これは"CRLF, "220 FTP 鯖っす。"CRLF ・・ってな感じ。 クライアントは, それをみて読み飛ばしたりする。 (ってゆーのは決まり事であって, 〆(^^) が勝手に決めた訳じゃないんだよ)

DataConnection: parse arg DataConnection; return DataConnectionCore();
DataConnectionCore: procedure expose (DataConnection) PLog
   val = value(DataConnection)
   parse var val raddr rport val
   call value DataConnection, val
   if raddr = '' | \datatype(rport, 'W') then return -1

   dtsk = SockSocket('AF_INET', 'SOCK_STREAM', 0)
   if dtsk < 0 then return dtsk

   locaddr.family = 'AF_INET'
   locaddr.port = 20   /* -- 20; FTP 'data port' */
   locaddr.addr = 'INADDR_ANY'
   if SockBind(dtsk, 'locaddr.') < 0 then
      say PLog('data port bind err') 'Bind: err='errno 'h='h_errno

   remaddr.family = 'AF_INET'
   remaddr.addr = raddr
   remaddr.port = rport
   Rc = SockConnect(dtsk, 'remaddr.')
   if Rc < 0 then do
      say 'Connect: err='errno 'h='h_errno
      if SockSoClose(dtsk) < 0 then say 'Close: err='errno 'h='h_errno
      dtsk = -1
   end
return dtsk

ここは (↑), FTPクライアントが "ここに接続しに来てね"ってゆっておいた場所に, 接続しようとする部分。 分かりにくいかな。やっぱ・・
まっ, こーゆーもんさっ。

3階に, もとい 3回に渡って続いた FTPサーバー。Eメールの連載ほどまでは長くないけど, やっぱ長い (?)。 これを使って何ができるのか(新たにってゆー意味ね), それは 〆(^^) も分からない (だれか教えて)。
作ってから意味を考えるってーのも善し悪しだねっ。

うじゃ((C)竹本泉) そゆことで (^o^)/~


FTP Server (FTP コマンド) -- '99.2.8

--- つづき --- ≪ 前回へもどる

今回コマンドの部分。これすんごく長い。あんまし長くって, 一部ブラウザで枠が出ない (T_T
まー, 説明すんのにも分けた方がいいんだろっつーんで分けちった。(^_^)

SMTPでもそーだったけど, FTPでも, クライアントからは 4文字の英字, サーバーからはその応答として 3文字の数字(この場合?)がやりとりされる。
ここいらへんは RFC0959 を参照のこと。逃げてるなぁ とか思わないよーに。実際逃げてんだけどさっ (笑)。

で, ソレによると 最小限の実装(MINIMUM IMPLEMENTATION)とゆーのがあって, (↓)も, その最小限に近い。 とゆーより, (よく覚えていないんだけど, もしかしたら)最小限のものさえ実装していなかったよーな気もする。 あ, そんでも Warpの "FTP ホスト" Objectでアクセスできたよーな記憶はある。

書いてて心配になってきた (^^;; 読むほうはもっとアレかもしんないけど・・

DoExecCmd:   /*procedure expose nl PLog sklist CnAddr.*/
parse arg sk, cmd str
cmd = translate(cmd)
select
   when cmd == 'SYST' then Rc = ReplyMesg(sk, 215 'WARP Rexx')
   when cmd == 'QUIT' then Rc = ReplyMesg(sk, 221)
   when cmd == 'NOOP' then Rc = ReplyMesg(sk, 200)

   when cmd == 'USER' then do
      Rc = SockGetPeerName(sk, 'remaddr.')
      say PLog('FTP login from' remaddr.addr';' str)
      Rc = ReplyMesg(sk, 331)
   end
   when cmd == 'PASS' then do
      if True
      then Rc = ReplyMesg(sk, 230)
      else Rc = ReplyMesg(sk, 530)
   end

   when cmd == 'PORT' then do
      parse var str h1','h2','h3','h4','p1','p2
      if datatype(h1, 'W')datatype(h2, 'W')datatype(h3, 'W')datatype(h4, 'W'),
         datatype(p1, 'W')datatype(p2, 'W') == 1111 11 then do
         if symbol('CnAddr.sk') == 'LIT' then CnAddr.sk = ''
         CnAddr.sk = CnAddr.sk h1'.'h2'.'h3'.'h4 p1 *256 +p2
         Rc = ReplyMesg(sk, 200)
      end
      else Rc = ReplyMesg(sk, 500)
      drop h1 h2 h3 h4 p1 p2
   end
   when cmd == 'TYPE' then do
      n = left(word(str, 1), 1)
      if pos(n, 'AI') > 0
         then Rc = ReplyMesg(sk, '200 Type set to' n'.')
         else Rc = ReplyMesg(sk, 504)
   end

(↑)うーん, これみるとユーザー認証もへったくれもないな。笑ってごまかすか・・。 ASCII TYPE とか IMAGE TYPE とかも, なんにもしてないしぃ。 まぁ, いいかぁ。各自が実装するってことで・・ (^o^)

PORT は, クライアントがデータ転送用のポートを指定するもの。
dir, get, put などを実行する時に, それに先立ってクライアントから発行される。 サーバーが, その内容を送受信する時には, ここに接続するってゆーふーになってる。 むつかしいねっ FTPって。(逆パターンもあるんだけどパス, わはは)

   when cmd == 'RETR' then do   /* Retrieve */
      fname = str
      if fname = '' then return ReplyMesg(sk, 501)
      if Stream(fname, 'c', 'Query exists') == '' then
         return ReplyMesg(sk, 550, fname)
      dtsk = DataConnection('CnAddr.'sk)
      if dtsk < 0 then return ReplyMesg(sk, 425)
      sklist = sklist dtsk
      Rc = ReplyMesg(sk, 150)
      Rc = stream(fname, 'c', 'open read')
      val = CharIn(fname,, Chars(fname))
      Rc = Stream(fname, 'c', 'Close')
      Rc = SockSend(dtsk, val)
      if SockShutdown(dtsk, 1) < 0 then say 'Shutdown: err='errno 'h='h_errno
      Rc = ReplyMesg(sk, 226)
   end
   when cmd == 'STOR' then do   /* Store */
      fname = str
      if fname = '' then do
         fname = '$FTP.tmp'
         say PLog('FILE='fname)
      end
      dtsk = DataConnection('CnAddr.'sk)
      if dtsk < 0 then return ReplyMesg(sk, 425)
      sklist = sklist dtsk
      if Stream(fname, 'c', 'Query exists') > '' then
         Rc = CharOut(fname,, 1)
      Rc = ReplyMesg(sk, 150)
      do Forever
         if SockRecv(dtsk, 'txt', MAX_RECVLN) <= 0 then leave
         Rc = CharOut(fname, txt)
      end
      Rc = Stream(fname, 'c', 'Close')
      Rc = ReplyMesg(sk, 226)
   end

(↑)ここは, getputの所だねっ。(←えらく簡単な説明だな)

   when cmd == 'PWD' then do
      val = Directory()
      if DBRight(val, 1) \= '\' then val = val'\'
      val = FileSpec('D', val)translate(FileSpec('P', val), '/', '\')
      Rc = ReplyMesg(sk, '257 "'val'" です')
   end
   when cmd == 'CWD' then do
      if pos(DBRight(str, 1), '\/') > 0 then
         str = left(str, length(str) -1)
      val = Directory(str)
      if val > ''
         then Rc = ReplyMesg(sk, 250)
         else Rc = ReplyMesg(sk, 550, val)
   end

   when cmd == 'LIST' | cmd == 'NLST' then do
      dtsk = DataConnection('CnAddr.'sk)
      if dtsk < 0 then return ReplyMesg(sk, 425)
      sklist = sklist dtsk
      Rc = ReplyMesg(sk, 150)
      if str = '' then str = '.'
      Rc = SysFileTree(str, 'file')
      val = ''
      do i = 1 to file.0
         parse var file.i d t s a n
         if cmd == 'LIST' then
            val = right(s, 10) translate(a,, '-')right(d, 9)right(t, 7)' '
         Rc = SockSend(dtsk, val''FileSpec('N', n)nl)
      end
      if SockShutdown(dtsk, 1) < 0 then say 'Shutdown: err='errno 'h='h_errno
      Rc = ReplyMesg(sk, 226)
   end
otherwise
   say PLog('???:['cmd']')
   Rc = ReplyMesg(sk, 502)
end
return

(↑)ここは, ディレクトリ関係とか, ファイル一覧とか。
・・あんま説明になってないねー。分けることもなかったのかも・・。

おー, そーそー。この FTPサーバーを動かすには Hobbes 版の RxSockが必要。 そーじゃないと, 途中で停止してしまう。Warp 4.5なら大丈夫かも
でも, こんだけじゃ動かないんだけどねっ。わははははっ。

FTP サーバーも いよいよ大詰め。「キミはこの話についてこれるか」 (←何をゆっとんじゃ)

≫ 次回へすすむ


記録 ( ログ ) -- '99.1.25

ログ取りホイホイ。
えとー, say 'エラー' errno とかするのを, それに似た感じの形式で(?)記録するためのもの。 どーゆーふーに使うのかと言えば, say PLog('エラー' errno) と, こーんな感じ。 ログだけ記録して, 表示なんかしなくってよいってゆーんだったら,
call PLog 'エラー' errno ←こーんなふーに呼び出すのだ。

中身はとゆーと・・

PLog: procedure expose PLog
parse arg txt, FName sz cnt
if FName == '' then parse var PLog FName sz cnt
if stream(FName, 'c', 'query size') > sz then do
   Rc = stream(FName, 'c', 'close')
   parse var FName n'.'
   do i = 1 to cnt until Rc = 0
      ext = right(i, 3, 0)
      str = translate(stream(n'.'ext, 'c', 'query datetime'),, '-')
      y = word(str, 3)
      if datatype(y, 'W') then y = y +word(1900 2000, (y <= 50) +1)
      str = y subword(str, 1, 2) subword(str, 4)
      if str = '' then '@ren' FName '*.'ext '2>nul'
      else if str < val then val = str ext
   end
   if Rc \= 0 then do
      ext = right(val, 3)
      if datatype(ext, 'W') then do
         '@erase' n'.'ext
         '@ren' FName '*.'ext '2>nul'
      end
   end
end
call LineOut FName, txt
return txt
・・やってる内容の割にでかいなっ とか思ったねっ?

こりは, ある容量を越すと次のファイルにバトンタッチして, 次々とところてん方式にファイルを作っていくからなのだー。 こーゆーことをやってるんでぇー, 最初にその定義が必要。それは例えば次のように指定する。
PLog = 'FTPsvr.log 40960 5'
最初のがファイル名, 次のが容量限界, そして最後が ところてんの長さ (←?)。

こりは DOSとかには移植が大変。なぜかっつーと renの戻り値が Rcに入ってこないから。
それから, これ自身にも ちょっち問題がない事も・・・。たとえばオープンして何かやってクローズするところまで ってゆーよーな時に, その内容がファイルにまたがって記録されっかもしんないっつーこと。
これの解決策は, PLogを毎回呼び出すんじゃなくって, ある程度内容をまとめてから呼び出す とゆー方法がある。


ちょびっと追加。'query timestamp' だったら[年]が先頭なのでよいんだけど (しかもちゃんと 4桁), 'query datetime' だったら[年]は途中に入ってるんだった。わっはっはー。
じゃ 前者のを使えよってゆー声が聞こえてくるよーな気もするけど, これをサポートしていないタイプの Rexxもあるんで・・。

ところで関係ない話だけれど, こーゆーのを公開する時, 色をどうしたもんか・・

くぅーっ, センスのなさが身に染みる (T_T

File Transfer Protocol(FTP) 鯖 (1) -- '99.1.16

うーむっ, 今回 FTPサーバに挑戦してみる。 挑戦とゆーのは他でもない, 『これに興味がある人がいるだろうか』とかゆーこと。 だってねぇー, Warpには標準で FTPサーバついてるかんねぇ。 わざわざこれを取り上げる必要があるのか・・とか思ってしまう。 でも, それってば SilkWebとか 〆(^^) が作るソフト全般に言える事かもしんない。わっはっはー。

まずは, 初期処理。

signal on halt name ServHalt

socket = SockSocket('AF_INET', 'SOCK_STREAM', 0)
sklist = socket
say PLog(center(' FTP Start ', 30, '='))
say PLog('FTP Server sock='socket date() time())

locaddr.family = 'AF_INET'
locaddr.port = 21   /* -- 21; FTP 'command port' */
locaddr.addr = 'INADDR_ANY'
do i = 1
   if SockBind(socket, 'locaddr.') == 0 then leave
   if i > BindWait then do
      say 'Bind: err='errno 'h='h_errno
      signal ServHalt
   end
   call SysSleep 1
end

if SockListen(socket, NumListen) < 0 then
   say 'Listen: err='errno 'h='h_errno

えー, FTPでのコマンドポートの番号は 21番。そうそ, FTPではコマンドのポートと データのポートの 2つの接続によって, いろいろアレする。 クライアント(FTPクライアントプログラム)から, コマンド・ポートに接続があってはじめて データ・ポートを準備するんで, 初期処理ではデータ・ポートに関しては何もアレしなくてよい。

でも, あれだな。HTTPの方が転送に面倒さがなくっていいと感じてしまうねっ。 いろんなケースはあるだろうけど。

で, 次が主処理。

/* -- Main loop */
do forever
   r.0 = words(sklist)
   do i = 1 to r.0
      r.i = word(sklist, i)
   end
   call CharOut , '*'
   Rc = SockSelect('r.',,, SelectWait)
   if Rc < 0 then do; say 'Select: err='errno 'h='h_errno; leave; end;
   if Rc = 0 then do; r.0 = 0; end;

   /* -- receive */
   do i = 1 to r.0
      sk = r.i
      if sk == socket then do   /* -- 接続用socketへの到着 */
         sk = SockAccept(socket, 'remaddr.') /* nsk */
         if sk < 0 then do; say 'Accept: err='errno 'h='h_errno; iterate; end;
         say
         say PLog('Connection' sk 'from' remaddr.addr 'at' date() time())
         sklist = sklist sk
         RcvBuff.sk = ''
         Rc = ReplyMesg(sk, 220 OpeningMessage)
         iterate
      end

      Rc = SockRecv(sk, 'txt', MAX_RECVLN)
      if Rc < 0 then say 'Receive: err='errno 'h='h_errno
      else if Rc == 0 then call CutStart   /* 受信: 遮断された */
      else RcvBuff.sk = RcvBuff.sk ||txt   /* 受信: 電文受信 */
   end

   /* -- parse */
   do i = 1 to words(sklist)
      sk = word(sklist, i)
      if symbol('RcvBuff.sk') == 'LIT' then iterate

      if pos(nl, RcvBuff.sk) == 0 then iterate
      parse var RcvBuff.sk txt (nl) RcvBuff.sk
      call Plog sk '"'txt'"'
      call DoExecCmd sk, txt
   end
end

どーっ・・。こーやって Webに出してみると大きくみえる (^^;;
んーと, えっとえっと, おーそー。PLog() っつーのはログを取るだけのものなんで, あんま気にしなくってよい。それから・・えー, そのほかは次回以降に持ち越しってーことで (^^;;

最後の方にあるのは, 改行で区切って文字列を取り出している (って説明必要なかったカナ)。

で, 後処理。

/* -- 後処理 */
ServHalt:
do i = words(sklist) to 1 by -1
   sk = word(sklist, i)
   if SockSoClose(sk) < 0 then say 'Close: err='errno 'h='h_errno
end
exit

なぜ, リストの逆からクローズしているかとゆーと, listenでの接続用ソケットは 先頭にあるため, 年のために, じゃないや 念のためにこーやっている。

ちょっと忘れるところだった。変数の初期化。こりは先頭にでも。

/* FTP */
options 'ETmode'
True = 1; False = 0;
nl = '0d0a'x

if RxFuncQuery('SysLoadFuncs') then do
   call RxFuncAdd 'SysLoadFuncs', 'rexxutil', 'SysLoadFuncs'
   call SysLoadFuncs
end

if RxFuncQuery('SockLoadFuncs') then do
   call RxFuncAdd 'SockLoadFuncs', 'rxsock', 'SockLoadFuncs'
   call SockLoadFuncs
end

NumListen = 10
SelectWait = 120
BindWait = 3*60
MAX_RECVLN = 2048
MAX_SENDLN = 1500
FTPbaseDir = '/Trash'
PLog = 'FTPsvr.log 40960 5'
OpeningMessage = 'Hello, This is WARP Rexx FTP Server'nl,
                 'こんにちは、Rexx FTP serverです'nl,
                 '≪ Version 1.00 ≫'

でも, こんだけやってても, 次回は別なのをアレするかもしんない。てへっ (^^)\(バキッ☆)

≫ 次回へすすむ


Copyright (C) 1998-2004 Rexx使いの織華
email: ori@drive.co.jp