#!/usr/local/bin/ruby
#
# Uogashi Planing System
# (C)2000-2005 by HIROSE Yuuji [yuuji@gentei.org]
# $Id: uogashi.rb,v 1.14 2005/05/09 02:25:22 yuuji Exp $
# Last modified Mon May  9 11:24:04 2005 on firestorm
# See http://www.gentei.org/~yuuji/software/uogashi/
# このスクリプトはEUCで保存してください。

require 'cgi'
require 'kconv'
me = File.expand_path($0)
$mydir, $myname = File.dirname(me), File.basename(me)
$mybase = $myname.sub(/\.\w+$/, '')

=begin
【なんだこれ】
スクリプトと同じディレクトリの uogashi.conf というファイルに
表となる項目の一覧を書いておく。
例:
	[uogashi.conf]
	name
	7/20		option(○,×,△)
	7/21		option(○,×,△)
	7/27		option(○,×,△)
	ひとこと	text(10)

すると、このページに何人かが書き込んだあとはこんな感じの表になる。

	+----------+------+------+------+----------+
	|   name   | 7/20 | 7/21 | 7/27 | ひとこと |
	+----------+------+------+------+----------+
	|yuuji     |  ○  |  ×  |  ○  | がおー   |
	+----------+------+------+------+----------+
	|腹        |  ○  |  ○  |  ○  | ぐおー   |
	+----------+------+------+------+----------+
	|ま        |  ○  |  ○  |  ○  | ぎょー   |
	+----------+------+------+------+----------+
	[登録or修正]

uogashi.conf は1行1エントリで、TAB区切りで記述。
最初の項目は表の項目名。TABで区切った第二項目はそのカラムの値となる
ものの属性を書く。属性は、

	option		選択肢から選ばせる
	text		任意の文字列

のどれか。optionは後続する括弧内に選択肢をカンマで区切って列挙する。
textは後続する括弧内に最大バイト数を書く。また、実際には uogashi.conf に
ページの管理者とか、ちょっとした体裁を決める値の定義を書く必要がある。た
とえば、こんなかんじ:

	maintainer=yuuji@gentei.org
	title=定例OB会
	heading=定例OB会
	leader=登録画面に進み,行けそうな日に○をつけてください(10日までに!)
	bgcolor=yellow
	# #で始まる行はコメント
	name
	7/20		option(○,×,△):stat
	7/21		option(○,×,△):stat
	7/27		option(○,×,△):stat
	ひとこと	text(10)

ここで初登場した option に対する :stat スイッチは、各選択肢が何人によっ
て選ばれたかの表を末尾にまとめて表示することを指示する。これで幹事さんも
楽チン。

	-	-	-	-	-	-	-	-
【インストール】

まずこのスクリプトを動かすディレクトリを決める。たとえば ~/public_html/uo/
そこでCGIが動くように .htaccess を確認。

	[~/public_html/uo/.htaccess]
	AddHandler cgi-script .cgi
	DirectoryIndex uogashi.cgi

そして、uogashi.rb をコピー。で、実際に集計を取りたい事項の名前を決める。
ここでは、 "OB-kai2000" としよう。これがURLの一部になるのでたっぷり悩み
たまへ。では、集計ディレクトリの作成。

	% cp uogashi.rb ~/public_html/uo
	% cd  ~/public_html/uo
	% ./uogashi.rb install OB-kai2000

すると必要な項目を聞いて来るので適当に答えると
~/public_html/uo/OB-kai2000/
に必要なものが揃うのでそのURLを知合いに教えよう。そしてサクっと一番いい
日程を決めて飲み会に行こう。

ただしこのスクリプト、集計という幹事の手間を、出席者自身にやらせようとす
る性質上、悪意のある人がアクセスしたら一瞬で集計ディレクトリを壊されてし
まう。その辺は覚悟して、あくまでも良く知った仲間うちで利用し、部外者はア
クセスできないようにページ全体にパスワードをかける、使い終わったらすぐに
消去する、などの予防策を怠らないこと。

ちなみに、インストーラを使わずに手でインストールするには、
* uogashi.cgi を置くディレクトリに uogashi.conf をおく
  httpdは読めればOK
* uogashi.cgi を置くディレクトリに uogashi.log, uopass.db をおく
  httpdは書き込み許可が必要
* uogashi.cgi を置くディレクトリに repository/ ディレクトリを掘る
  httpdは書き込み許可が必要
  chmod 1777 repository などが適当

