Created
November 21, 2011 21:30
-
-
Save chicks/1384007 to your computer and use it in GitHub Desktop.
Net::SSH Password Management
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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