summaryrefslogtreecommitdiff
path: root/lib/webrick/httpauth/htdigest.rb
blob: 4bb25e1724b3a4ee71cd3eedf85f7a03bc37d07a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: false
#
# httpauth/htdigest.rb -- Apache compatible htdigest file
#
# Author: IPR -- Internet Programming with Ruby -- writers
# Copyright (c) 2003 Internet Programming with Ruby writers. All rights
# reserved.
#
# $IPR: htdigest.rb,v 1.4 2003/07/22 19:20:45 gotoyuzo Exp $

require 'webrick/httpauth/userdb'
require 'webrick/httpauth/digestauth'
require 'tempfile'

module WEBrick
  module HTTPAuth

    ##
    # Htdigest accesses apache-compatible digest password files.  Passwords are
    # matched to a realm where they are valid.  For security, the path for a
    # digest password database should be stored outside of the paths available
    # to the HTTP server.
    #
    # Htdigest is intended for use with WEBrick::HTTPAuth::DigestAuth and
    # stores passwords using cryptographic hashes.
    #
    #   htpasswd = WEBrick::HTTPAuth::Htdigest.new 'my_password_file'
    #   htpasswd.set_passwd 'my realm', 'username', 'password'
    #   htpasswd.flush

    class Htdigest
      include UserDB

      ##
      # Open a digest password database at +path+

      def initialize(path)
        @path = path
        @mtime = Time.at(0)
        @digest = Hash.new
        @mutex = Mutex::new
        @auth_type = DigestAuth
        open(@path,"a").close unless File::exist?(@path)
        reload
      end

      ##
      # Reloads passwords from the database

      def reload
        mtime = File::mtime(@path)
        if mtime > @mtime
          @digest.clear
          open(@path){|io|
            while line = io.gets
              line.chomp!
              user, realm, pass = line.split(/:/, 3)
              unless @digest[realm]
                @digest[realm] = Hash.new
              end
              @digest[realm][user] = pass
            end
          }
          @mtime = mtime
        end
      end

      ##
      # Flush the password database.  If +output+ is given the database will
      # be written there instead of to the original path.

      def flush(output=nil)
        output ||= @path
        tmp = Tempfile.create("htpasswd", File::dirname(output))
        renamed = false
        begin
          each{|item| tmp.puts(item.join(":")) }
          tmp.close
          File::rename(tmp.path, output)
          renamed = true
        ensure
          tmp.close if !tmp.closed?
          File.unlink(tmp.path) if !renamed
        end
      end

      ##
      # Retrieves a password from the database for +user+ in +realm+.  If
      # +reload_db+ is true the database will be reloaded first.

      def get_passwd(realm, user, reload_db)
        reload() if reload_db
        if hash = @digest[realm]
          hash[user]
        end
      end

      ##
      # Sets a password in the database for +user+ in +realm+ to +pass+.

      def set_passwd(realm, user, pass)
        @mutex.synchronize{
          unless @digest[realm]
            @digest[realm] = Hash.new
          end
          @digest[realm][user] = make_passwd(realm, user, pass)
        }
      end

      ##
      # Removes a password from the database for +user+ in +realm+.

      def delete_passwd(realm, user)
        if hash = @digest[realm]
          hash.delete(user)
        end
      end

      ##
      # Iterate passwords in the database.

      def each # :yields: [user, realm, password_hash]
        @digest.keys.sort.each{|realm|
          hash = @digest[realm]
          hash.keys.sort.each{|user|
            yield([user, realm, hash[user]])
          }
        }
      end
    end
  end
end