Apacheのダイジェスト認証DB(いわゆる.htdigest)のエントリを生成するスクリ プトを書いた。

  • kanatouserid.rb:
    日本語仮名表記の名前を与えると、ユーザIDの案を出力する。

  • tohtdigest.rb:
    ユーザIDとパスワードとrealmを与えると、htdigest向けの エントリを出力する。

使い方

% echo "やまだ たろう\nすずき はなこ" | kanatouserid.rb
tyamada
hsuzuki

% echo -n "userid PASSWORD" | tohtdigest.rb --realm=realm
userid:realm:3c5849e99abf53956919e822fd3c6f32

% apg
...
% cat | kanatouserid.rb | tohtdigest.rb --realm=foo
やまだ たろう
すずき はなこ
^D
...
tyamada:foo:6cd6ad4e982e880951edf09b93677793
hsuzuki:foo:3ea0703da39fdd92b9c7d00e9df67d74
%

kanatouserid.rb

#!/usr/bin/ruby
# -*- coding: utf-8 -*-
# usage:
#   % echo "やまだ たろう\nすずき はなこ" | kanatouserid.rb
#   tyamada
#   hsuzuki
# requirements: ruby, kakasi
# license: public domain

require 'nkf'

kana_names = ARGF.readlines

roman_names = kana_names.map{|n|
  if /./.match(n)
  IO.popen("kakasi -Ha -Ka -Ja -Ea -ka", "rb+"){|k|
    k.puts NKF.nkf("--unix", n)
    k.close_write
    k.gets
  }
  else
    $stderr.print "Warn: cannot convert to roman: #{n.inspect}\n"
  end
}

userids = roman_names.map{|n|
  if /[^\s]+?\s+?[^\s]+?/.match(n)
    family, given = n.split(/\s+/)
    given.split(//).first + family # jdoe
  else
    $stderr.print "Warn: invalid name (order family given):
#{n.inspect}\n"
  end
}

userids.map{|u|
  puts u
}

tohtdigest.rb

#!/usr/bin/ruby
# -*- coding: utf-8 -*-
# htdigest helper
# usage:
#   % echo -n "userid PASSWORD" \
#     | tohtdigest.rb --realm=realm \
#     >> /path/to/apache2/.htdigest
#   % tail -n 1 /path/to/apache2/.htdigest
#   userid:realm:3c5849e99abf53956919e822fd3c6f32
# requirements: ruby, openssl
# license: public domain

module HTDigest
  def htdigest(userid, realm, password)
    src = [userid, realm, password].join(":")
    hash = ` echo -n #{src} | openssl dgst -md5`.chomp
    [userid, realm, hash].join(":")
  end
  module_function :htdigest
end

def main(lines, config)
  entries = lines.map{|l|
    userid, password = l.split(/\s+/)
    HTDigest.htdigest(userid, config[:realm], password)
  }
  puts entries
end

if $0 == __FILE__
  # handle options
  require 'optparse'
  default_config = {
    :realm => nil,
    :test  => false
  }
  clo = command_line_options = {}
  ARGV.options {|o|
    o.def_option('--realm=REALM',
                 'specify realm (mandatory)',
                 String){|s| clo[:realm] = s}
    o.def_option('--test', 'run test'){clo[:test] = true}
    o.def_option('--help', 'show this message'){puts o; exit(0)}
    o.parse!
  } or exit(1)
  unless clo[:realm]
    raise OptionParser::MissingArgument, "--realm option required"
  end
  config = default_config.update(clo)

  if config[:test]
    # run test
    require 'test/unit'
    class TC_HTDigest < Test::Unit::TestCase
      def setup
        @srcs  = [["userid", "realm", "PASSWORD"],
                  ["jdoe",   "realm", "sesame"]]
      end
      def test_htdigest
        expected = ["userid:realm:3c5849e99abf53956919e822fd3c6f32",
                    "jdoe:realm:92c75fce053b3dc40e3571ecd4c2822f"]
        result = @srcs.map{|s| HTDigest.htdigest(s[0], s[1], s[2])}
        assert_equal(expected, result)
      end
    end
  else
    # run main
    lines = ARGF.readlines
    main(lines, config)
  end
end