Project

General

Profile

[Solved] allowing a cgi to communicate with an external program

Added by jiml8 over 2 years ago

In an embedded environment based on OpenWRT 20.something (latest...) using lighttpd v1.4.59, I am writing a cgi in C that has to communicate with an external controller.

There is an api command /sbin/scmd that is available to communicate with this controller, and the return of scmd is the information I need.

I have attempted to popen scmd with the relevant command, and the controller sees the communication, and therefore replies, but the return that I get in the cgi code is an empty string, which should not be. When I run the cgi code from the console, the communication occurs properly.

Not to be denied, I modified the scmd routine so that it would not return the data I wanted, but would get it and write it into a file called /tmp/rtn. I will note that the permissions on /tmp are 777.

Again, the controller sees the communication and therefore replies. But when the modified scmd writes to /tmp, what I get is an empty file.

Well, the scmd utility is for convenience; the controller talks on a unix socket. So I migrated the scmd code into the cgi program and directly access the controller by connecting to the socket. When I do this, the connection occurs, the request for data is sent and received, and the controller responds...but the response does not reach the cgi program, which errors out after waiting for a response that never arrives.

Clearly, lighttpd is sandboxing this cgi. And I am sure there is a way around it, but I have not figured it out.

The lighttpd.conf file is:

server.document-root        = "/www" 
server.upload-dirs          = ( "/tmp" )
#server.errorlog             = "/var/log/error.log" 
server.pid-file             = "/var/run/lighttpd.pid" 
server.username             = "www" 
server.groupname            = "www" 
index-file.names            = ( "index.php", "index.html",
                                "index.htm", "default.htm",
                              )
static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" )
### Features
#https://redmine.lighttpd.net/projects/lighttpd/wiki/Server_feature-flagsDetails
server.feature-flags       += ("server.graceful-shutdown-timeout" => 5)
#server.feature-flags       += ("server.graceful-restart-bg" => "enable")
### Options that are useful but not always necessary:
#server.chroot               = "/" 
server.port                 = 80
server.bind                 = "100.69.69.1" 
#server.tag                  = "lighttpd" 
#server.errorlog-use-syslog  = "enable" 
#server.network-backend      = "writev" 
### Use IPv6 if available
#include_shell "/usr/share/lighttpd/use-ipv6.pl" 
#dir-listing.encoding        = "utf-8" 
#server.dir-listing          = "enable" 
include "/etc/lighttpd/mime.conf" 
include "/etc/lighttpd/conf.d/*.conf" 

modules in use are cgi, openssl, and setenv. I added setenv hoping to fix this problem.


Replies (10)

RE: allowing a cgi to communicate with an external program - Added by jiml8 over 2 years ago

I forgot to ask the question, and I don't see how to edit my post. So the question; can anyone tell me what I am missing?

RE: allowing a cgi to communicate with an external program - Added by gstrauss over 2 years ago

Try creating a simple CGI program and verify that it works with lighttpd.

#!/bin/sh
printf "Status: 200\n\nHello World!\n" 

This will likely show you that lighttpd CGI is working, and that the issue is with your specific program, which might work only with a controlling terminal, rather than a pipe. CGI is created with pipes to stdin and stdout of the CGI program. You'll probably find that accessing your /sbin/scmd in a shell pipeline also fails. If your external controller requires a controlling terminal, you'll have to code that into your C program.

RE: allowing a cgi to communicate with an external program - Added by jiml8 over 2 years ago

/sbin/scmd is accessed via pipe from a number of C functions in this system; it works via pipe without issue. Even if that was not the case, I did replace the pipeline call with a direct socket connect, and again failed to get a return. The direct socket connect, BTW, has been slated to replace the pipeline calls for a long time; I just had not gotten around to doing it. Now I will make that change.

Also, my cgi code does successfully write the HTML page it is supposed to write (just missing the data that it is supposed to get from the controller), and I am receiving POST information into the cgi when the user fills out the presented form and presses the "Save" button. Doing the required Save will involve contacting the controller again, and my inability to receive responses from the controller will be a problem.

All the code works from the console, and all the code works when invoked from various daemons in the system. There has to be some environmental issue involving lighttpd that I don't have figured out. The obvious thing is that the daemons all run as root:root while lighttpd runs as www:www. I tried to run lighttpd as root:root to see if that dealt with the issue, but lighttpd refused to do that.

RE: allowing a cgi to communicate with an external program - Added by gstrauss over 2 years ago

I tried to run lighttpd as root:root to see if that dealt with the issue, but lighttpd refused to do that.

lighttpd runs as the user it is started as, unless you specify server.username. lighttpd refuses to run as a setuid binary.

RE: allowing a cgi to communicate with an external program - Added by gstrauss over 2 years ago

If your device only works when the command is run as root, then you might create your own setuid binary that is callable from the CGI script, and your setuid binary should check environment for security and then perform a specific and precise command using elevated privileges, returning the output to the CGI.

RE: allowing a cgi to communicate with an external program - Added by jiml8 over 2 years ago

Hmmm...that's an idea. I think I will give that a try over the weekend.

I was also considering forking the cgi away from the webserver environment to get an independent process to handle controller communications. Two forks, with a setsid() in the middle, should do it given that the child won't inherit most of the parent's environment. This would let me collect the data I need and write it to /tmp, then read it in from /tmp into the webserver environment after it was collected. It would also let me direct the controller when processing the Save button.

Rather a complicated and fairly high-overhead solution, though. Would be a lot better if I could figure out how to configure lighttpd to let me just pass messages back and forth with the controller. That, after all, is how the entire system is designed to work.

RE: allowing a cgi to communicate with an external program - Added by jiml8 over 2 years ago

I wonder what will happen if I use memory mapping when I fork. Will lighttpd stop me then? Hmmm...not sure how it could. But then, I am astonished that I can't input data through a unix socket into the webserver environment...

RE: allowing a cgi to communicate with an external program - Added by jiml8 over 2 years ago

lighttpd runs as the user it is started as, unless you specify server.username. lighttpd refuses to run as a setuid binary.

And, on re-reading this thread, I paid attention to this.

I commented out the server.username and server.group in the config file, and now it works.

I don't want to run the webserver as root, but I am not sure it is a big issue for me. Basically, our device self-configures and self-registers with our servers the first time it is powered up and connected to the internet. Should this connection fail for whatever reason, the controller will start lighttpd and the "no internet" page (which is what the cgi provides) will be displayed in a captive portal, enabling the user to manually configure the device so it can connect.

This is the only time in the device's existence that lighttpd will run; we don't use web services for anything else ever. So, given that this only runs on the first boot, and then only if the device fails to find the internet, and then only until the device is initially configured, maybe I don't care if the server runs as root.

RE: allowing a cgi to communicate with an external program - Added by gstrauss over 2 years ago

Security is important to lighttpd, and there are plenty of uses where people run lighttpd as root, so if that works for you and you are aware of the risks, go for it.

For others who run arbitrary CGI downloaded from elsewhere on the internet, or for those who are not aware of the risks, it is recommended that lighttpd not be run as root if doing so can be avoided. That is simply the best practice of "least privilege".

RE: [Solved] allowing a cgi to communicate with an external program - Added by jiml8 over 2 years ago

OK, after playing with this a bit more, all I really need to do is make root the owner of the cgi, and set the SETUID bit on that file.

This lets me leave lighttpd running as user www, while enabling my cgi to connect with the controller.

I fiddled with the socket permissions on the controller's communications socket, and that didn't do much. The SETUID solved the problem.

Thanks for your assistance and ideas.

    (1-10/10)