という条件を満たすようにファイル配置する。

さらにちなみに、Webインタフェースのインストーラもあるらしいぞ。
まず一個uogashi.cgiが動くページを作り、管理人のパスワードを設定した後で、
uogashi.cgi?webinstall
をアクセスすればいいらしい。パスワードが分かると誰でも作れちゃうので
取扱注意だ。よって、手取り足取り教えないのでがんばりたまへ。動きさえ
すればとても簡単に作れてしまう…。

【謝辞】

2003/6/3
YAMAGUCHI Takuya(takuya@yamaguch.sytes.net)さんにはBIND9付属のhostコマンドの
挙動について助言と対処法をいただきました。

感謝致します。
=end

$repository	= "./repository"
$passwd		= "./uopass"

class TableMaker
  def initialize
    @conf = $mybase + ".conf"
    @ctitle = Array.new
    @ctype = Array.new
    @align = Array.new
    @counter = Hash.new
    @attr = defaultAttr
    @cgi = CGI.new("html4Tr")
    loadconf(@conf)
  end
  def defaultAttr
    {
      "title"		=> "Uogashi",
      "bgcolor"		=> "white",
      "heading"		=> "Table",
      "leader"		=> "Uogashi Planning Table",
      "inputwarning"	=> "全ての項目を埋めて「GO」",
      "tablewidth"	=> "700",
      "numbering"	=> "true",
      "mail"		=> "/usr/bin/mail",
      "sendmail"	=> "/usr/sbin/sendmail",
      "hostcmd"		=> "/usr/bin/host",
      "nslookup"	=> "/usr/sbin/nslookup",
      "passwdlength"	=> 6,
      "maintainer"	=> "",
      "forgotten"	=> "wasureta",
      "logfile"		=> "./#{$mybase}.log"
    }
  end
  def loadconf(file)
    i = 0
    return unless test(?s, file)
    IO.foreach(file){|line|
      line.chop!
      line.sub!(/^\s*#.*/, '')
      next if /^$/ =~ line
      case line
      # title, type = line.split(/\t+/)
      when /^([a-z]+)=(.*)/oi
	key, value = $1, $2
	case value
	when /^(no|none|null|nil)$/io
	  @attr[key] = nil
	else
	  @attr[key] = value
	end
	print "#{$1} set to #{$2}\n" if $DEBUG
      else
	@ctitle[i], @ctype[i], @align[i] = line.split(/\t+/)
	@counter[@ctitle[i]] = Hash.new(0)
	
	print @ctitle[i], "\n" if $DEBUG
	i+=1
      end
    }
  end
  def cgi() @cgi end
  def outputTable()
    if test(?d, $repository) then
      begin
        cwd = Dir.pwd
      rescue
      	cwd = `pwd`
      end
      i = -1
      trs = @cgi.tr {
	if /true/ =~ @attr["numbering"] then
	  @cgi.th{"番号"}
	else
	  ""
	end + \
	@ctitle.collect{|t|
	  next if @align[i+=1] == 'ignore'
	  @cgi.th{t}
	}.join('')
      }
      Dir.open($repository){|dp|
	Dir.chdir $repository
	n = 0
	dp.select{|x|		# should contain @mark
	  /@/ =~ x   ###  /^\.\.?$/ !~ x
	}.collect{|e| [e, File.stat(e).mtime]}.sort{|a, b|
	  b[1] <=> a[1]
	}.each{|f, dummy|
	  trs += @cgi.tr {
	    i = -1
	    if /true/ =~ @attr["numbering"] then
	      @cgi.td("align" => "center"){(n+=1).to_s}
	    else
	      ""
	    end + "\n" + \
	    IO.readlines(f).collect{|line|
	      i+=1
	      next if @align[i] == 'ignore'
	      line.chop!
	      if /^option.*:stat/ =~ @ctype[i] then
		@counter[@ctitle[i]][line] += 1
	      end
	      if /^(name|名前|氏名|お?なまえ|御芳名)$/io =~ @ctitle[i] then
		#line += "<br><small>(#{f})</small>"
		line = @cgi.a("name"=>f){line}
	      end
	      @cgi.td("align" => @align[i]){
		if line > "" then line else "<br>" end
	      }
	    }.join('')
	  } + "\n"
	}
      }
      # Dir.chdir cwd
      @cgi.out({"charset" => "EUC-JP"}){
	@cgi.html{
	  "\n" + @cgi.head{@cgi.title{@attr["title"]}} + "\n" + \
	  @cgi.body("bgcolor" => @attr["bgcolor"]) { "\n" + \
	    @cgi.h1{@attr["heading"]} + "\n" + \
	    @cgi.p{@attr["leader"]} + "\n" + \
	    @cgi.a("href" => "./"+$myname+"?entry"){"登録/修正する" }+"\n" + \
	    @cgi.table("border" => "1", "width" => @attr["tablewidth"]){
	      "\n" + trs} + "\n" + 
	      @cgi.hr + 
	      @cgi.a("href" => "./"+$myname+"?entry") {"登録/修正する" } +
	      statinfo() +
	      @cgi.br + @cgi.p{@attr["footer"]
	    } + "\n"
	  }
	}
      }
    end
  end # def outputTable
  def statinfo
    (0..@ctype.length-1).collect{|i|
      next if @align[i] == 'ignore'
      if /option\((.*)\).*:stat/ =~ @ctype[i] then
	optlist = $1.split(',')
	# p optlist
	@cgi.table("border" => "1") { "\n" + \
	  @cgi.tr {
	    @cgi.th{"<br>"} + \
	    optlist.collect{|t| @cgi.th{t}}.join('')
	  } + "\n" + \
	  @cgi.tr {
	    @cgi.td {@ctitle[i]} + \
	    optlist.collect{|d|
	      @cgi.td{@counter[@ctitle[i]][d]}
	    }.join('') + "\n"
	  }
	}
      else
	""
      end
    }.join("\n") + "\n"
  end
  def entry
    @cgi.out({"charset" => "EUC-JP"}) {
      @cgi.html {
	@cgi.head{@cgi.title{@attr["title"]+" Entry"}+"\n"} + "\n" + \
	@cgi.body("bgcolor" => @attr["bgcolor"]) {"\n" + \
	  @cgi.form("POST","./"+$myname+"?entry2") { "\n" + \
	    @cgi.table{
	      @cgi.tr {
		@cgi.td{"あなたのメイルアドレス"} + \
		@cgi.td{@cgi.text_field("email", nil, 40)} + "\n" } + \
	      @cgi.tr {
		@cgi.td{"パスワード<br>(初回の場合は無記入)"} + \
		@cgi.td{@cgi.password_field("passwd", nil, 40)} + "\n"
	      }
	    } + \
	    @cgi.submit("LOGIN") + @cgi.br + "\n"
	  } + "\n"
	}
      }
    }
  end # def entry
  def entry2
    params = @cgi.params
    mail, passwd = ["email", "passwd"].collect{|x| params[x][0]}
    pm = PasswdMgr.new($passwd, 0660)
    userdata = if test(?s, "#{$repository}/#{mail}") then
		 userData("#{$repository}/#{mail}")
	       else
		 []
	       end
    if !checkmail(mail) then
      htmlOut("Invalid Mail address",
	      @cgi.p{"メイルアドレスが無効です: #{mail}"})
    elsif pm.userexist?(mail) then
      if pm.checkpasswd(mail, passwd) then
	outputForm(mail, passwd, userdata)
      elsif passwd == @attr["forgotten"] then
	sendMail(mail, "#{@attr['title']} password",
		 "(#{ENV['REMOTE_ADDR']} からのアクセスによる送信)\n" +
                 "#{@attr['title']} 用の #{mail} さんのパスワードは\n" +
                 (pm.getpasswd(mail)||"未定義") + "\nです。\n")
        htmlOut("Sent to you", @cgi.p{"#{mail} 宛に送信しておきました"})
      else
	outputError("パスワードが違います")
      end
    else			# First time registration
      outputForm(mail, nil, userdata)
    end

    # p mail, passwd
    pm.close()
  end
  def outputError(msg)
    @cgi.out{
      @cgi.html{
	@cgi.head{@cgi.title{@attr["title"]+" error"}} + \
	@cgi.body("bgcolor" => @attr["bgcolor"]){
	  @cgi.p{msg}
	}
      }
    }
  end
  def outputForm(user, passwd, default = [])
    @cgi.out({"charset" => "EUC-JP"}) {
      @cgi.html {
	@cgi.head{@cgi.title{@attr["title"]+" Entry for #{user}"}} + "\n" + \
	  @cgi.body("bgcolor" => @attr["bgcolor"]){
	  @cgi.p{"#{user} さんのデータ入力"} + "\n" + \
	  if passwd == nil
	    passwd = generateNewPasswd
	    pm = PasswdMgr.new($passwd)
	    pm.setpasswd(user, passwd)
	    pm.close()
	    sendMail(user, "Your password for #{@attr['title']}",
		     "あなたの #{@attr['title']} 用パスワードは\n" +
		     passwd + "\nです。保管しておいてください。\n" +
		     "(" + ENV['HTTP_REFERER'].sub(/\?.*/, "") + ")\n" +
		     "もしパスワードを忘れた場合は、本当のパスワードの" +
		     "代わりに\n#{@attr['forgotten']} と入れてください。\n")
	    @cgi.p{
	      "新規登録です。あなたのパスワードは"
	    } + @cgi.pre{passwd} + \
	    @cgi.p{"です。大切に保管してください。"} + "\n"
	  else
	    ""
	  end + \
	  @cgi.p{@attr["inputwarning"]} + "\n" + @cgi.hr + "\n" + \
	  @cgi.form("POST", "./#{$myname}?register") {
	    "\n" + \
	    @cgi.hidden("email", user) + "\n" + \
	    @cgi.hidden("passwd", passwd) + "\n" + \
	    @cgi.table{
	      "\n" + \
	      (0..@ctitle.length-1).select{|i| @align[i]!='ignore'}.collect{|i|
		title = @ctitle[i]
		type  = @ctype[i]
		dflt  = default[i]
		@cgi.tr {
		  @cgi.td{title} + \
		  @cgi.td{
		    case type
		    when /^text(\((\d+)\))?/
		      # print title, default[i]
		      @cgi.text_field(title, dflt, $2)
		    when /option\((.*)\)/
		      @cgi.popup_menu({ "NAME" => title,
					"VALUES" => popupValues($1, dflt)})
		    end
		  }
		} + "\n"
	      }.join('')
	    } + \
	    @cgi.hr + "\n" + \
	    @cgi.submit("GO")
	  } + "\n"
	} + "\n"
      }
    }
  end
  def popupValues(choice, default)
    choice.split(",").collect{|elm|
      if elm == default then [elm, true] else [elm] end
    }
  end
  def userData(file)
    IO.readlines(file).collect{|l|l.chop!}
  end
  def generateNewPasswd
    srand(Time.now.to_i)
    length = @attr["passwdlength"].to_i
    left	= "qazxswedcvfrtgb12345"
    right	= "yhnmjuik.lop;/67890-"
    array	= [left, right]
    (1..length).collect{|i|
      a = array[i%array.length]
      a[rand(a.length), 1]
    }.join('')
  end

  def register
    params = @cgi.params
    user = params["email"][0].strip
    pm = PasswdMgr.new($passwd)
    if !checkAllFilled(params) then
      outputError("全ての項目を埋めてください")
    elsif pm.checkpasswd(user, params["passwd"][0]) then
      uback = File.umask
      File.umask(002)
      i = -1
      open("#{$repository}/#{user}", "w") {|fp|
	@ctitle.each{|k|
	  fp.print(if @align[i+=1] == 'ignore' then
	  		'<br>'
		   else
	  		params[k][0]
		   end + "\n")
	}
      }
      if /.*\@.*/ =~ @attr["maintainer"] then
	sendMail(@attr["maintainer"], @attr["title"]+" registration",
		 "#{@attr['title']} registration by #{user}\n" +
		 @ctitle.collect{|k|
		   sprintf "%-20s = #{params[k][0]}", k
		 }.join("\n"))
      end
      File.umask(uback)
      htmlOut(@attr["title"]+" Result",
	      @cgi.p{"完了しました"} + \
	      @cgi.a("href" => "./#{$myname}"){"確認"})
    else
      outputError("送信エラー")
    end
    pm.close()
  end
  def checkAllFilled(params)
    i = -1
    @ctitle.each{|x|
      @align[i+=1] == 'ignore' or params[x][0] or return false
    }
    true
  end

  def htmlOut(title, body)
    @cgi.out({"charset" => "EUC-JP"}) {
      @cgi.html {
	@cgi.head{@cgi.title{title}}+"\n" + \
	@cgi.body("bgcolor" => @attr["bgcolor"]) {
	  body
	}
      }
    }
  end
  def setpasswd(user)
    pm = PasswdMgr.new($passwd)
    if pm.userexist?(user) then
      begin
	system "stty -echo"
	STDERR.print "New passwd: "
	p1 = STDIN.gets
	STDERR.print "\nAgain: "
	p2 = STDIN.gets
      ensure
	system "stty echo"
      end
      if (p1 == p2) then
	pm.setpasswd(user, p1.chop!)
      end
      STDERR.print "New password for #{user} set successfully\n"
    else
      STDERR.print "User #{user} not found\n"
    end
    pm.close()
  end
  def adduser(user)
    pm = PasswdMgr.new($passwd)
    newpwd = generateNewPasswd
    pm.setpasswd(user, newpwd)
    print "#{user}'s password is #{newpwd}\n"
    pm.close()
  end
  def deluser(user)
    pm = PasswdMgr.new($passwd)
    pm.delete(user)
    pm.close()
  end
  def sendMail(to, subject, body)
    body = Kconv::tojis(body)
    subject = Kconv.tojis(subject)
    if /\e/ =~ subject		# If contains JIS chars...
      subject = subject.split(//,1).pack('m')
      subject = "=?iso-2022-jp?B?#{subject}?="
    end
    subject.gsub!(/\n/, '')
    begin
      if (m=open("|-", "w"))
	m.print "To: #{to}\n"
	m.print "Subject: #{subject}\n"
	m.print "Mime-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Type: Text/Plain; charset=iso-2022-jp

"
	m.print body, "\n"
	m.close
      else
	# exec(@attr['mail'], "-s", subject, to)
	exec(@attr['sendmail'], to)
      end
      putLog("Sent '#{subject}' to #{to}\n")
    rescue
      putLog("FAILED! - Sent '#{subject}' to #{to}\n")
    end
  end
  def putLog(msg)
    msg += "\n" unless /\n/ =~ msg
    open(@attr["logfile"], "a+") {|lp|
      lp.print Time.now.to_s + " " + msg
    }
  end
  def checkmail(mail)
    account, domain = mail.scan(/(.*)@(.*)/)[0]
    return false unless account != nil && domain != nil
    return false unless /^[-0-9a-z_.]+$/oi =~ domain
    require 'socket'
    begin
      TCPSocket.gethostbyname(domain)
      return true
    rescue
      if test(?x, @attr["hostcmd"])
	open("| #{@attr['hostcmd']} -t mx #{domain}.", "r") {|ns|
	  #p ns.readlines.grep(/\d,\s*mail exchanger/)
	  return ! ns.readlines.grep(/is handled .*(by |=)\d+/).empty?
	}
      elsif test(?x, @attr["nslookup"])
	open("| #{@attr['nslookup']} -type=mx #{domain}.", "r") {|ns|
	  #p ns.readlines.grep(/\d,\s*mail exchanger/)
	  return ! ns.readlines.grep(/\d,\s*mail exchanger/).empty?
	}
      end
      return false
    end
  end # checkmail
  def install(targetdir)
    @attr = defaultAttr
    STDERR.print "[] の中にあるのはデフォルト値\n\n"
    newconfig = Hash.new
    mailguessed = ENV["USER"]+"@"+`hostname`.chop!.sub!(/[^.]+./, '')
    STDERR.print "あなた(=管理人)のメイルアドレス(重要) [#{mailguessed}]: "
    if (l=STDIN.gets.chop!) == "" then l = mailguessed end
    if !checkmail(l) then
      STDERR.print "そんなメイルアドレスじゃあ管理させられんな\n"
      exit 1
    end
    newconfig["maintainer"] = l
    [ "タイトル:title",
      "ページ背景色:bgcolor",
      "見出し:heading",
      "最初の一行説明文:leader",
      "表の幅:tablewidth",
      "表の番号づけ(true/false):numbering",
    ].each{|item|
      guide, attr = item.scan(/(.*):(.*)/)[0]
      default = @attr[attr]
      STDERR.printf "%s [%s]: ", guide, default
      value = Kconv::toeuc(STDIN.gets.chop)
      if value > "" then
	newconfig[attr] = value
      end
    }
    # Input columns
    cols = Array.new
    q = lambda{|n, default|
      odf = Kconv::toeuc("○,△,×")
      STDERR.printf "第%d項目名 (もう要らないときは q) [\"%s\"]: ", n, default
      l = Kconv::toeuc(STDIN.gets.chop)
      throw :done, true if l == "q"
      cols[n] = Array.new
      if l == "" then l = default end
      cols[n][0] = l
      STDERR.print "種別 (1=text, 2=option) [2]: "
      l = STDIN.gets.chop!
      case l
      when "1"
	STDERR.print "text入力最大バイト数 [20]: "
	if (l=STDIN.gets.chop) == "" then l = "20" end
	cols[n][1] = "text(#{l})"
      else
	STDERR.print "選択肢を半角カンマで区切って入れてください [#{odf}]\n"
	l = Kconv::toeuc(STDIN.gets.chop!)
	if l == "" then l = odf end
	cols[n][1] = "option(#{l})"
	STDERR.print "この項目を集計する? [no]: "
	if /y/i =~ STDIN.gets then cols[n][1] += ":stat" end
      end
    }
    #q.call(0, "名前")
    STDERR.print "勝手ながらインストーラでは第0項目名を「名前」に致します.\n"
    cols[0] = ["名前", "text(20)"]
    i=0
    catch (:done) {
      while true
	q.call(i+=1, "項目#{i}") 
      end
    }
    confstr = confdump(newconfig, cols)
    print "-" * 70 + "\n", confstr, "-"*70, "\n"
    STDERR.print \
    "設定ファイル(#{@conf})に書き出して作業ディレクトリも準備しますか? [y/N]: "
    if /y/i =~ STDIN.gets then
      if pid=fork then
	Process.wait
      else
	File.umask(0)
	Dir.mkdir(targetdir, 01777) unless test(?d, targetdir)
	#Dir.chdir(targetdir)
	open(targetdir+"/"+@conf, "w"){|fp| fp.print confstr}
	repdir = targetdir+"/"+$repository
	Dir.mkdir(repdir, 0777) unless test(?d, repdir)
	clone = targetdir+"/"+$mybase+".cgi"
	File.link($0, clone) unless test(?f, clone)
	logfile = targetdir+"/"+@attr["logfile"]
	open(logfile, "a+"){|fp| fp.chmod(0666)}
	Dir.chdir(targetdir)
	PasswdMgr.new($passwd, 0666).close()
	exit 0
      end
      STDERR.print "\nディレクトリ属性とかあま目に設定したので後で直してね.\n"
      STDERR.print "あと、\n\tDirectoryIndex\tuogashi.cgi\nなんてのを"
      STDERR.print ".htaccess に書いとくといいかも。\n"
    end
  end
  def webinstall
    htmlOut("#{$mybase} planner web installer",
	    @cgi.form("POST", "#{$myname}?webinstall_2") { "\n" +
	      @cgi.table{ "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"メイルアドレス"} +\
		  @cgi.td{@cgi.text_field("email", nil, 40)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"パスワード"} + \
		  @cgi.td{@cgi.password_field("passwd", nil, 40)}
		}
	      } + \
	      @cgi.submit("OK") + @cgi.reset("reset")
	    })
  end # webinstall_1
  def webinstall_2
    params = @cgi.params
    admin  = params["email"][0]
    passwd = params["passwd"][0]
    if admin != @attr["maintainer"]
      htmlOut("#{$mybase} authentication error", @cgi.p{"管理人しかだめよ."})
      exit 0
    end
    df = defaultAttr
    htmlAuth(admin, passwd)	# if invalid passwd, exits
    pm = PasswdMgr.new($passwd)
    sessionkey = Time.now.to_i.to_s
    sessionpasswd = generateNewPasswd
    pm.setpasswd(sessionkey, sessionpasswd.crypt("uo"))
    pm.close()
    sendMail(@attr["maintainer"], "uogashi: webinstall notice",
	     <<EOS
#{ENV['REMOTE_HOST']} より uogashi webinstall のリクエストがありました。
このセッションのパスワードを #{sessionpasswd} に設定しました。
もし予期せずこのメイルを受け取った場合は誰かがイタズラしている
可能性があります。悪気は無く何も起きていないかもしれませんが、一応
#{ENV['HTTP_REFERER'].sub(/\?.*/, "")}
のディレクトリを確認してみてください。
EOS
	     )
    htmlOut("#{$mybase} Planning Editor",
	    "\n" +
	    @cgi.p {
	      <<-EOM
	      この集計を行なうディレクトリ名を指定する。
	      アルファベットでわかりやすい単語を必ず入れること。<br>
	      「ページ背景色」~「表の番号付け」は任意。<br>
	      「セッションパスワード」は、今メイルで送ったのでそれを入れる。
	      EOM
	    } + \
	    @cgi.form("POST", "#{$myname}?webinstall_3") { "\n" + \
	      @cgi.table("border" => "1") { "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"セッションパスワード"} + \
		  @cgi.td{@cgi.password_field("pas", nil, 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"集計ディレクトリ名"} + \
		  @cgi.td{@cgi.text_field("dir", nil, 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"集計名(タイトル)"} + \
		  @cgi.td{@cgi.text_field("title", nil, 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"ページ先頭の見出し"} + \
		  @cgi.td{@cgi.text_field("heading", nil, 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"ページ背景色"} + \
		  @cgi.td{@cgi.text_field("bgcolor", df["bgcolor"], 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"最初の一行説明文"} + \
		  @cgi.td{@cgi.text_field("leader", df["leader"], 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"表の幅(nilとすると自由幅)"} + \
		  @cgi.td{@cgi.text_field("tablewidth", "700", 20)}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{"表の番号付け (true/false)"} + \
		  @cgi.td{@cgi.popup_menu("numbering", "true", "false")}
		} + "\n"
	      } + "\n" + \
	      @cgi.p {
		<<-GUIDE
		続いて項目名を決定。<br>
		「種別」 text の場合は「値」に最大バイト数を<br>\n
		「種別」 option の場合は「値」に選択肢文字列をカンマで区切って
		入れる<br>例 
		晴れ,雨,曇,雪<br><br>
		  必要なだけ項目を埋めたらそこから下は空欄のままにする。
		GUIDE
	      } + \
	      @cgi.table("border" => "1") { "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.th{"項目名"}+@cgi.th{"種別"}+@cgi.th{"値"}
		} + "\n" + \
		@cgi.tr { "\n" + \
		  @cgi.td{@cgi.popup_menu("0,0",
					  "名前", "氏名", "なまえ",
					  "御芳名", "Name")} + "\n" + \
		  @cgi.td{@cgi.popup_menu("0,1", "text")} + "\n" + \
		  @cgi.hidden("0,2", "40") + @cgi.td{"40"}
		} + \
		(1..9).collect{|n|
		  @cgi.tr {
		    @cgi.td{@cgi.text_field("#{n},0", n<4 && "項目#{n}", 20)} \
		    + "\n" + \
		    @cgi.td{@cgi.popup_menu("#{n},1", "option", "text",
					    "option:stat")} + \
		    @cgi.td{@cgi.text_field("#{n},2", n<4 && "○,△,×", 30)}
		  }
		}.join("\n") + "\n"
	      } + \
	      @cgi.hidden("session", sessionkey) + "\n" + \
	      @cgi.hidden("adm", admin) + "\n" + \
	      @cgi.submit("OK") + " " + @cgi.reset("reset")
	    })
    
  end # webinstall_2
  def webinstall_3
    params = @cgi.params
    admin  = params["adm"][0]; params.delete("adm")
    sskey  = params["session"][0]; params.delete("session")
    spass  = params["pas"][0]; params.delete("pas")
    dir = params["dir"][0]; params.delete("dir")
    if admin != @attr["maintainer"]
      htmlOut("#{$mybase} authentication error", @cgi.p{"管理人しかだめよ."})
      exit 0
    end
    if dir == nil || dir == ""
      htmlOut("#{$mybase} directory error", @cgi.p{"ディレクトリ名は必須."})
      exit 0
    elsif /^[-0-9a-z_]+$/ !~ dir
      htmlOut("#{$mybase} directory error",
	      @cgi.p{"ディレクトリ名は一応半角英数字だけにしといて下さい."})
      exit 0
    end
    htmlAuth(sskey, spass.crypt("uo"))	# if invalid passwd, exits
    deluser(sskey)		# remove session key
    newconfig = Hash.new
    newconfig["maintainer"] = admin
    params.each_key{|key|
      next unless /^[a-z]/ =~ key
      newconfig[key] = params[key][0]
      params.delete(key)
    }
    cols = Array.new
    (0..9).each{|i|
      break unless params["#{i},0"][0] && params["#{i},0"][0] > ""
      cols[i] = Array.new
      cols[i][0] = params["#{i},0"][0]
      case (type=params["#{i},1"][0])
      when /text/
	cols[i][1] = "text(" + (params["#{i},2"][0] || "40") + ")"
      when /option/
	cols[i][1] = "option(" + (params["#{i},2"][0] || "○,△,×") + ")"
	if /:stat/ =~ type
	  cols[i][1] += ":stat"
	end
      end
    }
    #htmlOut("Result", "<p>OKおしまい</p>")
    # Begin installation
    begin
      File.umask(0)
      ucgi = dir+"/"+$mybase+".cgi"
      test(?d, dir) || Dir.mkdir(dir, 01777)
      test(?d, dir+"/repository") || Dir.mkdir(dir+"/repository", 01777)
      test(?r, ucgi) && File.unlink(ucgi)
      File.link($0, ucgi)
      open("#{dir}/#{$mybase}.conf", "w") {|fp|
	fp.print(confdump(newconfig, cols))
      }
    rescue
      htmlOut("#{$mybase} installation error at final stage",
	      @cgi.p{
		<<-EOM
		エラー $! <br>
		書き込み時にエラーが起きました。おそらく
		#{$mybase} の置いてあるディレクトリに書き込み権限が
		無いのだと思います。webinstallを利用したい場合は
		wwwサーバに自由に書き込みできる属性設定にしておいて
		下さい。
		EOM
	      })
      exit 0
    end
    htmlOut("#{$mybase} installation done",
	    @cgi.p {
	      <<-EOM
	      以下の設定を #{dir}/#{$mybase}.conf に書き込みました。
		EOM
	    } + \
	    @cgi.pre{confdump(newconfig, cols)} + \
	    @cgi.a("href" => "#{dir}/#{$mybase}.cgi"){
		"新しく作成されたページ"
	    })
  end # webinstall_3
  def htmlAuth(user, passwd)
    pm = PasswdMgr.new($passwd)
    if pm.getpasswd(user) == nil then
      htmlOut("#{$mybase} authentication error", @cgi.p{"パスワード設定せよ"})
      exit 0
    end
    if !pm.checkpasswd(user, passwd) then
      htmlOut("#{$mybase} authentication error", @cgi.p{"パスワード違うの."})
      exit 0
    end
  end
  def userdel(user)
    pm = PasswdMgr.new($passwd)
    pm.delete(user)
    pm.close()
  end
  def confdump(prop, column)
    prop.keys.collect{|k|
      "#{k}=#{prop[k]}"
    }.join("\n") + "\n" + \
    (0..column.length-1).collect{|n|
      column[n][0] + "\t" + column[n][1]
    }.join("\n") + "\n"
  end
