Missing Execution

Okay, there’s probably a lot of stuff in this code that can be improved, and I am willing to listen. The reason I’m here though is because this program in its current state doesn’t execute anything and I feel it should:

require "option_parser"

string = ""
passname = ""
things = ""
uname = ""
ip = ""

option_parser = OptionParser.parse do |parser|
  parser.banner = "Awesomeness 1.0"

  parser.on "-v", "--version", "Show version" do
    puts "version 1.0"
    exit
  end
  parser.on "-h", "--help", "Show help" do
    puts parser
    exit
  end
  parser.on "-d ENCRYPTED_FILE", "--dcrypt ENCRYPTED_FILE", "Decrypt and user encrypted file with ccrypt" do |dcrypt|
    dname = dcrypt
  end
  parser.on "-w PASSWORD", "--pass PASSWORD", "Password if SSH keys are not used" do |pass|
    passname = pass
  end 
  parser.on "-p PORT", "--port PORT", "Port for SSH if not default" do |port|
    pname = port
  end
  parser.on "-u USER", "--user USER", "Username" do |user|
    uname = user
  end
  parser.on "-l HOST", "--host HOST", "Host or IP" do |host|
    ip = host
  end  
  parser.on "-f FILENAME", "--file FILENAME", "File of hosts" do |file|
    hostfile = file
  end
  parser.on "-e COMMANDS", "--execute COMMANDS", "Commands to run" do |commands|
    things = commands
  end
end

if(passname.empty? != true)
  string = "sshpass -p \"#{passname}\" "
end

string += "ssh -o StrictHostChecking=no -T "

if(uname.empty? != true)
  string += "#{uname}@"
end

if(ip.empty? != true)
  string += "#{ip} "
end

if(things.empty? != true)
  string += "\"#{things}\""
end

unless things.empty?
  Process.run(string, shell: true) do |proc|
  IO.copy(proc.output, STDOUT)
 end
end

The reason I feel it “should” work, is that I see the output of the program display correctly when I run it through strace:

[pid 164083] 18:40:24 execve("/usr/bin/sshpass", ["sshpass", "-p", "password", "ssh", "-o", "StrictHostChecking=no", "-T", "user@192.168.x.x", "date"], ["SHELL=/bin/bash", "COLORTERM=truecolor",...

Program will need to be ran like this right now as I’ve added zero error checking so far.

./program -u username -l 192.168.x.x -w "password" -e date

I don’t see anything needing to be escaped in the strace output, but perhaps it needs more escaping? Any advice appreciated.

Some tips:

  1. String should be the process you’re executing, like ssh in this example.
  2. You should use an array of strings to hold the arguments. I.e the second argument to Process.run.
  3. You can just set the output of Process.run to be STDOUT.
  4. You can just do like string += "#{uname}@" unless uname.empty? (but using an array). No need for uname.empty? != true .

Some sample code:

command = "ssh"
args = ["-o", "StrictHostChecking=no", "-T"]

args.push "sshpass", "-p", passname unless passname.empty?

result = Process.run command, args, output: STDOUT

I’m not sure if this’ll solve your issue or not, but should at least make it a bit cleaner and easier to figure out.

@Blacksmoke16 With shell: true you can pass the command and all arguments in the first argument. So points 1 and 2 of what you said don’t apply in this case.

@glued2thefloor, unrelated to your problem, but you may be interested in the 'Percent string literals` https://crystal-lang.org/reference/syntax_and_semantics/literals/string.html that would allow you to not have to escape quotes:

things = "cats"
"\"#{things}\"" == %<"#{things}"> 

https://play.crystal-lang.org/#/r/95hj

Also, I believe system command automatically inherits stderr and stdout from your current process.
https://crystal-lang.org/api/0.34.0/toplevel.html#system(command:String,args=nil):Bool-class-method

@HankAnderson - Thank you. I was thinking shell: true was required. As I’m new to Crystal I wasn’t 100% positive though.

@Blacksmoke16 - For point #4, I should have stated this, but I’m trying to port some code from another project I did in C. Some of those can be fixed like you said, but some will have error checking added later. Obviously someone can’t ssh somewhere with no host specified. I was going to print out an error message later if the some of the results were null and figured an if statement would be the best way to do this. Maybe I’m wrong though. I did like the way you used unless here. Also, I did try your suggestion with STDOUT:

require “option_parser”

passname = ""
things = ""
uname = ""
ip = ""

option_parser = OptionParser.parse do |parser|
  parser.banner = "Awesomeness 1.0"

  parser.on "-v", "--version", "Show version" do
    puts "version 1.0"
    exit
  end
  parser.on "-h", "--help", "Show help" do
    puts parser
    exit
  end
  parser.on "-d ENCRYPTED_FILE", "--dcrypt ENCRYPTED_FILE", "Decrypt and user encrypted file with ccrypt" do |dcrypt|
    dname = dcrypt
  end
  parser.on "-w PASSWORD", "--pass PASSWORD", "Password if SSH keys are not used" do |pass|
    passname = pass
  end 
  parser.on "-p PORT", "--port PORT", "Port for SSH if not default" do |port|
    pname = port
  end
  parser.on "-u USER", "--user USER", "Username" do |user|
    uname = user
  end
  parser.on "-l HOST", "--host HOST", "Host or IP" do |host|
    ip = host
  end  
  parser.on "-f FILENAME", "--file FILENAME", "File of hosts" do |file|
    hostfile = file
  end
  parser.on "-e COMMANDS", "--execute COMMANDS", "Commands to run" do |commands|
    things = commands
  end
end

command = "ssh"
args = ["-o", "StrictHostChecking=no", "-T", "user@192.168.x.x", "date"]
args.push "sshpass", "-p", passname unless passname.empty?
result = Process.run command, args, output: STDOUT

I then ran it like so:
./program -w "password"
but received no output.

Looking at this in strace again it seems the commands to be executed were out of order:
[pid 278978] 02:24:30 execve("/usr/bin/ssh", ["/usr/bin/ssh", "-o", "StrictHostChecking=no", "-T", "ph33r@192.168.x.x", "date", "sshpass", "-p", "password"], ["SHELL=/bin/bash", "COLORTERM=truecolor"...

Any thoughts?

@ducktape - Thank you. I will look into this later. Good find.

Try also outputting errors, add a , error: STDERR to the Process.run. As currently any errors would be silenced.

Well, not sure if I’m doing this right, but I just changed the last lines of the program to this:

command = "ssh"
args = ["-o", "StrictHostChecking=no", "-T", "user@192.168.x.x", "date"]
args.push "sshpass", "-p", passname unless passname.empty?
result = Process.run command, args, output: STDERR

I executed this again with the password and got no output again. Maybe I put STDERR in the wrong place?

Now you’ve set STDOUT to STDERR. You want to do result = Process.run command, args, output: STDOUT, error: STDERR. So that normal output goes to STDOUT and error output goes to STDERR. I’m pretty sure these should be the default tho… I don’t remember.

Default behavior is :close. You can use output: :inherit etc. instead of having to do it explicitly.

1 Like