実践的Windows DNA構築法

インターネットを利用したWindows DNAシステムの考案

メールオリエンテッドアプローチの勧め



初音 玲 HATSUNE, Akira



はじめに

 Windows DNAの基本的な考え方は、複数の開発者がそれぞれの得意分野を担当し、それを最適な場所に配置し効率的にシステムを稼働させようというものだ。もちろん、ここで言うところの最適な場所というのは、CPUやハードディスクなどのコンピュータ本体だけを指すのではなく、コンピュータとコンピュータを接続する通信回線などのインフラも含まれる。要するに使えるハードウェアと人材を最大限に生かしてコラボレーションすることで、よりよいものをよりよい形で動かしてゆこうとしている、と言えるだろう。
 そして、そのためのコーディネーター役がCOM(Component Object Model)でありDCOM(分散Component Object Model)である。COMやDCOMを使うことで、さまざまな技術をコンポーネント化し、統一したインターフェイス(手法)で結合できる。そして、その時々のハードウェアの進化に合わせて、コンポーネントを最適な場所に再配置することも可能なのだ(図1)。

図1:Windows DNAのコンセプト
図1

 このようにWindows DNAが紡ぎだす世界は、なかなか素晴らしい世界のように見えるのだが、現実的な問題を考慮すると、どうしてもネックとなりやすい部分が存在する。それは、皮肉にもWindows DNAの要であるDCOMだ。なぜなら、DCOMのセキュリティはWindowsドメインの認証系に頼っているので、分散環境下においてもWindowsドメインの認証系を維持し、さらにインターネット上に公開して利用するなどというのは現実的ではないからだ。

インターネットにはインターネットな技術で

 DCOMのコンセプトを踏襲しつつ、インターネット上で構築しやすい仕組みは、やはりインターネットで培われてきた技術を使うのがよいだろう。今回は、HTTPおよびSMTPとPOP3のプロトコルを使ってみたい(図2)。この3つのプロトコルは、それぞれWebブラウザやメーラーといった、一般的なアプリケーションがインターネットを利用する際に使用している。つまり、インターネットサービスプロバイダと契約していれば(インターネットと繋がっていれば)ほとんど誰でも利用できるプロトコルだ。このようなプロトコルをインターネット標準プロトコルといい、RFC(Request For Comments)できちんと定義されている。

