318 lines
5.8 KiB
Crystal
318 lines
5.8 KiB
Crystal
require "csv"
|
|
require "file_utils"
|
|
|
|
# FIXME: Should we work on arrays and convert to CSV at the last second when adding rows?
|
|
# FIXME: Use split, not CSV.
|
|
# FIXME: Prevent using ':' in fields.
|
|
|
|
class Passwd
|
|
end
|
|
|
|
require "./user.cr"
|
|
require "./group.cr"
|
|
|
|
class Passwd
|
|
@passwd : String
|
|
@group : String
|
|
|
|
# FIXME: Missing operations:
|
|
# - Reading groups.
|
|
# - Adding and removing groups (ok, maybe admins can do that?)
|
|
# - Adding users to group.
|
|
# - Removing users from group.
|
|
|
|
def initialize(@passwd, @group)
|
|
end
|
|
|
|
private def passwd_as_array
|
|
CSV.parse File.read(@passwd), separator: ':'
|
|
end
|
|
|
|
private def group_as_array
|
|
CSV.parse File.read(@group), separator: ':'
|
|
end
|
|
|
|
private def set_user_groups(user : Passwd::User)
|
|
group_as_array.each do |line|
|
|
group = Passwd::Group.new line
|
|
|
|
if group.users.any? { |name| name == user.login }
|
|
user.groups << group.name
|
|
end
|
|
end
|
|
end
|
|
|
|
def each_user(&block)
|
|
passwd_as_array.each do |line|
|
|
yield Passwd::User.new line
|
|
end
|
|
end
|
|
|
|
def user_exists?(login : String) : Bool
|
|
each_user do |user|
|
|
return true if user.login == login
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
def get_user(&block) : Passwd::User?
|
|
each_user do |user|
|
|
if yield user
|
|
set_user_groups user
|
|
|
|
return user
|
|
end
|
|
end
|
|
end
|
|
|
|
def get_user(uid : Int32)
|
|
get_user &.uid.==(uid)
|
|
end
|
|
|
|
def get_user(login : String)
|
|
get_user &.login.==(login)
|
|
end
|
|
|
|
##
|
|
# Will fail if the user is found but the password is invalid.
|
|
# Use this method if you somehow need to check the password…
|
|
# Be careful though, your system probably does not use SHA256 hashes as
|
|
# passwords. You should use this only on your own passwd and group files.
|
|
def get_user(login : String, password : String)
|
|
hash = Passwd.hash_password password
|
|
|
|
get_user do |user|
|
|
user.login == login && user.password_hash == hash
|
|
end
|
|
end
|
|
|
|
def each_group(&block)
|
|
group_as_array.each do |line|
|
|
yield Passwd::Group.new line
|
|
end
|
|
end
|
|
|
|
def get_all_users
|
|
users = Array(Passwd::User).new
|
|
|
|
passwd_as_array.each do |line|
|
|
users << Passwd::User.new line
|
|
end
|
|
|
|
users
|
|
end
|
|
|
|
def get_group(&block) : Passwd::Group?
|
|
each_group do |group|
|
|
if yield group
|
|
return group
|
|
end
|
|
end
|
|
end
|
|
|
|
def get_group(gid : Int32)
|
|
get_group &.gid.==(gid)
|
|
end
|
|
|
|
def get_group(name : String)
|
|
get_group &.name.==(name)
|
|
end
|
|
|
|
def get_all_groups
|
|
groups = Array(Passwd::Group).new
|
|
|
|
group_as_array.each do |line|
|
|
groups << Passwd::Group.new line
|
|
end
|
|
|
|
groups
|
|
end
|
|
|
|
def to_passwd
|
|
get_all_users.map(&.to_csv).join("\n") + "\n"
|
|
end
|
|
|
|
def to_group
|
|
get_all_groups.map(&.to_csv).join("\n") + "\n"
|
|
end
|
|
|
|
def get_free_uid(starting_uid = 1000)
|
|
uid = starting_uid
|
|
|
|
users = get_all_users
|
|
|
|
while users.any? &.uid.== uid
|
|
uid += 1
|
|
end
|
|
|
|
uid
|
|
end
|
|
|
|
def get_free_gid(starting_gid = 1000)
|
|
gid = starting_gid
|
|
|
|
users = get_all_users
|
|
|
|
while users.any? &.gid.== gid
|
|
gid += 1
|
|
end
|
|
|
|
gid
|
|
end
|
|
|
|
def self.hash_password(password)
|
|
digest = OpenSSL::Digest.new("sha256")
|
|
digest << password
|
|
digest.hexdigest
|
|
end
|
|
|
|
def add_user(login,
|
|
password = nil,
|
|
uid = nil,
|
|
gid = nil,
|
|
home = "/",
|
|
shell = "/bin/nologin",
|
|
full_name = nil,
|
|
location = nil,
|
|
office_phone_number = nil,
|
|
home_phone_number = nil,
|
|
other_contact = nil)
|
|
# FIXME: If user already exists, exception? Replacement?
|
|
|
|
uid = get_free_uid if uid.nil?
|
|
|
|
gid = get_free_gid if gid.nil?
|
|
|
|
password_hash = if password
|
|
Passwd.hash_password password
|
|
else
|
|
"x"
|
|
end
|
|
|
|
user = Passwd::User.new(login, password_hash, uid, gid, home, shell,
|
|
full_name: full_name,
|
|
location: location,
|
|
office_phone_number: office_phone_number,
|
|
home_phone_number: home_phone_number,
|
|
other_contact: other_contact)
|
|
|
|
File.write(@passwd, user.to_csv + "\n", mode: "a")
|
|
|
|
add_group login, gid: gid, users: [user.login]
|
|
|
|
set_user_groups user
|
|
|
|
user
|
|
end
|
|
|
|
def add_group(name, password_hash = "x", gid = nil, users = Array(String).new)
|
|
gid = get_free_gid if gid.nil?
|
|
|
|
group = Passwd::Group.new name, password_hash, gid, users
|
|
|
|
File.write(@group, group.to_csv + "\n", mode: "a")
|
|
end
|
|
|
|
# FIXME: Edit other important fields.
|
|
def mod_user(uid, password_hash : String? = nil)
|
|
new_passwd = passwd_as_array.map do |line|
|
|
user = Passwd::User.new line
|
|
|
|
if uid == user.uid
|
|
password_hash.try do |hash|
|
|
user.password_hash = hash
|
|
end
|
|
|
|
user.to_csv
|
|
else
|
|
line.join(':')
|
|
end
|
|
end
|
|
|
|
File.write @passwd, new_passwd.join("\n") + "\n"
|
|
end
|
|
|
|
def mod_group(group : ::Passwd::Group)
|
|
new_group = group_as_array.map do |line|
|
|
_group = Passwd::Group.new line
|
|
|
|
if _group.gid == group.gid
|
|
group.to_csv
|
|
else
|
|
line.join(':')
|
|
end
|
|
end
|
|
|
|
File.write @group, new_group.join('\n') + '\n'
|
|
end
|
|
|
|
private def safe_rewrite(path : String, body : String)
|
|
tempfile = File.tempfile(File.basename path)
|
|
tempfile << body
|
|
tempfile.close
|
|
|
|
FileUtils.cp tempfile.path, path
|
|
end
|
|
|
|
def remove_user_from_groups(user)
|
|
new_group = group_as_array.map do |line|
|
|
group = Group.new line
|
|
|
|
group.users.select! &.!=(user.login)
|
|
group.users.select! &.!=(user.uid.to_s)
|
|
|
|
group.to_csv
|
|
end
|
|
|
|
safe_rewrite @group, new_group.join('\n') + '\n'
|
|
end
|
|
|
|
def remove_user(&block)
|
|
new_passwd = passwd_as_array.compact_map do |line|
|
|
user = User.new line
|
|
|
|
if yield user
|
|
remove_user_from_groups user
|
|
|
|
nil
|
|
else
|
|
line.join ':'
|
|
end
|
|
end
|
|
|
|
safe_rewrite @passwd, new_passwd.join("\n") + "\n"
|
|
end
|
|
|
|
def remove_user(uid : Int32)
|
|
remove_user &.uid.==(uid)
|
|
end
|
|
|
|
def remove_user(login : String)
|
|
remove_user &.login.==(login)
|
|
end
|
|
|
|
def remove_group(&block)
|
|
new_group = group_as_array.compact_map do |line|
|
|
group = Group.new line
|
|
|
|
if yield group
|
|
nil
|
|
else
|
|
line.join ':'
|
|
end
|
|
end
|
|
|
|
safe_rewrite @group, new_group.join('\n') + '\n'
|
|
end
|
|
|
|
def remove_group(gid : Int32)
|
|
remove_group &.gid.==(gid)
|
|
end
|
|
|
|
def remove_group(name : String)
|
|
remove_group &.name.==(name)
|
|
end
|
|
end
|
|
|