Skip to content

Instantly share code, notes, and snippets.

@chicks
Created November 21, 2011 21:30
Show Gist options
  • Save chicks/1384007 to your computer and use it in GitHub Desktop.
Save chicks/1384007 to your computer and use it in GitHub Desktop.
Net::SSH Password Management
require 'net/ssh'
module AccountAdapters
class AdapterHelper
def self.ssh_version(system)
result = false
begin
sshSocket = TCPSocket::new(system.ip_address, 22)
r,w,e = IO.select([sshSocket], nil, nil, 5)
result = r[0].gets()
sshSocket.close
rescue Errno::EHOSTUNREACH
result = "No Route to Host"
rescue Errno::ETIMEDOUT
result = "Connection Timed Out"
rescue Errno::ECONNREFUSED
result = "Connection Refused"
rescue Errno::ECONNRESET
result = "Connection Reset"
end
return result
end
def self.adapter?(os)
adapter = case os
when "Solaris": "SolarisAdapter"
when "Linux" : "LinuxAdapter"
else "Unsupported"
end
return adapter
end
def self.ssh_version_ok?(ssh_version)
supported = case ssh_version
when /SSH-2.0-Sun_SSH_1.0.1/ : false
when /SSH-2.0-Sun_SSH_1.0/ : false
when /SSH-1.99-OpenSSH_4.1/ : false
else true
end
end
end
class AccountAdapter
def users
end
def add_user
end
def remove_user
end
def enable_user
end
def disable_user
end
def update_user
end
def update_password
end
def fetch_user
end
def log(message)
timestamp = Time.new.strftime("%Y-%m-%dT%H:%M:%S")
log_message = timestamp << ": " << message
@log.puts(log_message)
Rails.logger.info log_message
end
def ping!
end
def protocol
end
def details
end
end
class PosixTelnetAdapter < AccountAdapter
def protocol
"Telnet"
end
end
class PosixSshAdapter < AccountAdapter
attr_reader :ssh_version
def initialize(system)
@errors = Array.new
@log = File.open("log/#{self.class}.log", "a")
# Build an SSH connection
@system = system
begin
@shell = Net::SSH.start( system.ip_address, system.user, :password => system.password, :timeout => 2, :verbose => :info, :auth_methods => %w(password), :compression => false)
@ssh_version = @shell.transport.server_version.version
rescue Net::SSH::HostKeyMismatch
@errors << "SSH: Host Key Mismatch"
return false
rescue Net::SSH::AuthenticationFailed
@errors << "SSH: Login Failure"
return false
rescue Net::SSH::Disconnect
@errors << "SSH: Login Failure: Disconnect"
return false
rescue Errno::ECONNREFUSED
@errors << "SSH: Connection Refused"
return false
rescue Errno::ECONNRESET
@errors << "SSH: Connection Reset"
return false
rescue Exception
return false
end
end
def users
end
def fetch_user(user)
pwent = {}
pwent.merge!(grep_passwd(user))
pwent.merge!(grep_shadow(user))
if pwent.length > 4
return pwent
else
return false
end
end
def disable_user(user)
cmd = "passwd -l #{user.login}"
return false unless @shell
out = @shell.exec!(cmd)
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{out}"
return out
end
def hostname
cmd = "uname -a"
return false unless @shell
out = @shell.exec!(cmd)
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{out}"
return out
end
def ping!
start = Time.now
cmd = "uptime"
return false unless @shell
out = @shell.exec!(cmd)
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{out}"
return (Time.now - start)
end
def protocol
"SSH"
end
def details
@ssh_version
end
private
def grep_shadow(user)
# track our success
success = false
# the command we want to run
cmd = "grep #{user.login} /etc/shadow"
pwent = {}
@shell.open_channel do |ch|
ch.request_pty
ch.exec cmd do |ch, cmd_sent|
abort "could not execute #{cmd}" unless cmd_sent
ch.on_data do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{data}"
if data =~ /^#{user.login}:/
# qchahic:crypted stuff:14190:0:99999:7:::
success = true
pw_elems = data.split(/:/)
pwent[:password] = pw_elems[1]
pwent[:last_change] = pw_elems[2]
pwent[:min_change] = pw_elems[3]
pwent[:max_change] = pw_elems[4]
pwent[:warn_change] = pw_elems[5]
pwent[:inactive] = pw_elems[6]
pwent[:expire] = pw_elems[7]
else
success = false
end
end
# Exit status
ch.on_request "exit-status" do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: Exit Status: #{data.read_long}"
end
end
end
@shell.loop
if success
return pwent
else
return success
end
end
def grep_passwd(user)
# track our success
success = false
# the command we want to run
cmd = "grep #{user.login} /etc/passwd"
pwent = {}
@shell.open_channel do |ch|
ch.request_pty
ch.exec cmd do |ch, cmd_sent|
abort "could not execute #{cmd}" unless cmd_sent
ch.on_data do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{data}"
if data =~ /^#{user.login}:/
# qchahic:x:501:502::/home/qchahic:/bin/bash
success = true
pw_elems = data.split(/:/)
pwent[:name] = pw_elems[0]
pwent[:uid] = pw_elems[2]
pwent[:gid] = pw_elems[3]
pwent[:gecos] = pw_elems[4]
pwent[:home_dir] = pw_elems[5]
pwent[:shell] = pw_elems[6]
else
success = false
end
end
# Exit status
ch.on_request "exit-status" do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: Exit Status: #{data.read_long}"
end
end
end
@shell.loop
if success
return pwent
else
return success
end
end
end
class LinuxAdapter < PosixSshAdapter
def add_user(user, password)
# track our success
success = false
# the command we want to run
cmd = "useradd -c \"#{user.name}\" #{user.login}"
# open a new ssh channel
@shell.open_channel do |ch|
# request a pty... otherwise some commands wont work
ch.request_pty
# send the command
ch.exec cmd do |ch, cmd_sent|
abort "could not execute #{cmd}" unless cmd_sent
# on_data = stdout
ch.on_data do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{data}"
# User exists already
if data =~ /#{user.login} is already in use. Choose another./
success = true
# Home directory already exists
elsif data =~ /useradd: warning: the home directory already exists./
success = true
else
success = true
end
end
# on_extended_data = stderr
ch.on_extended_data do |ch, type, data|
stderr << data
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDERR: #{data}"
success = false
end
# Exit status
ch.on_request "exit-status" do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: Exit Status: #{data.read_long}"
end
end
end
@shell.loop
# update password
success = false unless self.update_password(user, password)
return success
end
def remove_user(user)
cmd = "userdel #{user.login}"
out = @shell.exec!(cmd)
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{out}"
return true
end
def update_password(user, password)
# track our success
success = false
# the command we want to run
cmd = "passwd #{user.login}"
@shell.open_channel do |ch|
ch.request_pty
ch.exec cmd do |ch, cmd_sent|
abort "could not execute #{cmd}" unless cmd_sent
ch.on_data do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{data}"
if data =~ /assword: /
ch.send_data("#{password}\n")
success = true
end
end
# Exit status
ch.on_request "exit-status" do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: Exit Status: #{data.read_long}"
end
end
end
@shell.loop
return success
end
end
class SolarisAdapter < PosixSshAdapter
USERADD = "/usr/sbin/useradd"
def add_user(user, password)
log("#{@system.ip_address}: #{self.class}: Calling add_user")
# track our success
success = false
# the command we want to run
cmd = "#{USERADD} -c \"#{user.name}\" -d \"/export/home/#{user.login}\" #{user.login}"
# open a new ssh channel
@shell.open_channel do |ch|
# request a pty... otherwise some commands wont work
ch.request_pty
# send the command
ch.exec cmd do |ch, cmd_sent|
abort "could not execute #{cmd}" unless cmd_sent
# on_data = stdout
ch.on_data do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{data}"
# User exists already
if data =~ /#{user.login} is already in use. Choose another./
success = true
# Home directory already exists
elsif data =~ /useradd: warning: the home directory already exists./
success = true
else
success = true
end
end
# on_extended_data = stderr
ch.on_extended_data do |ch, type, data|
stderr << data
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDERR: #{data}"
success = false
end
# Exit status
ch.on_request "exit-status" do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: Exit Status: #{data.read_long}"
end
end
end
@shell.loop
# update password
success = false unless self.update_password(user, password)
# create our homedir
success = false unless create_homedir(user)
return success
end
def remove_user(user)
cmd = "userdel #{user.login}"
out = @shell.exec!(cmd)
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{out}"
return true
end
# TODO: Change Adapter methods to use username and passwords, instead of user objects!
#
def update_password(user, password)
# track our success
success = false
# the command we want to run
cmd = "passwd #{user.login}"
@shell.open_channel do |ch|
ch.request_pty
ch.exec cmd do |ch, cmd_sent|
abort "could not execute #{cmd}" unless cmd_sent
ch.on_data do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{data}"
if data =~ /assword: /
ch.send_data("#{password}\n")
success = true
end
end
# Exit status
ch.on_request "exit-status" do |ch, data|
log "#{@system.ip_address}: #{self.class}: #{cmd}: Exit Status: #{data.read_long}"
end
end
end
@shell.loop
return success
end
private
def create_homedir(user)
pwent = fetch_user user
return false unless pwent
home_dir = pwent[:home_dir]
uid = pwent[:uid]
gid = pwent[:gid]
success = false
# the command we want to run
cmds = ["mkdir -p #{home_dir}", "chown -R #{uid}:#{gid} #{home_dir}"]
cmds.each do |cmd|
out = @shell.exec!(cmd)
log "#{@system.ip_address}: #{self.class}: #{cmd}: STDOUT: #{out}"
end
return true
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment