forked from zuazo/dovecot-cookbook
-
Notifications
You must be signed in to change notification settings - Fork 1
/
pwfile.rb
162 lines (151 loc) · 5.91 KB
/
pwfile.rb
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# Cookbook Name:: dovecot
# Library:: pwfile
# Author:: Xabier de Zuazo (<xabier@zuazo.org>)
# Copyright:: Copyright (c) 2013-2014 Onddo Labs, SL.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
module DovecotCookbook
# Helper module to check password file and import it
module Pwfile
extend Chef::Mixin::ShellOut
# Checks if the file exists locally.
#
# @param localdata [String] File path.
# @return [Boolean] Whether the file exists.
def self.exists?(localdata)
::File.exist?(localdata)
end
# Reads a password file from this and it returns its contents as a hash.
#
# @param inputfile [String] File path.
# @return [Hash] The file contents.
def self.file_to_hash(inputfile)
output_entries = {}
File.open(inputfile, File::RDONLY | File::CREAT, 0o0640) do |passwordfile|
passwordfile.readlines.each do |line|
user, data = fileline_to_userdb_hash(line)
output_entries[user] = data
end
end
output_entries
end
# Returns Password file in userdb style hash and if exists.
#
# @param input [String] The path of the file.
# @return [Array] An array with two values: The input in hash format and
# whether the file exists on the disk or not.
# @api public
def self.passfile_read(input)
[file_to_hash(input), exists?(input)]
end
# Returns a hash of details taken from the userdb file line.
#
# @param input [String] A line of input.
def self.fileline_to_userdb_hash(input)
data = [nil] * 7
if input.strip.split(':').length == 2
user, data[0] = input.strip.split(':')
else
user = input.strip.split(':')[0]
data = input.strip.split(':')[1..7]
end
[user, data]
end
# Returns an array with 8 values to use with user copy.
#
# @param key [String] The name, or the first value to be included in the
# final array.
# @param value [String, Array] An array of values to be added to the key.
# @return [Array] An array of length 8 with the format
# `[key, value1, value2, ..., value7]`. The array is filled with `nil`
# values if there are less than 7 values in `value` array.
def self.dbentry_to_array(key, value)
if value.is_a?(Array)
[key] + (value + ([nil] * (7 - value.size)))
else
[key, value] + ([nil] * 6)
end
end
# Checks if a plain text password matches a specific encrypted password.
#
# @param hashed_pw [String] The password encrypted.
# @param plaintext_pw [String] The password in clear text.
# @return [Boolean] Whether the two passwords are the same.
def self.password_valid?(hashed_pw, plaintext_pw)
shell_out("/usr/bin/doveadm pw -t '#{hashed_pw}' -p '#{plaintext_pw}'")
.exitstatus.zero?
end
# Checks if two arrays contain the same values.
#
# @param array1 [Array] The first array.
# @param array2 [Array] The second array.
# @return [Boolean] Returns `true` if both arrays contain the same values.
# @api public
def self.arrays_same?(array1, array2)
(array1 - array2).empty? && (array2 - array1).empty?
end
# Encrypts a plain text password.
#
# @param plaintextpass [String] The password to encrypt.
# @return [String] The password encrypted.
def self.encrypt_password(plaintextpass)
shell_out("/usr/bin/doveadm pw -s MD5 -p '#{plaintextpass}'")
.stdout.tr("\n", '')
end
# Generates the user password on the disk password file only if required.
#
# @param input_creds [Array] Credentials on disk for a user.
# @param plaintextpass [String] The password to encrypt.
# @param updated [Boolean] Previous values of whether any user has been
# updated.
# @param file_exists [Boolean] Whether the local file already exists.
# @return [Array] An array with two values: the encrypted password and
# whether it needs to be updated on disk.
def self.generate_userpass(input_creds, plaintextpass, updated, file_exists)
if !input_creds.nil? && file_exists == true &&
password_valid?(input_creds[0], plaintextpass)
return [input_creds[0], updated]
end
[encrypt_password(plaintextpass), true]
end
# Update users credentials only if required.
#
# The `credentials` parameter is updated with all the credentials.
#
# @param databag_users [Hash] User list read from the Data Bag.
# @param current_users [Hash] User list read from a file on the disk.
# @param pwfile_exists [Boolean] Whether exists a file on the disk.
# @param prev_updated [Boolean] Previous values of this function return
# value.
# @param credentials [Array] The list of credentials. This value is
# populated by this function with the generated (encrypted) credentials
# ready to be written to disk.
# @return [Boolean] `true` if any user has been updated.
# @api public
def self.compile_users(
databag_users, current_users, pwfile_exists, prev_updated, credentials
)
databag_users.reduce(prev_updated) do |updated, (username, user_details)|
current_user = dbentry_to_array(username, user_details)
current_user[1], updated =
generate_userpass(
current_users[username], current_user[1], updated, pwfile_exists
)
credentials.push(current_user)
updated
end
end
end
end