Bad result from groovy's ProcessGroovyMethods (UNIXProcess) - grails

While using Grails 2.4.5 org.codehaus.groovy.runtime.ProcessGroovyMethods on Ubuntu 14.04:
def command = "mysqldump -h${databaseProperties.host} -u'${databaseProperties.username}' -p'${databaseProperties.password}' ${databaseProperties.name} " + table
print command
def proc = command.execute()
def oneMinute = 60000
proc.waitForOrKill(oneMinute)
if(proc.exitValue()!=0){
println "[[return code: ${proc.exitValue()}]]"
println "[[stderr: ${proc.err.text}]]"
return null
}else{
return proc.in.text.readLines()
}
I've got
[[return code: 2]]
[[stderr: mysqldump: Got error: 1045: Access denied for user 'root'#'localhost' (using password: YES) when trying to connect]]
but when I copy-paste printlined command into my bash I receive proper dump. What is going on?
I've also tried:
changing mysqldump to full path: /usr/bin/mysqldump
sending arguments as a String array but with the same
result.
sending command as a regular String to execute:
"mysqldump -hlocalhost -u'root' -p'password' database table"
it works in system bash, it doesn't as a ProcessGroovyMethod...

Update:
After thinking about this overnight, I'm (still) convinced that the problem is related to your password. Since it's really not a best practice to provide the password on the command line (mysqldump even warns you about this), I think you should change tactics by creating a login-path.
Use the following command to create a login path (this is a one-time step):
mysql_config_editor set --login-path=name --host=localhost --user=youruser --password
Then change the command you're attempting to execute from Groovy to this:
def command="mysqldump --login-path=name database table"
This will work around the issue you're seeing and is more secure.
Original answer:
I was able to replicate the problem. String.execute() doesn't use a command shell, and therefore the single quotes are getting passed to mysqldump as if they were part of your password.
Edit: After some further thought, I don't think Groovy's String.execute() is the way to go here, because of its unexpected handling of quotes. It's fine if your password doesn't include spaces, but this is likely to be brittle.
If you need more control, you should consider using ProcessBuilder:
ProcessBuilder pb = new ProcessBuilder("mysqldump", "-h${databaseProperties.host}", "-u${databaseProperties.username}", "-p${databaseProperties.password}", databaseProperties.name, table);
pb.inheritIO();
Process p = pb.start();
Edit: Further research, just tested this with a password that includes spaces. command.execute() doesn't handle this properly, but using the ProcessBuilder method works.
Here's another post explaining some of the unexpected behavior of the String.execute() method:
Groovy: strings with embedded quotes don't execute as expected

Related

Validate URL in Informix 4GL program

In my Informix 4GL program, I have an input field where the user can insert a URL and the feed is later being sent over to the web via a script.
How can I validate the URL at the time of input, to ensure that it's a live link? Can I make a call and see if I get back any errors?
I4GL checking the URL
There is no built-in function to do that (URLs didn't exist when I4GL was invented, amongst other things).
If you can devise a C method to do that, you can arrange to call that method through the C interface. You'll write the method in native C, and then write an I4GL-callable C interface function using the normal rules. When you build the program with I4GL c-code, you'll link the extra C functions too. If you build the program with I4GL-RDS (p-code), you'll need to build a custom runner with the extra function(s) exposed. All of this is standard technique for I4GL.
In general terms, the C interface code you'll need will look vaguely like this:
#include <fglsys.h>
// Standard interface for I4GL-callable C functions
extern int i4gl_validate_url(int nargs);
// Using obsolescent interface functions
int i4gl_validate_url(int nargs)
{
if (nargs != 1)
fgl_fatal(__FILE__, __LINE__, -1318);
char url[4096];
popstring(url, sizeof(url));
int r = validate_url(url); // Your C function
retint(r);
return 1;
}
You can and should check the manuals but that code, using the 'old style' function names, should compile correctly. The code can be called in I4GL like this:
DEFINE url CHAR(256)
DEFINE rc INTEGER
LET url = "http://www.google.com/"
LET rc = i4gl_validate_url(url)
IF rc != 0 THEN
ERROR "Invalid URL"
ELSE
MESSAGE "URL is OK"
END IF
Or along those general lines. Exactly what values you return depends on your decisions about how to return a status from validate_url(). If need so be, you can return multiple values from the interface function (e.g. error number and text of error message). Etc. This is about the simplest possible design for calling some C code to validate a URL from within an I4GL program.
Modern C interface functions
The function names in the interface library were all changed in the mid-00's, though the old names still exist as macros. The old names were:
popstring(char *buffer, int buflen)
retint(int retval)
fgl_fatal(const char *file, int line, int errnum)
You can find the revised documentation at IBM Informix 4GL v7.50.xC3: Publication library in PDF in the 4GL Reference Manual, and you need Appendix C "Using C with IBM Informix 4GL".
The new names start ibm_lib4gl_:
ibm_libi4gl_popMInt()
ibm_libi4gl_popString()
As to the error reporting function, there is one — it exists — but I don't have access to documentation for it any more. It'll be in the fglsys.h header. It takes an error number as one argument; there's the file name and a line number as the other arguments. And it will, presumably, be ibm_lib4gl_… and there'll be probably be Fatal or perhaps fatal (or maybe Err or err) in the rest of the name.
I4GL running a script that checks the URL
Wouldn't it be easier to write a shell script to get the status code? That might work if I can return the status code or any existing results back to the program into a variable? Can I do that?
Quite possibly. If you want the contents of the URL as a string, though, you'll might end up wanting to call C. It is certainly worth thinking about whether calling a shell script from within I4GL is doable. If so, it will be a lot simpler (RUN "script", IIRC, where the literal string would probably be replaced by a built-up string containing the command and the URL). I believe there are file I/O functions in I4GL now, too, so if you can get the script to write a file (trivial), you can read the data from the file without needing custom C. For a long time, you needed custom C to do that.
I just need to validate the URL before storing it into the database. I was thinking about:
#!/bin/bash
read -p "URL to check: " url
if curl --output /dev/null --silent --head --fail "$url"; then
printf '%s\n' "$url exist"
else
printf '%s\n' "$url does not exist"
fi
but I just need the output instead of /dev/null to be into a variable. I believe the only option is to dump the output into a temp file and read from there.
Instead of having I4GL run the code to validate the URL, have I4GL run a script to validate the URL. Use the exit status of the script and dump the output of curl into /dev/null.
FUNCTION check_url(url)
DEFINE url VARCHAR(255)
DEFINE command_line VARCHAR(255)
DEFINE exit_status INTEGER
LET command_line = "check_url ", url
RUN command_line RETURNING exit_status
RETURN exit_status
END FUNCTION {check_url}
Your calling code can analyze exit_status to see whether it worked. A value of 0 indicates success; non-zero indicates a problem of some sort, which can be deemed 'URL does not work'.
Make sure the check_url script (a) exits with status zero on success and non-zero on any sort of failure, and (b) doesn't write anything to standard output (or standard error) by default. The writing to standard error or output will screw up screen layouts, etc, and you do not want that. (You can obviously have options to the script that enable standard output, or you can invoke the script with options to suppress standard output and standard error, or redirect the outputs to /dev/null; however, when used by the I4GL program, it should be silent.)
Your 'script' (check_url) could be as simple as:
#!/bin/bash
exec curl --output /dev/null --silent --head --fail "${1:-http://www.example.com/"
This passes the first argument to curl, or the non-existent example.com URL if no argument is given, and replaces itself with curl, which generates a zero/non-zero exit status as required. You might add 2>/dev/null to the end of the command line to ensure that error messages are not seen. (Note that it will be hell debugging this if anything goes wrong; make sure you've got provision for debugging.)
The exec is a minor optimization; you could omit it with almost no difference in result. (I could devise a scheme that would probably spot the difference; it involves signalling the curl process, though — kill -9 9999 or similar, where the 9999 is the PID of the curl process — and isn't of practical significance.)
Given that the script is just one line of code that invokes another program, it would be possible to embed all that in the I4GL program. However, having an external shell script (or Perl script, or …) has merits of flexibility; you can edit it to log attempts, for example, without changing the I4GL code at all. One more file to distribute, but better flexibility — keep a separate script, even though it could all be embedded in the I4GL.
As Jonathan said "URLs didn't exist when I4GL was invented, amongst other things". What you will find is that the products that have grown to superceed Informix-4gl such as FourJs Genero will cater for new technologies and other things invented after I4GL.
Using FourJs Genero, the code below will do what you are after using the Informix 4gl syntax you are familiar with
IMPORT com
MAIN
-- Should succeed and display 1
DISPLAY validate_url("http://www.google.com")
DISPLAY validate_url("http://www.4js.com/online_documentation/fjs-fgl-manual-html/index.html#c_fgl_nf.html") -- link to some of the features added to I4GL by Genero
-- Should fail and display 0
DISPLAY validate_url("http://www.google.com/testing")
DISPLAY validate_url("http://www.google2.com")
END MAIN
FUNCTION validate_url(url)
DEFINE url STRING
DEFINE req com.HttpRequest
DEFINE resp com.HttpResponse
-- Returns TRUE if http request to a URL returns 200
TRY
LET req = com.HttpRequest.create(url)
CALL req.doRequest()
LET resp = req.getResponse()
IF resp.getStatusCode() = 200 THEN
RETURN TRUE
END IF
-- May want to handle other HTTP status codes
CATCH
-- May want to capture case if not connected to internet etc
END TRY
RETURN FALSE
END FUNCTION

SQLPlus: using special character for password in script

I have issue with special character in password - '#'.
Issue that i don't know how to pass password with this special character to script file and connect using sqlplus.
For example i have user 'test_user' with password 'temp123#'. For login via sqlplus i use following string with escaping: sqlplus test_user/\"temp123#\"#some-db:1521/SID
It's works well. For my case i need to pass password as parameter for sql script. For example, i have following script to connect (actually script using several pairs of login/password to update many users at once):
-- #deploy.sql
connect &&2./&&3.#&&1.
At have failure with that because password with '#' can't interpreted correctly by sqlplus. I tried many advices from google with escaping but mostly of them applicable for connect string at sqlplus invoke. For my case its required for parameter that passed to script.
use : cmd.exe and not powershell window
if not :
in powershell windows : sqlplus 'login/\"P#$$w0rd\"#TNS_NAME'
in cmde.exe : sqlplus login/\"P#$$w0rd\"#TNS_NAME
Just quotes required in connect string:
connect &&2./"&&3."#&&1.

Issues while using sqlplus with ruby

I want to use sqlplus within ruby. Dont want to use any gems[bec I cannot get it installed on our servers without much help from other teams ..etc] and want to keep it very minimal.
I am trying something as simple as this in my ruby script:
`rlwrap sqlplus user/pswd#host << EOF`
`set serveroutput on;`
`commit;` #ERROR1: sh: commit: not found
sql = "insert /*+ APPEND*/ INTO table(col1, col2) values (#{data[0]},#{data[1]});"
`#{sql}` #ERROR2: sh: Syntax error: "(" unexpected
Can anyone help me with ERROR1 and ERROR2 above
Basically for "commit: not found" I think its getting executed on shell rather than in sqlplus. However seems like "set serveroutput on" seems to execute fine !
For ERROR2, I am clueless. I also tried using escape slash for the "/" in the sql.
Thanks
The answer is, don't use SQL*Plus. Don't call a command-line utility from inside your script; between the ruby-oci8 gem and the ruby-plsql gem, you can do anything you could accomplish from within SQL*Plus.
The reason you get the errors is that you are sending each line to the shell individually. If your entire statement was wrapped in a single pair of backticks, it might work.
But if you really are unable to install the proper gems, put the commands in a temporary file and tell sqlplus to execute that, eg:
require 'tempfile'
file = Tempfile.open(['test', '.sql'])
file.puts "set serveroutput on;"
file.puts "commit;"
file.puts "insert /*+ APPEND*/ INTO table(col1, col2) values (#{data[0]},#{data[1]});"
file.puts "exit;" # needed or sqlplus will never return control to your script
file.close
output = `sqlplus user/pswd#host ##{file.path}`
file.unlink
You'll have to be very careful about:
Quoting values (if using oci8/dbi you could use bind variables)
Error handling. If using ruby libraries, errors would raise exceptions. Using sqlplus, you'll have to parse the output instead. Yuck!
So it can be done but I highly recommend you jump through whatever hoops are required to get oci8 (and maybe ruby-DBI) installed properly :)
ps are you sure you want to commit before the insert?

Requesting another server for file exist in ruby on rails

I have a condition where I need to check for the file in another server, if that file exists I need to delete from the current server. Can any body help me.
You can place a script on another server and ask it in restful way to perform that tasks for you:
http://another.server/exists/:file_name
http://another.server/delete/:file_name
but you will have to think about security aspects of this solution.
Also take a look on executing remote commands via ssh: http://bashcurescancer.com/run_remote_commands_with_ssh.html. Combined with using ssh "without password" it can be acceptable solution to run command line program that run what you need.
Just write a ruby script and do something along the line with:
require "open-uri"
file_name = "file.name"
begin
file = open("http://www.example.com/#{file_name}")
File.delete("path_to" + file_name)
p "File #{file_name} deleted"
rescue
p "File not found"
end

Strange variable-argument problem in Capistrano Task

Hello stackoverflow experts,
I got a very strange problem in a task I'm creating with Capistrano. I'm trying to pass a variable from the command line:
>> cap create_dir -s name_of_dir=mydir
task :create_dir do
printf("#{name_of_dir}")
if !(exists?(:name_of_dir)) then
name_of_dir = Capistrano::CLI.ui.ask("Name of dir to be created.")
end
full_path = "/home/#{name_of_dir}"
run "mkdir #{full_path}"
end
The very strange this is that correctly parses the variable when I do printf, but parses as a blank(empty) string in the following command. I really find no explanation for this and I'm sure is not a stuping typo or anything like that?
I'm not expierenced in Ruby like in Java and PHP, I'm affraid that there maybe a strange rule?
Thanks!!
A few suggestions:
Avoid using variables with the same name of internal task variables
use fetch() instead of dealing with if exits? else then...
Here's the code
>> cap create_dir -s name_of_dir=mydir
task :create_dir do
printf("#{name_of_dir}")
directory = fetch(:name_of_dir) { Capistrano::CLI.ui.ask("Name of dir to be created.") }
full_path = "/home/#{directory}"
run "mkdir #{full_path}"
end
In newer versions of capistrano, at least from 2.5.19 which I run now the whole command line argument thing works different now. You call it like this.
cap command argument=value
And the syntax in the code is
ENV.has_key?('argument') and ENV['argument']
That's basically it, but you can look at my blogpost about it for a working example
It looks like in the second line you are checking if the symbol :name_of_dir exists - not the actual value of the variable name_of_dir.
Because you're unlikely to have a filename name_of_dir it will count as not existing... and then name_of_dir (the variable) is overwritten by the Capistrano::CLI.ui.ask command.
Not sure why but that must be killing it somehow.
Try removing the ":" and seeing if that fixes the problem.

Resources