#!/usr/bin/ruby -w
#
# NAME
#   symbiosis-encrypt-password - encrypt a password
#
# SYNOPSIS
#   symbiosis-encrypt-password [--help] [ --manual ] [--verbose] [--alogrithm a] [--marker m] password_or_password_file
#
# OPTIONS
#  --algorithm a  Specify which alogrithm to use. This is specified by ID.
#                 Defaults to 6, i.e. SHA-512
#
#  --marker m     Specify what should be prepended to the output.
#                 Defaults to "{CRYPT}". Leave blank to specify nothing should be prepended.
#
#  --verbose      Give verbose output
#
#  --help         Show usage
#
#  --manual       Show full manual
#    
# DESCRIPTION
#
# Takes a password from standard input, as an argument, or the first line of a
# given file and encrypts it using the standard glibc crypt(3) format.
#
# The alogritms are specified by number, as per the table in crypt(3). For
# reference this is
#
#   1   MD5
#   5   SHA-256
#   6   SHA-512
#
# It outputs the encrypted password to standard out, prepended with the marker. 
#
# EXAMPLES
#
# symbiosis-encrypt-password "my password" 
#
# produces "{CRYPT}$1$M7$/ElErZcP9VN8fRgJfOKQa.".
#
# echo "another password" | symbiosis-encrypt-password --algorithm 5
#
# produces "{CRYPT}$5$v.$8nmtFKlaW5kkxkHCNjZLo0WR4b9hTJrOEmtBYOi9rb/".
#
# BUGS
#
# If your password has spaces in it, you must enclose it in inverted commas if
# you're putting it as an argument to the program.
#
# SEE ALSO
#
#  crypt(3)
#
# AUTHOR
#
# Patrick J. Cherry <patrick@bytemark.co.uk>
#

require 'getoptlong'

opts = GetoptLong.new(
    [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
    [ '--manual', '-m', GetoptLong::NO_ARGUMENT ],
    [ '--verbose', '-v', GetoptLong::NO_ARGUMENT ],
    [ '--algorithm', '-a', GetoptLong::REQUIRED_ARGUMENT ],
    [ '--marker', '-k', GetoptLong::OPTIONAL_ARGUMENT ]
)

manual = help = false 
$VERBOSE  = false
algorithm = "6"
marker    = "{CRYPT}"

opts.each do |opt,arg|
  case opt
    when '--help'
      help = true
    when '--manual'
      manual = true
    when '--verbose'
      $VERBOSE = true
    when '--marker'
      marker = arg.to_s
    when '--algorithm'
      algorithm = arg.to_s
  end
end

#
# Output help as required.
#
if help or manual
  require 'symbiosis/utils'
  Symbiosis::Utils.show_help(__FILE__) if help
  Symbiosis::Utils.show_manual(__FILE__) if manual
  exit 0
end

#
# This is acceptable salt.
#
SALT = [".", "/"] + [0..9, "A".."Z", "a".."z"].collect{|r| r.to_a}.flatten

#
# These are the algorithms
#
ALGORITHMS = {
  "1" => "MD5",
  "5" => "SHA-256",
  "6" => "SHA-512"
}

require 'cracklib'

password = nil

unless ALGORITHMS.has_key?(algorithm)
  warn "Invalid algorithm code #{algorithm.inspect}.  Defaulting to SHA-512."
  algorithm = "6"
end

if ARGV.length == 0
  warn "Reading from standard input" if $VERBOSE
  # 
  # Read the password in from STDIN
  #
  password = STDIN.gets.chomp 
elsif File.exist?(ARGV.first)
  #
  # Read the password from a file
  #
  warn "Reading from #{ARGV.first}." if $VERBOSE
  password = File.open(ARGV.first){|fh| fh.gets}

  #
  # If the password is nil, use the ARGV instead.
  #
  if password.nil?
    warn "No password found in #{ARGV.first}."
    password = ARGV.first 
  else
    #
    # Remove any stray line ending
    #
    password.chomp!
  end
else
  # 
  # Use the ARGV
  #
  warn "Using password from the command line" if $VERBOSE
  password = ARGV.first
end

c = CrackLib::Fascist(password)
warn "This is a weak password -- #{c.reason}." unless c.ok?

# 
# Collect some salt.
#
salt = 8.times.collect{SALT[rand(SALT.length)]}.join

warn "Encrypting password #{password.inspect} using the #{ALGORITHMS[algorithm]} algorithm and #{salt.inspect} as salt." if $VERBOSE

# And encrypt and output.
puts marker+password.chomp.crypt("$#{algorithm}$#{salt}$")


