diff --git a/README.md b/README.md index 6ef2b3c..ddac83b 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ shell2http HTTP-server to execute shell commands. Designed for development, prototyping or remote control. Settings through two command line arguments, path and shell command. -By default bind to :8080. Usage ----- @@ -21,6 +20,7 @@ Usage -host="host" : host IP for http server (default bind to all interfaces) -port=NNNN : port for http server, 0 - to receive a random port (default 8080) -form : parse query into environment vars, handle uploaded files + -form-check : regexp for check form fields (pass only vars that match the regexp) -cgi : run scripts in CGI-mode: - set environment variables with HTTP-request information - write POST|PUT|PATCH-data to script STDIN (if is not set -form) @@ -50,6 +50,11 @@ In the `-form` mode, variables are available for shell scripts: * $filepath_ID -- uploaded file path, ID - id from ``, temporary uploaded file will be automatically deleted * $filename_ID -- uploaded file name from browser +With `-form-check` option you can specify the regular expression for checking the form fields. +For example, if you want to allow only variables that contain the only digits, +you can specify the following option: `-form-check='^[0-9]+$'`. +Then only requests like `http://localhost:8080/path?NNN=123` will be produce variable `$v_NNN`. + To setup multiple auth users, you can specify the `-basic-auth` option multiple times. The credentials for basic authentication may also be provided via the `SH_BASIC_AUTH` environment variable. You can specify the preferred HTTP-method (via `METHOD:` prefix for path): `shell2http GET:/date date` diff --git a/config.go b/config.go index b7a0c52..70a40ab 100644 --- a/config.go +++ b/config.go @@ -7,6 +7,7 @@ import ( "net" "os" "os/exec" + "regexp" "runtime" "strconv" "strings" @@ -47,25 +48,26 @@ func (au authUsers) isAllow(user, pass string) bool { // Config - config struct type Config struct { - port int // server port - cache int // caching command out (in seconds) - timeout int // timeout for shell command (in seconds) - host string // server host - exportVars string // list of environment vars for export to script - shell string // custom shell - defaultShell string // shell by default - defaultShOpt string // shell option for one-liner (-c or /C) - cert string // SSL certificate - key string // SSL private key path - auth authUsers // basic authentication - exportAllVars bool // export all current environment vars - setCGI bool // set CGI variables - setForm bool // parse form from URL - noIndex bool // don't generate index page - addExit bool // add /exit command - oneThread bool // run each shell commands in one thread - showErrors bool // returns the standard output even if the command exits with a non-zero exit code - includeStderr bool // also returns output written to stderr (default is stdout only) + port int // server port + cache int // caching command out (in seconds) + timeout int // timeout for shell command (in seconds) + host string // server host + exportVars string // list of environment vars for export to script + shell string // custom shell + defaultShell string // shell by default + defaultShOpt string // shell option for one-liner (-c or /C) + cert string // SSL certificate + key string // SSL private key path + auth authUsers // basic authentication + exportAllVars bool // export all current environment vars + setCGI bool // set CGI variables + setForm bool // parse form from URL + noIndex bool // don't generate index page + addExit bool // add /exit command + oneThread bool // run each shell commands in one thread + showErrors bool // returns the standard output even if the command exits with a non-zero exit code + includeStderr bool // also returns output written to stderr (default is stdout only) + formCheckRe *regexp.Regexp // regexp for check form fields } // getConfig - parse arguments @@ -105,6 +107,8 @@ func getConfig() (*Config, error) { flag.Var(&cfg.auth, "basic-auth", "setup HTTP Basic Authentication (\"user_name:password\"), can be used several times") flag.IntVar(&cfg.timeout, "timeout", 0, "set `timeout` for execute shell command (in seconds)") + formCheck := flag.String("form-check", "", "regexp for check form fields (pass only vars that match the regexp)") + flag.Usage = func() { fmt.Printf("usage: %s [options] /path \"shell command\" /path2 \"shell command2\"\n", os.Args[0]) flag.PrintDefaults() @@ -149,6 +153,14 @@ func getConfig() (*Config, error) { } } + if formCheck != nil && len(*formCheck) > 0 { + re, err := regexp.Compile(*formCheck) + if err != nil { + return nil, fmt.Errorf("an error has occurred while compiling regexp %s: %s", *formCheck, err) + } + cfg.formCheckRe = re + } + return &cfg, nil } diff --git a/shell2http.1 b/shell2http.1 index 0ae31b2..3fd79b2 100644 --- a/shell2http.1 +++ b/shell2http.1 @@ -1,8 +1,8 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "SHELL2HTTP" "" "October 2021" "" "" -HTTP\-server to execute shell commands\. Designed for development, prototyping or remote control\. Settings through two command line arguments, path and shell command\. By default bind to :8080\. +.TH "SHELL2HTTP" "" "February 2023" "" "" +HTTP\-server to execute shell commands\. Designed for development, prototyping or remote control\. Settings through two command line arguments, path and shell command\. . .SH "Usage" . @@ -13,6 +13,7 @@ options: \-host="host" : host IP for http server (default bind to all interfaces) \-port=NNNN : port for http server, 0 \- to receive a random port (default 8080) \-form : parse query into environment vars, handle uploaded files + \-form\-check : regexp for check form fields (pass only vars that match the regexp) \-cgi : run scripts in CGI\-mode: \- set environment variables with HTTP\-request information \- write POST|PUT|PATCH\-data to script STDIN (if is not set \-form) @@ -53,6 +54,9 @@ $filename_ID \-\- uploaded file name from browser .IP "" 0 . .P +With \fB\-form\-check\fR option you can specify the regular expression for checking the form fields\. For example, if you want to allow only variables that contain the only digits, you can specify the following option: \fB\-form\-check=\'^[0\-9]+$\'\fR\. Then only requests like \fBhttp://localhost:8080/path?NNN=123\fR will be produce variable \fB$v_NNN\fR\. +. +.P To setup multiple auth users, you can specify the \fB\-basic\-auth\fR option multiple times\. The credentials for basic authentication may also be provided via the \fBSH_BASIC_AUTH\fR environment variable\. You can specify the preferred HTTP\-method (via \fBMETHOD:\fR prefix for path): \fBshell2http GET:/date date\fR . .SH "Install" diff --git a/shell2http.go b/shell2http.go index 5a4cd14..f1106d2 100644 --- a/shell2http.go +++ b/shell2http.go @@ -205,7 +205,7 @@ func execShellCommand(appConfig Config, shell string, params []string, req *http finalizer := func() {} if appConfig.setForm { var err error - if finalizer, err = getForm(osExecCommand, req); err != nil { + if finalizer, err = getForm(osExecCommand, req, appConfig.formCheckRe); err != nil { log.Printf("parse form failed: %s", err) } } @@ -420,7 +420,7 @@ func parseCGIHeaders(shellOut string) (string, map[string]string) { } // getForm - parse form into environment vars, also handle uploaded files -func getForm(cmd *exec.Cmd, req *http.Request) (func(), error) { +func getForm(cmd *exec.Cmd, req *http.Request, checkFormRe *regexp.Regexp) (func(), error) { tempDir := "" safeFileNameRe := regexp.MustCompile(`[^\.\w\-]+`) finalizer := func() { @@ -441,8 +441,22 @@ func getForm(cmd *exec.Cmd, req *http.Request) (func(), error) { } } - for key, value := range req.Form { - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", "v_"+key, strings.Join(value, ","))) + for key, values := range req.Form { + if checkFormRe != nil { + checkedValues := []string{} + for _, v := range values { + if checkFormRe.MatchString(v) { + checkedValues = append(checkedValues, v) + } + } + values = checkedValues + } + if len(values) == 0 { + continue + } + + value := strings.Join(values, ",") + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", "v_"+key, value)) } // handle uploaded files, save all to temporary files and set variables filename_XXX, filepath_XXX