require 'fileutils' POSIX_NAME_PREFIX = "emacs" USER_LOCATION = "./users/" $projects = [] $users = [] $project_id_map = {} $user_id_map = {} ##################### # User manipulation # ##################### class User attr_accessor :id, :keypath attr_reader :keys def to_s "#{@id}" end def initialize(id) @id = id @keypath = "#{USER_LOCATION}#{id}" refresh_keys end def add_key_from_string(keystring) unless valid_pubkey? keystring raise InvalidSSHPubKey, "Public key not valid" end ks = keystring.split(" "); key = SSHKey.new(ks[0][4..6], ks[1], ks[2]) unless @keys.include? key @keys << key else raise DuplicateSSHPubKey, "Public key is already added to user" end flush refresh_keys flush_projects_with_user(self) end def remove_key(key) @keys.delete(key) flush refresh_keys flush_projects_with_user(self) end def refresh_keys File.open(@keypath,"r") do |f| @keys = f.read.split("\n").select{ |line| line[0..2] == "ssh" }.map{ |ks| k = ks.split(" "); SSHKey.new(k[0][4..6], k[1], k[2]) } end end def flush File.open(@keypath,"w+") do |key_file| @keys.each do |key| key_file << "#{key.to_s}\n" end end end end def add_user(id) user = User.new(id) $users << user $user_id_map[id] = user end def get_user_by_id(id) $user_id_map[id] end def validate_password end ######################## # Project manipulation # ######################## class Project attr_accessor :id, :path, :posixname, :users attr_reader :keys def initialize @users = [] end def to_s "#{@id}, #{@path}" end def add_key(key) @keys << key end def add_user(user) @users << user user.keys.each do |key| add_key(key) end flush refresh end def remove_user(user) @user.delete(user) flush refresh end def refresh @users = File.open("#{@path}/.ssh/users","r") do |f| f.read.split("\n").map{|id| get_user_by_id(id)} end @keys = extract_ssh_pubkeys("#{@path}/.ssh/authorized_keys") end def flush File.open("#{@path}/.ssh/users","w+") do |user_file| @users.each do |u| user_file << "#{u.id}\n" end end get_ssh_keyfile(self,"w+") do |key_file| @users.each do |user| user.keys.each do |key| key_file << "#{key.to_s}\n" end end end end end class DuplicateProjectError < StandardError end def get_project(id) $project_id_map[id] end def add_project(project) $projects << project $project_id_map[project.id] = project end def create_project(id) $projects.each do |p| if p == id then raise DuplicateProjectError, "Project with id #{id} already exists" end end if not system("useradd -m #{POSIX_NAME_PREFIX}#{id} -s /usr/bin/eshell") then raise "Project creation failed" end project = Project.new project.id = id project.path = "/home/#{POSIX_NAME_PREFIX}#{id}" project.posixname = "#{POSIX_NAME_PREFIX}#{id}" $projects << project $project_id_map[id] = project FileUtils.mkdir "#{project.path}/.ssh" FileUtils.touch "#{project.path}/.ssh/authorized_keys" FileUtils.touch "#{project.path}/.ssh/users" FileUtils.chown_R project.posixname, project.posixname, "#{project.path}/.ssh" project end def remove_project(project) system("userdel -r #{project.posixname}") $projects.delete(project) $project_id_map[id] = nil end def project_by_posix_name(posix_name) project = Project.new project.id = posix_name[POSIX_NAME_PREFIX.length..-1] project.path = "/home/#{posix_name}" project.posixname = posix_name project end #################### # Key manipulation # #################### class SSHKey attr_accessor :cipher, :pubkey, :comment def initialize(cipher,pubkey,comment="") @cipher = cipher @pubkey = pubkey @comment = comment end def ==(o) o.class == self.class && o.attrs == attrs end def pretty "#{@cipher} #{@comment}" end def to_s "ssh-#{@cipher} #{@pubkey} #{@comment}" end def attrs [@cipher, @pubkey, @comment] end end class InvalidSSHPubKey < StandardError end class DuplicateSSHPubKey < StandardError end def get_ssh_keyfile(project,mode,&block) file = File.open("#{project.path}/.ssh/authorized_keys",mode) return file unless block_given? yield(file) ensure file.close end def get_ssh_pubkeys(project) extract_ssh_pubkeys("#{project.path}/.ssh/authorized_keys") end def valid_pubkey?(key) IO.popen("ssh-keygen -qlf -","r+") do |io| io.write key.to_s io.close_write io.read end if $?.to_i == 0 true else false end end def extract_ssh_pubkeys(file) File.open(file,"r") do |f| f.read.split("\n").select{ |line| line[0..2] == "ssh" }.map{ |ks| k = ks.split(" "); SSHKey.new(k[0][4..6], k[1], k[2]) } end end ################ ########### # Project DB # ########### def reload_projects_from_passwd $projects = [] `cut -d: -f1 /etc/passwd`.split("\n"). select{|u| u.start_with? POSIX_NAME_PREFIX}. map{|u| project_by_posix_name(u)}. each{|u| add_project(u)} $projects.each{|p| p.refresh} end def reload_users_from_dir `ls -1 #{USER_LOCATION}`.split("\n"). map{|u| User.new(u)}. each{|u| add_user(u.id)} end def flush_all_to_disk $users.each {|u| u.flush} $projects.each {|p| p.flush} end def flush_projects_with_user(user) $projects.select{|p| p.users.include? user}.each do |p| p.flush end end ################################################ reload_users_from_dir reload_projects_from_passwd #remove_project 5 #create_project 3 #add_ssh_pubkey(get_project(3), "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7ZKN2fKcUQUaQqDjNCQQBSdqBVGX7lprNCJmceDBfybhbnuZJ8KvzJJIJIIbzqheW5BVCfkWJY6OgkpAumLWSRCS5n2+AnDHwQgpKDS93OeV+9/kattVtsVUBZaghymyJ2UfA0r918dkxcT9SZbNSl9raiDUUmj3JY8UM219BQP7BRqoZ6e/YZz9lO7ORy6yQT6fIMaVOaZcoDPr6oyNJfadm9POvS/Wl63onoRI9dzpHQG9RuHCcUhHJhkGtzY7GeRWc85WqA9Q4vYo0SK5Je9BG1cvAAVTfV+eYEJEiSDMwWj60roH0C3/ipmzxD/kWqg6YBJWL+XAyQkDnmbuD christoffermadsen@strawberry.thedevcave.net")