図2:本稿のコンセプト
図2

 RFC (Request For Comments) は、IETF (Internet Engineering Task Force) のWebサイト(http://www.ietf.org/rfc.html)にオリジナルドキュメントがあるので、一度は目を通したほうがいいだろう。

サンプルサービスを設計する

 以上のコンセプトを踏まえ、今回はインターネット標準プロトコルを使ったWindows DNAサンプルとして、
というサービスを構築してみることにする。
 具体的には、図3のような流れに沿ったサービスを提供することになる。

図3:サンプルサービス
図3

サンプルサービスを提供する

 必要なサービスが明確になってきたので、どのようにインターネット上でサービスを提供してゆくかを考えなければならない。今回は構築のしやすさから、利用者からのサービスへの入力はすべてWebページ経由で行なうことにした。そして、2000年9月1日までの限定公開という形ではあるが、知人のWebサイトに間借りする形で、サンプルWebページを設置させてもらった(http://www.ask.ne.jp/~pratze/vbm0009/)。
 また、実際にサービスを提供するクライアントは本誌編集部に設置して、メールをやりとりするためのメールサーバーは翔泳社のメールサーバーを利用することになった。

サンプルサービスを作成(登録サービス)

 登録サービスは、図4のようなWebページを用意して情報の登録を行ない、それに対して確認メールを送信するサービスだ。処理の流れは、図5のようになる。

図4:登録ページ
図4

図5:登録の流れ
図5

Webページの作成

 ホームページの作成が行なえるインターネットサービスプロバイダが利用しているWebサーバーがIIS(Internet Information Services)ならば、ASP(Active Server Pages)の機能を利用し、VBScriptなどでサーバーサイドスクリプトを記述してWebページを作成できる。しかし、なかなかそのようなインターネットサービスプロバイダはないようだ。今回利用するところもWebサーバーにはUNIXを使用しているので、一般的なHTMLでWebページを、またメール送信部分はPerlなどを利用してCGIプログラムを作成するアプローチとなる(リスト1)。

リスト1:登録ページ(formmail.html) ※実行できません

 このformmail.htmlでは、<form>タグで囲まれた入力情報を[送信]ボタンの左クリックにより、formmail.cgiへPOSTしている。formmail.cgiの働きは、送信内容の確認画面を表示後、リスト2のような内容のメールを登録画面に入力したメールアドレスと、あらかじめformmail.cgiに登録したメールアドレスにメール送信する。
 このformmail.cgiは、インターネット上に公開されているフリーソフトを作者のTerraさんに今回のサンプルとして利用することを快諾していただき、利用しているので、ソースコードは作者のWebサイト(http://www2.inforyoma.or.jp/~terra/)から入手していただきたい。インターネット標準プロトコルを使っているからこそ、このように一般公開されているものの恩恵を受けられる。まさにWindows DNAが提唱していることの一部が現実として存在しているということだ。

リスト2:送信されるメール
DATE = 100/7/3 17:24:19
AGENT = Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt)
----------------------------------------------------
txtName = しんじ
optSex = 1
cboArea = 関東
cboAge1 = 1
cboAge2 = 4
txtEmail = shinji@nerv.go.jp
chkArea01 = 北海道
chkArea02 = 東北
chkArea03 = 関東
chkArea04 = 中部
chkArea05 = 近畿
chkArea06 = 中国
chkArea07 = 四国
chkArea08 = 九州
cboAgeL1 = 1
cboAgeL2 = 0
cboAgeH1 = 9
cboAgeH2 = 9
----------------------------------------------------

POP3クライアントの作成

 登録内容のメール送信まで辿りつけば、次は登録サービスの核となるPOP3クライアントの作成だ。ここで、やっとVisual Basicの出番となる。
 Visual BasicでPOP3クライアントを作成するには、
  1. Windows APIを使う
  2. 添付のTCP/IPコントロールを使う
  3. 市販のPOP3コントロールを使う
のいずれかのアプローチをとることになる。
 〈2〉の添付コントロールは、いわゆるおまけコントロールであり、あまり品質のよいものではないので、現実的には〈1〉か〈3〉のアプローチを取るのが得策だろう。今回は、プロトコルの実装がメインではなく、インターネット標準プロトコルを使って、どうやってサービスを実装するかをメインとしたいので、〈3〉のアプローチを採用する。なお、〈1〉については、本誌1998年2月号で福岡寿和氏が詳細を説明されているので、バックナンバーかPC Developers NetworkのWebサイト(http://pcdn.int21.co.jp/pcdn/)のVisual Basic Magazineライブラリを参照して欲しい。
 〈3〉のアプローチを採用する場合、これまで、日本語対応のPOP3クライアントコントロールは、ほとんどなかったが、最近は各社から発売されているので、どの市販コントロールを使うか比較検討が必要だろう。この特集記事を執筆するにあたって、数社にコントロールの使用を打診した結果、文化オリエント株式会社の「iNetMail 1.0J」(以下iNetMail)という新製品を使用できることになった(図6)。

図6:市販コントロールを利用する
図6

 もちろん、新製品なので、今まで業務開発に使ってきた市販POP3クライアントコントロールとは異なるものだったので、限られた時間の中で使いこなせるようになるか心配だった。しかしならが、市販コントロールの老舗である文化オリエント株式会社の製品であり、インターネット標準プロトコルをカプセル化したコントロールであることも幸いしたのか、心配はまったくの杞憂であった。
 iNetMailを使ってメールを受信するためには、リスト3のようにプログラミングする。

リスト3:登録サービス(frmMailGW/tmrRecv_Timerからの抜粋)
' POPサーバーにログイン
popMailGW.Login pstrGetIni(App.EXEName, "POP3", mstrIniFile), _ ---+
                pstrGetIni(App.EXEName, "USER", mstrIniFile), _    +-《1》
                pstrGetIni(App.EXEName, "PASS", mstrIniFile)    ---+
lngCnt = popMailGW.Count   ------------------------------------------《2》
' メール処理
    If lngCnt > 0 Then
      Call subLogSet(lngCnt & "通のメールがあります。")
      For ilngLoop = 1 To lngCnt
      ' ヘッダ部分を受信して、送信元を判定
        popMailGW.Get msgPreview, CStr(ilngLoop)  -------------------《3》
        strSender = _
         StrConv(popMailGW.Messages(ilngLoop).From, vbLowerCase) ----《4》
        If strFrom = strSender Then
        ' 規定の送信元からなのでメール本文を処理
          popMailGW.Get msgContent, CStr(ilngLoop) ------------------《5》
          Set msgMail = popMailGW.Messages(ilngLoop) ----------------《6》
                             :
                           (中略)
                             :
                        popMailGW.Delete CStr(ilngLoop) -------------《7》
                             :
                           (中略)
                             :
    End If
    popMailGW.Logout ------------------------------------------------《8》
  1. LoginメソッドでPOP3サーバーに接続する
  2. 処理件数を得るためにメール総数取得。iNetMailではメールを1メソッドですべて取得することができるが、これは取得時間が未受信メール件数に左右されないように、1通ずつ処理する方式を採用しているためだ
  3. GetメソッドのmsgPreviewパラメータを指定して実行して、1通のヘッダ部分のみを取得する。これも規定外のメールアドレスから巨大なメールを送信されたとしても、短時間で破棄するための工夫だ
  4. Getメソッドの実施により、Messagesコレクションに順次メールが格納されていくので、MessagesコレクションのFromプロパティを参照して、送信元を確認する
  5. 規定の送信元からのメールだったら、GetメソッドのmsgContentパラメータを指定して実行して、1通のメール全体を取得する。今回のサービスでは添付ファイルは使わないので、メリッサなどのメール添付型ウィルスが混在していたとしても、ここで破棄することになる
  6. 1通分の処理がしやすいように、Messagesコレクションの1要素とMessageオブジェクトを関連づける
  7. 処理が終わったメールは、Deleteメソッドによりメールサーバー上から削除する
  8. 一連の処理が完了したら、LogoutメソッドでPOP3サーバーと切断する
 iNetMail以外のPOP3クライアントコントロールでもほぼ同じようにプログラミングできるはずだ。
 登録ページからのメールを受信するときに注意する点があるとすれば、登録ページから送られてくるデータは、URLエンコードされて送信されてくるので、URLデコードをしなければならない点である。URLエンコードは比較的単純なロジックなので(図7)、リスト4のようなURLデコード関数を用意すれば簡単に変換できる。しかし、iNetMailには、JCODEオブジェクトが添付されているのでそのオブジェクトのURLDecodeメソッドを使って、コード変換してしまうのが簡単だ。

図7:URLエンコードのアルゴリズム
図7

リスト4:URLデコード関数
Private Function strURLDecode( _
 ByVal rstrPost As String) As String
  Dim intindex  As Integer
  Dim intPos    As Integer
  Dim strAsc    As String
    
' 8bit Decode
  intindex = 1
  strURLDecode = ""
  intPos = InStr(rstrPost, "%")
  Do While intPos > 0
    If intPos > 1 Then
      strURLDecode = strURLDecode & _
       Mid$(rstrPost, intindex, intPos - 1)
      intindex = intindex + intPos
    Else
      intindex = intindex + 1
    End If
    strAsc = StrConv( _
      Mid$(rstrPost, intindex, 2), vbUpperCase)
    intindex = intindex + 2
    If strAsc > "7F" Then
    ' 漢字項目なので
      If Mid$(rstrPost, intindex, 1) = "%" Then
        strAsc = Chr(CInt("&H" & strAsc & _
                  Mid$(rstrPost, intindex + 1, 2)))
        intindex = intindex + 3
      Else
        strAsc = Chr$(CInt("&H" & strAsc & _
                  Hex(Asc(Mid$(rstrPost, intindex, 1)))))
        intindex = intindex + 1
      End If
    Else
      strAsc = Chr$(CInt("&H" & strAsc))
    End If
      strURLDecode = strURLDecode & strAsc
      intPos = InStr( _
       Mid$(rstrPost, intindex), "%")
  Loop
  strURLDecode = strURLDecode & _
                 Mid$(rstrPost, intindex)
' + Decode
  strAsc = strURLDecode
#If VB6 Then
  strURLDecode = _
   Replace(strAsc, "+", " ", 1, -1, vbTextCompare)
#Else
  intPos = InStr(strAsc, "+")
  Do While intPos > 0
    Mid$(strAsc, intPos, 1) = " "
    intPos = InStr(strAsc, "+")
  Loop
  strURLDecode = strAsc
#End If
End Function

登録データの保存

 登録メールを受信したら、その内容を分析して、登録ページで入力された値をデータベースに格納する(リスト5)。これは「登録データ」テーブルに対して、メールアドレスをキーに検索し、新規データであれば登録情報を格納することで実現する。このとき、登録済フラグをFalseにしておくことで、登録確認をもつことになる。
 データベースは、同一マシンでもかまわないし、別に用意したデータベースサーバーを利用してもよい。データベース製品も任意の製品でかまわない。サンプルプログラムでは、mdbファイルに対してミドルウェアにDAO/Jet使ってプログラミングしているが、利用者にとってもWebページ作成者にとっても、どのデータベース製品を使っているかは、まったく無関係となる。

リスト5:メールの格納(frmMailGWより抜粋)
Private Function blnMailStore( _
 ByRef rastrItem() As String) As Boolean
' メールをDBに格納する
  Dim blnRet    As Boolean   ' 処理結果
  Dim rdynLocal As Recordset ' レコード
  Dim strSQL    As String    ' SQL文
  Dim strItem   As String    ' 項目値
  Dim lngCID    As Long      ' 処理対象ID
  On Error GoTo errMailStore:
        
  blnRet = False
    
  strItem = StrConv(rastrItem(6), _
   vbLowerCase + vbNarrow)
  strSQL = "SELECT * FROM 登録データ " & _
               "WHERE email='" & strItem & "'"
  Set rdynLocal = _
   pdbsLocal.OpenRecordset(strSQL, dbOpenDynaset)
  If rdynLocal.EOF Then
  ' 新規登録
    rdynLocal.AddNew
    rdynLocal("姓名").Value = Trim$(rastrItem(1))
    rdynLocal("性別").Value = rastrItem(2)
    rdynLocal("居住地域").Value = rastrItem(3)
    rdynLocal("年齢").Value = _
             CInt(rastrItem(4) & rastrItem(5))
    rdynLocal("email").Value = strItem
    rdynLocal("希望地域1").Value = rastrItem(8)
    rdynLocal("希望地域2").Value = rastrItem(9)
    rdynLocal("希望地域3").Value = rastrItem(10)
    rdynLocal("希望地域4").Value = rastrItem(11)
    rdynLocal("希望地域5").Value = rastrItem(12)
    rdynLocal("希望地域6").Value = rastrItem(13)
    rdynLocal("希望地域7").Value = rastrItem(14)
    rdynLocal("希望地域8").Value = rastrItem(15)
    rdynLocal("希望年齢下限").Value = _
             CInt(rastrItem(16) & rastrItem(17))
    rdynLocal("希望年齢上限").Value = _
             CInt(rastrItem(18) & rastrItem(19))
    rdynLocal("登録済フラグ").Value = False
    rdynLocal.Update
    rdynLocal.Close
    strSQL = "SELECT * FROM 登録データ " & _
             "WHERE email='" & strItem & "'"
    Set rdynLocal = _
     pdbsLocal.OpenRecordset(strSQL, dbOpenDynaset)
    If Not rdynLocal.EOF Then
      lngCID = rdynLocal("ID").Value
      blnRet = blnMailSend(mcintEntry, strItem, lngCID)
    End If
      rdynLocal.Close
  Else
  ' 個人情報の更新(今回は未サポート)
    blnRet = True
  End If
exitMailStore:
  On Error Resume Next
  rdynLocal.Close
  blnMailStore = blnRet
  Exit Function
    
errMailStore:
  Call subLogSet("frmMailGW/blnMailStore:" & Error$)
  blnRet = False
  Resume exitMailStore:
  Resume Next
End Function

SMTPクライアントの作成

 登録サービスでは登録メールの処理が完了すると、登録確認メールを送信する。この登録確認メールにより、他人の名前とメールアドレスを使ったイタズラを発見・除去できることを期待している。
 iNetMailを使ってメールを送信するためには、リスト6のようにプログラミングする。

リスト6:SMTPクライアント(frmMailGW/blnMailSendより抜粋)
Private Function blnMailSend( _
 ByVal vintType As Integer, _
 ByVal vstrTo As String, _
 ByVal vlngCID As Long, _
 Optional ByVal vstrMsg As String) As Boolean
' リプライメールを返信する
  Dim blnRet     As Boolean  ' 処理結果
  Dim strSMTP    As String   ' SMTPサーバー
  Dim strBody    As String   ' メール本文
  Dim intFno     As String   ' メッセージ
  Dim strBuf     As String   ' 読み込みバッファ
  Dim strFotter  As String   ' メールフッター
  On Error GoTo errMailSend:
    
  blnRet = False
  smtpMailGW.Login pstrGetIni( _
    App.EXEName, "SMTP", mstrIniFile) -------------------《1》
' メッセージの取得
  strBody = ""
  intFno = FreeFile
  Select Case vintType
  Case mcintEntry
    Open CurDir$ & _ -----------------------------------------+
     "\entry.txt" For Input Access Read Lock Write As #intFno +-《2》
    smtpMailGW.Message.Subject = "VBM0009 - 登録確認" --------+
                              :
                            (中略)
                              :
' メッセージの送信
  smtpMailGW.To.Clear  -----------------------------------《3》
  smtpMailGW.To.Add vstrTo -------------------------------《4》
  If pstrGetIni(App.EXEName, "SENDER", mstrIniFile) <> "" Then
    smtpMailGW.From = _
     pstrGetIni(App.EXEName, "SENDER", mstrIniFile)
  Else
    smtpMailGW.From = _
     pstrGetIni(App.EXEName, "FROM", mstrIniFile) --------《5》
  End If
  smtpMailGW.Message.Text = strBody  ---------------------《6》
  smtpMailGW.Send  ---------------------------------------《7》
  Call subLogSet(vbTab & "-->" & vstrTo)
  blnRet = True
exitMailSend:
  Close #intFno
  smtpMailGW.Logout  -------------------------------------《8》
  blnMailSend = blnRet
  Exit Function
    
errMailSend:
  Call subLogSet("frmMailGW/blnMailSend:" & Error$)
  Resume exitMailSend:
  Resume Next
End Function
  1. LoginメソッドでSMTPサーバーに接続する。将来的にSMTPサーバー認証がサポートされたときは、ユーザーIDとパスワードをパラメータに指定すればよい
  2. MessageオブジェクトのSubjectプロパティに件名を設定する
  3. Toプロパティをクリアして送信先をクリアする
  4. Toプロパティに改めて送信先を設定する
  5. Fromプロパティに送信元を設定する
  6. MessageオブジェクトのTextプロパティにメールの本文を設定する。このメール本文に、登録確認ページのURLとユーザーIDを記載している(リスト7)
  7. Sendメソッドを使って設定内容を送信する
  8. 一連の処理が完了したら、LogoutメソッドでSMTPサーバーと切断する
リスト7:メール本文
入会申込を受領しました。
いたずら防止のために、下記のurl にアクセスして、登録
完了を行なってください。
もし、本メールが身に覚えのないものでしたら、破棄して
頂ければ、入会した事にはなりませんので、ご安心くださ
い。
http://www.ask.ne.jp/~pratze/vbm0009/reg.cgi?ID=1111

サンプルサービスを作成(登録確認サービス)

 登録確認サービスは、図8のようなWebページを用意して登録の確認を行なう。処理の流れは、図9のようになる。登録の確認ができたら、友達を紹介することになる。

図8:登録確認ページ
図8

図9:登録の流れ
図9

 登録確認ページは、HTMLlによる静的なページではない。記事末リスト8のようなCGIプログラムを用意し、登録確認メールに記載されたURLをダブルクリックするなどして、Webページを表示したときにIDが自動的に転記されるようにしている。今回はPerlでCGIプログラムを記述しているが、IISをWebサーバーにしているのならば、ASPで記述することもできるし、WindowsプラットフォームでWebサーバーを稼働しているのならば、Visual BasicでCGIプログラムを作成してもよい。

リスト8:確認ページを生成する
#!/usr/local/bin/perl
require './jcode.pl';
@pairs = split(/&/,$ENV{'QUERY_STRING'});
foreach $pair (@pairs) {
	($name, $value) = split(/=/, $pair);
	$value =~ tr/+/ /;
	$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
	$value =~ s/\n//g;
	$value =~ s/\,//g;
	&jcode'convert(*value,'sjis');
	$FORM{$name} = $value;
}
#確認ページを生成
print "Content-type: text/html\n\n";
print "<html><head><title>Visual Basic Magazine 2000/09 Sample Web Page</title></head>\n";
print "<body>\n";
print "<img src=\"./vbm_logo.gif\">\n";
print "<center><h1>Visual Basic Magazine 2000年9月号特集のサンプル</h1></center>\n";
print "<center><h3>登録確認</h3></center>\n";
print "<!--フォームの送信ボタンが押されるとformmail.cgiを実行-->\n";
print "<form action=\"./formmail.cgi\" method=\"POST\">\n";
print "<input type=hidden name=subject VALUE=\"VBM0009 - REGIST\">\n";
print "<table border=0>\n";
print "<tr>\n";
print "<td align=right>ID</td>\n";
print "<td><input type=text size=31 name=CID value=\"$FORM{'ID'}\"></td>\n";
print "</tr>\n";
print "<tr>\n";
print "<td align=right>メールアドレス</td>\n";
print "<td><input type=text size=31 name=txtEmail></td>\n";
print "</tr>\n";
print "<tr>\n";
print "<td align=right></td>\n";
print "<td>\n";
print "<SELECT id=cboYearLow1 name=cboReg>\n";
print "	<OPTION selected value=\"登録\">登録</OPTION> <OPTIONSELECTED>\n";
print "	<OPTION value=\"退会\">退会</OPTION> <OPTIONSELECTED>\n";
print "</SELECT>\n";
print "IDとメールアドレスで本人かどうかを認証しています。</td>\n";
print "</td>\n";
print "</tr>\n";
print "</table>\n";
print "<!--送信ボタンのnameプロパティーは設定しない-->\n";
print "<p><input type=submit value='送信'>\n";
print "<input type=reset value='リセット'></p>\n";
print "</form>\n";
print "<hr width=80%>\n";
print "本サンプルは、2000年9月1日までの限定公開サンプルです。<br>\n";
print "</body>\n";
print "</html>\n";
exit;

 Visual BasicでCGIプログラムを作成するときの注意点としては、Visual Basicでは標準入出力をサポートしていないために、WebサーバーからCGIプログラムとして呼び出されても、WebブラウザにHTMLを返信できないという点だ。そのため、リスト9にあるようにAPIを使い、標準出力に対して文字列を出力する必要がある。また、今回のようにURLにパラメータを指定したようなときは、環境変数[QUERY_STRING]にパラメータが格納されてくるので、それを分解してパラメータ値を取得しなければならない。

リスト9:Visual Basic版 reg.cgi(RegCGI.bas)
Option Explicit
Declare Function GetStdHandle Lib "kernel32" _
                (ByVal nStdHandle As Long) As Long
Declare Function WriteFile Lib "kernel32" _
                (ByVal hFile As Long, _
                 lpBuffer As Any, _
                 ByVal nNumberOfBytesToWrite As Long, _
                 lpNumberOfBytesWritten As Long, _
                 ByVal lpOverlapped As Long) As Long
Declare Function ReadFile Lib "kernel32" _
                (ByVal hFile As Long, _
                 lpBuffer As Any, _
                 ByVal nNumberOfBytesToRead As Long, _
                 lpNumberOfBytesRead As Long, _
                 lpOverlapped As Any) As Long
Public Const STD_INPUT_HANDLE As Long = -10&
Public Const STD_OUTPUT_HANDLE As Long = -11&
Public Const STD_ERROR_HANDLE As Long = -12&
Private Sub Main()
  On Error GoTo errMain:
  ' 応答コード部
  printf "HTTP/1.0 200 OK" & vbCrLf
  ' ヘッダ部
  printf "Content-type: text/html" & vbCrLf & vbCrLf
  printf "<html><head><title>Visual Basic Magazine " & _
         "2000/09 Sample Web Page</title></head>" & vbCrLf
  printf "<body>" & vbCrLf
  printf "<img src=""./vbm_logo.gif"">" & vbCrLf
  printf "<center><h1>Visual Basic Magazine" & _
         "2000年9月号特集のサンプル</h1></center>" & vbCrLf
  printf "<center><h3>登録確認</h3></center>" & vbCrLf
  printf "<!--フォームの送信ボタンが押されるとformmail.cgiを実行-->" & vbCrLf
  printf "<form action=""./formmail.cgi"" method=""POST"">" & vbCrLf
  printf "<input type=hidden name=subject VALUE=""VBM0009 - REGIST""> " & vbCrLf
  printf "<table border=0>" & vbCrLf
  printf "<tr>" & vbCrLf
  printf "<td align=right>ID</td>" & vbCrLf
  printf "<td><input type=text size=31 name=CID " & _
         "value=""" & _
         strFindKeyValue(Environ("QUERY_STRING"), _
         "ID") & """>" & _
         "</td>" & vbCrLf
  printf "</tr>" & vbCrLf
  printf "<tr>" & vbCrLf
  printf "<td align=right>メールアドレス</td>" & vbCrLf
  printf "<td><input type=text size=31 name=txtEmail></td>" & vbCrLf
  printf "</tr>" & vbCrLf
  printf "<tr>" & vbCrLf
  printf "<td align=right></td>" & vbCrLf
  printf "<td>" & vbCrLf
  printf "<SELECT id=cboYearLow1 name=cboReg>" & vbCrLf
  printf " <OPTION selected value=""登録>登録</OPTION> <OPTIONSELECTED>" & vbCrLf
  printf " <OPTION value=""退会>退会</OPTION> <OPTIONSELECTED>" & vbCrLf
  printf "</SELECT>" & vbCrLf
  printf "IDとメールアドレスで本人かどうかを認証しています。</td>" & vbCrLf
  printf "</td>" & vbCrLf
  printf "</tr>" & vbCrLf
  printf "</table>" & vbCrLf
  printf "<!--送信ボタンのnameプロパティーは設定しない-->" & vbCrLf
  printf "<p><input type=submit value='送信'>" & vbCrLf
  printf "<input type=reset value='リセット'></p>" & vbCrLf
  printf "</form>" & vbCrLf
  printf "<hr width=80%>" & vbCrLf
  printf "本サンプルは、2000年9月1日までの限定公開サンプルです。<br>" & vbCrLf
  printf "</body>" & vbCrLf
  printf "</html>" & vbCrLf
exitMain:
  On Error Resume Next
  Exit Sub
errMain:
  printf Error$
  Resume exitMain:
End Sub
Private Sub printf(ByVal strBuffer As String)
  Dim abytBuffer()  As Byte
  Dim lngOutLen     As Long
  Dim lngBytes      As Long
  Dim lngRet        As Long
  Dim hStdOut       As Long
  
  Debug.Print strBuffer;
' VBの内部コードであるUnicodeから
' SJISに変換してそれをバイナリとして扱う。
  abytBuffer = StrConv(strBuffer, vbFromUnicode)
' バイト配列の大きさ(いわゆる文字列長)を求める
  lngOutLen = UBound(abytBuffer) - LBound(abytBuffer) + 1
'標準出力ハンドルを取得する
  hStdOut = GetStdHandle(STD_OUTPUT_HANDLE)
'標準出力先へSJISコードのまま出力する。
  lngRet = WriteFile(hStdOut, _
                     abytBuffer(0), _
                     lngOutLen, _
                     lngBytes, _
                     0&)
End Sub
Private Function strFindKeyValue( _
 rstrQuery As String, rstrKey As String) As String
  Dim astrTmp()   As String
  Dim iintLoop    As Integer
  Dim intPos      As Integer
  strFindKeyValue = ""
  If rstrQuery <> "" Then
   #If VB6 Then
     astrTmp() = Split(rstrQuery, "&", -1, vbTextCompare)
     For iintLoop = 0 To UBound(astrTmp)
       intPos = InStr(astrTmp(iintLoop), rstrKey & "=")
       If intPos > 0 Then
         strFindKeyValue = Mid$(astrTmp(iintLoop), _
                           intPos + Len(rstrKey & "="))
       End If
     Next
   #Else
     If InStr(astrTmp(iintLoop), rstrKey & "=") > 1 Then
       intPos = InStr(astrTmp(iintLoop), "&" & rstrKey & "=")
       strFindKeyValue = Mid$(rstrQuery, _
                         intPos + Len("&" & rstrKey & "="))
     Else
       strFindKeyValue = Left$(rstrQuery, Len(rstrKey & "="))
     End If
   #End If
  End If
End Function

 なお、友達の紹介は、「紹介」テーブルに記録され、多重紹介をしないように工夫すると同時にこの紹介IDをメッセージ転送時のキーにすることで、紹介していない人にメッセージを送信できないようにしている(リスト10)。

リスト10:IDも送付される
お友達をご紹介します。
--------------------
関東にお住まいのはつね様(女性)
--------------------
メッセージを送るときには
    http://www.ask.ne.jp/~pratze/vbm0009/msg.cgi?ID=132
をクリックしてメッセージを送信してください。

サンプルサービスを作成(メッセージ転送サービス)

 紹介した友達とのメール交換もサービスとして実装している。図10のWebページを用意して、友達紹介時のIDと本人のメールアドレスをキーにして本人認証して、メッセージを転送する(図11)。

図10:メッセージ送信ページ
図10

図11:メッセージ送信の流れ
図11

 メッセージ送信ページも登録確認ページと同様にCGIプログラムで生成している(Visual Basic用:LIST01.TXT、Perl用:LIST02.TXTとして、それぞれ付録CD-ROMに収録)。
 メッセージ転送サービスでは、メッセージ送信ページで入力されたIDと本人メールアドレスで本人と認証できたときは、入力されたメッセージを相手に転送する。

セキュリティの向上案

 サンプルサービスでも、メッセージの交換は、すべてVisual Basicで作成したアプリケーションを経由して行なっているために、添付ファイルの形で増殖を試みるメール媒介型ウィルスの感染を防止し、また、メールヘッダから送信元のアクセスポイントや使っているメーラーなどの情報を取得することは不可能な形になっている。
 しかし、セキュリティをさらに向上させる方法もある(図12)。たとえば、WebブラウザとWebサーバー間にHTTPSを使って暗号化通信を行なうことで、Webページ上に入力する情報を隠蔽できる。このHTTPSもWebショッピングなどのWebサイトでおなじみのプロトコルで、VeriSign社なので認証機関から承認を受ければ誰でも構築可能だ。インターネットサービスプロバイダの中には(もちろん別途費用がかかるが)認証機関への申請などの作業を代行してくれるところもある。

図12:セキュリティを確保した登録の流れ
図12

 また、メール自体にもPGP[MEMO]などの暗号化プログラムを使ってメール本文を暗号化する方法もある。メールの暗号化はWebページの暗号化ほど一般的ではないが、今回のサンプルのように会員登録制ということであれば、一種のエクストラネットのようなもので利用者が限定できるので、質問受付窓口を用意して、PGPのインストールを参加条件に加えることも可能だろう。

Memo 「PGPとは」
 PGPは、公開鍵+秘密鍵方式の暗号化方式だ。たとえば、Aさんに暗号化したメールを送りたいときは、
  1. Aさんに秘密鍵と公開鍵をPGPにより作成してもらい、公開鍵のみをメール(もちろんこのときは暗号化前の平文メールだ)してもらう
  2. メール送信元では、PGPにAさんの公開鍵を使ってメール本文を暗号化してからメール送信する。この暗号化メールは、Aさんの秘密鍵がないと復号化して平文にすることはできない
  3. Aさんはメールを受信したら、PGPと秘密鍵を使って復号化してメールを読む
 PGP自体は、RFC2440で標準化されており、フリーソフト版もインターネット上に公開されているので、アメリカ合衆国以外の国のWebサイトからダウンロードして使用することが可能だ。なぜ、アメリカ合衆国のWebサイトからダウンロードしてはいけないかと言えば、他の国のWebサイトに掲載されているものよりも暗号の強度(解けにくさ)が高いからだ。一見、そのほうがよいのだから、アメリカ合衆国内のWebサイトから入手したくなるが、対外輸出が禁じられているので、法律違反になってしまう。注意して欲しい。

最後に

 今回の特集の発端はCOMやDCOMの理想に近いことをインターネット技術で実現できるかを探ってみようというものだった。なぜなら、手軽に体験できるような環境を作り上げるのが難しいDCOMではなく、用意しやすいインターネット技術を土台として、同じような分散環境を実現してゆこうと考えたからだ。DCOMの認証系がWindowsドメインの認証系に依存している現状では、たとえ期間限定でも一般公開できる環境を構築することは難しい。もっとも、ADOでは、異機種上のRDBMSとの接続も可能になっているので、Windowsプラットフォーム以外でも標準的に搭載され、オプションパラメータとしてユーザーIDやパスワードを指定できるようになれば、DCOMによる異機種間接続の現実性も高くなるだろう。DCOMのコンセプト自体は悪くはないし、分散環境下で同期処理やトランザクション処理を実現するのにDCOMは便利なので、さらなる進歩を期待したい。


VB Magazine ライブラリ| Visual Basic WorkGroup
int21 ホームページ| PCDN ホームページ

PCDN
Copyright (c) 1998 int21 CorporationAll Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp