--- つづき --- ≪ 前回へもどる
さて, 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^)/~
--- つづき --- ≪ 前回へもどる
今回コマンドの部分。これすんごく長い。あんまし長くって, 一部ブラウザで枠が出ない (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
(↑)ここは, getと putの所だねっ。(←えらく簡単な説明だな)
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 サーバーも いよいよ大詰め。「キミはこの話についてこれるか」 (←何をゆっとんじゃ)
ログ取りホイホイ。
えとー, 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もあるんで・・。
◇
ところで関係ない話だけれど, こーゆーのを公開する時, 色をどうしたもんか・・
うーむっ, 今回 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 ≫'
でも, こんだけやってても, 次回は別なのをアレするかもしんない。てへっ (^^)\(バキッ☆)