end

class PasswdMgr
  def initialize(name, mode=0640)
    require 'dbm'
    @pdb = DBM.open(name, mode)
  end
  def checkpasswd(user, passwd)
    if @pdb[user] then
      @pdb[user] == passwd
    else
      @pdb[user] = passwd	# If empty, then set new passwd
      true
    end
  end
  def setpasswd(user, passwd)
    @pdb[user] = passwd
  end
  def userexist?(user)
    @pdb[user] ? true : false
  end
  def getpasswd(user)
    @pdb[user]
  end
  def delete(user)
    @pdb.delete(user)
  end
  def close()
    @pdb.close()
  end
end


args = ARGV.dup
#ARGV.shift
uogashi = TableMaker.new
open("uogashi.log", "w") {|lp|
  lp.print `env|sort`
  uogashi.cgi.params.each{|k, v|
    lp.print "#{k}=#{v}\n"
  }
}

if !args[0] and ENV["QUERY_STRING"]
 args[0] = ENV["QUERY_STRING"]
end
case args[0]
when /^(entry2?|register)$/
  eval "uogashi.#{args[0]}"
when /^(setpasswd|deluser|adduser|checkmail)$/
  p (eval "uogashi.#{args[0]}('#{args[1]}')")
when "install"
  if args[1] == nil
    STDERR.print "Usage: #{$0} install 集計ディレクトリ名\n"
    STDERR.print "例: #{$0} install uogashi\n"
    exit 1
  end
  STDERR.print "args1=#{args[1]}\n"
  uogashi.install(args[1])
when "webinstall"
  uogashi.webinstall
when /webinstall_[23]/
  eval "uogashi.#{args[0]}"
else
  uogashi.outputTable
end


if __FILE__ == $0
end


# Local variables:
# buffer-file-coding-system: euc-jp
# End: