Why does my simple fastCGI Perl script fail? - perl

I'm not of the Perl world, so some of this is new to me. I'm running Ubuntu Hardy LTS with apache2 and mod_fcgid packages installed. I'd like to get MT4 running under fcgid rather than mod-cgi (it seems to run OK with plain-old CGI).
I can't seem to get even a simple Perl script to run under fcgid. I created a simple "Hello World" app and included the code from this previous question to test if FCGI is running.
I named my script HelloWorld.fcgi (currently fcgid is set to handle .fcgi files only). Code:
#!/usr/bin/perl
use FCGI;
print "Content-type: text/html\n\n";
print "Hello world.\n\n";
my $request = FCGI::Request();
if ( $request->IsFastCGI ) {
print "we're running under FastCGI!\n";
} else {
print "plain old boring CGI\n";
}
When run from the command line, it prints "plain old boring..." When invoked via an http request to apache, I get a 500 Internal Server error and the output of the script is printed to the Apache error log:
Content-type: text/html
Hello world.
we're running under FastCGI!
[Wed Dec 03 22:26:19 2008] [warn] (104)Connection reset by peer: mod_fcgid: read data from fastcgi server error.
[Wed Dec 03 22:26:19 2008] [error] [client 70.23.221.171] Premature end of script headers: HelloWorld.fcgi
[Wed Dec 03 22:26:25 2008] [notice] mod_fcgid: process /www/mt/HelloWorld.fcgi(14189) exit(communication error), terminated by calling exit(), return code: 0
When I run the .cgi version of the same code, it works fine. Any idea why the output of the script is going to the error log? Apache config is the default mod_fcgid config plus, in a VirtualHost directive:
ServerName test1.example.com
DocumentRoot /www/example
<Directory /www/example>
AllowOverride None
AddHandler cgi-script .cgi
AddHandler fcgid-script .fcgi
Options +ExecCGI +Includes +FollowSymLinks
</Directory>

The problem is that the "Content-Type" header is sent outside of the request loop. You must print the "Content-Type" header for every request. If you move
print "Content-type: text/html\n\n";
to the top of the request loop it should fix the problem.
Also, you need to loop over the requests or you'll get no benefit, so following the first poster's example:
my $request = FCGI::Request();
while($request->Accept() >= 0) {
print("Content-type: text/html\n\n");
}

I use CGI::Fast more than FCGI, but the idea is the same, I think. The goal of fast cgi is to load the program once, and iterate in a loop for every request.
FCGI's man page says :
use FCGI;
my $count = 0;
my $request = FCGI::Request();
while($request->Accept() >= 0) {
print("Content-type: text/html\r\n\r\n", ++$count);
}
Which means, you have to Accept the request before being able to print anything back to the browser.

Movable Type uses CGI::Fast for FastCGI. The typical FastCGI script runs in a loop, as mat described. A loop that uses CGI::Fast would look like this:
#!/usr/bin/perl
use strict;
use CGI::Fast;
my $count = 0;
while (my $q = CGI::Fast->new) {
print("Content-Type: text/plain\n\n");
print("Process ID: $$; Count is: " . ++$count);
}
I tested this script on a server with the FCGI and CGI::Fast modules installed and count increments as you'd expect. If the process id changes, count will go back to 1 and then increment within that process. Each process has it's own variable space of course.
For MT, enabling FastCGI a matter of renaming (or symlinking) the cgi scripts to 'fcgi' (or making the handler for 'cgi' scripts fcgid, but that won't work for mt-xmlrpc.cgi which isn't FastCGI friendly yet). You'll also need to add some directives to your mt-config.cgi file so that it knows the new script names. Like this:
AdminScript mt.fcgi
CommentsScript mt-comments.fcgi
And so forth. More documentation specific to FastCGI and Movable Type is available on movabletype.org.

Anyway, based on your server's error logs, it looks like FCGI is working, and being invoked properly, but your script just isn't running in a loop, waiting for the next request to come along. So your test script did accomplish the task -- reporting whether FastCGI is configured or not. So now you should be able to reconfigure MT to use FastCGI.

Related

Proper working with suexec on apache httpd 2.4

I am trying to run 3 separated domains in one machine, each with its user (through virtual host) so I decided to use suExec for my task (it been also installed along with apache):
Global Server configuration running on "apache" user and group.
The conficuration of one of the users (the one I am trying to access with):
listen 9999
<VirtualHost *:9999>
ServerName *:9999
DocumentRoot "/home/efpanel/public_html"
ErrorLog "/home/efpanel/err.log"
<IfModule suexec_module>
# SuexecUserGroup efpanel efpanel
</IfModule>
# Directory settings...
...
<IfModule alias_module>
ScriptAlias /cgi-bin/ "/home/efpanel/public_html/cgi-bin/"
</IfModule>
</VirtualHost>
The issue:
When the line of the SuexecUserGroup is commented, running perl script will be on user "apache" - not what I want, but at least run.
HOWEVER: If I uncomment this line, even simple perl script will fail with error 500 (internal server error). Checking the log file, the error I get there is:
[Sat Dec 21 01:34:56.274872 2019] [cgi:error] [pid 31211] [client 183.28.7.14:7262] End of script output before headers: test.cgi, referer: http://99.99.99.99:9999/
Notes:
1) mod_suexec is installed and enabled (the below is the result of "ls -l /usr/sbin | grep suexec"):
-r-x--x--- 1 root apache 15440 Aug 8 07:42 suexec
2) I am running Apache 2.4 httpd on CentOS 7, CGI written in PERL.
3) I am aware that suEXEC only work on script files, but is enough for me - if I make it somehow to work.
4) Checking the configuration values, I cannot determine where is the suexec log file, and was unable to find it by "find / suexec.log" command. The configuration values are as follows (seems standard):
[root#vps cgi-bin]# suexec -V
-D AP_DOC_ROOT="/var/www"
-D AP_GID_MIN=100
-D AP_HTTPD_USER="apache"
-D AP_LOG_SYSLOG
-D AP_SAFE_PATH="/usr/local/bin:/usr/bin:/bin"
-D AP_UID_MIN=500
-D AP_USERDIR_SUFFIX="public_html"
My only concern here is the "DOC_ROOT" which is "/var/www" and has anything common with "/home/efpanel" - but: a) I have no idea how to reconfigure suexec after it been compiled, b) I don't think it is that necessary, maybe it is ok "as is"?
5) cgi script, cgi-bin directory, public_html directory and /home/efpanel directory are all belong to the same user and group (efpanel) and has access permit of 0755, except "/home/efpanel" that has 0711 access permit.
6) I have tried to add "-w" in the first line of the script, as suggested here:
#!/usr/bin/perl -w
my $u=(getpwuid $>)[0];
print "ContentType: text/html;\n\n$u";
exit 0;
As it seen, a VERY simple script and fail.

How to access a simple SOAP Service in Perl

I am currently dabbling around with perl and SOAP, using SOAP::Lite.
I have a simple SOAP server that appears to run fine:
#!perl -w
use SOAP::Transport::HTTP;
use Demo;
# don't want to die on 'Broken pipe' or Ctrl-C
$SIG{PIPE} = $SIG{INT} = 'IGNORE';
my $daemon = SOAP::Transport::HTTP::Daemon
-> new (LocalPort => 801)
-> dispatch_to('/home/soaplite/modules')
;
print "Contact to SOAP server at ", $daemon->url, "\n";
$daemon->handle;
It includes a small class called Demo, which simply retrieves the systems total memory:
Demo.py
#!/usr/bin/perl
use Sys::MemInfo qw(totalmem freemem totalswap);
print "total memory: ".(&totalmem / 1024)."\n";
I have an example of a SOAP client below written in PERL, although I am unsure how to communicate with the server (since the tutorial I am following here goes of on a tangent e.g. retrieve the result of the Demo.py class from the client:
#!perl -w
use SOAP::Lite;
# Frontier http://www.userland.com/
$s = SOAP::Lite
-> uri('/examples')
-> on_action(sub { sprintf '"%s"', shift })
-> proxy('http://superhonker.userland.com/')
;
print $s->getStateName(SOAP::Data->name(statenum => 25))->result;
Any help would be greatly appreciated :)
For the server script, the dispatch_to method takes the path to the package to load, and the name of the package itself. If you pass a third parameter, it will limit the names of the methods made visible by the server. (e.g. 2 methods named memory and time, passing Demo::time as the 3rd param will make memory invisible to the client service.)
File server.pl
my $daemon = SOAP::Transport::HTTP::Daemon
-> new (LocalPort => 801)
-> dispatch_to('/home/soaplite/modules', 'Demo')
;
Your Demo package should be a package with methods that return the values. I couldn't get Sys::MemInfo compiled on my system, so I just used localtime instead. I'm not sure why you named your package Demo.py, but Perl packages must have the extension pm, otherwise they won't be properly loaded.
File Demo.pm
#!/usr/bin/perl
package Demo;
#use Sys::MemInfo qw(totalmem freemem totalswap);
sub memory {
#print "total memory: ".(&totalmem / 1024)."\n";
return "Can't load Sys::MemInfo, sorry";
}
sub time {
my $time = localtime;
return $time;
}
1;
For the client code, there's 2 important pieces that must be properly specified to work, the proxy and the uri. The proxy is the url path to the soap web service. Since you are running the server script as a daemon process, your path is just the web site's url. My computer doesn't have a url, so I used http://localhost:801/. The 801 is the port you specified above. If you were running as a cgi script inside of a different web server (such as Apache), then you would need to specify the cgi script to call (e.g. http://localhost/cgi-bin/server.pl, changing the package in server.pl to SOAP::Transport::HTTP::CGI.
uri is probably the most confusing, but it's the namespace of the xml files returned by the web service. Turn on +trace => 'debug' to see the xml file returned by the web service. The uri should just be the name of the server. Even if you switch ports or to a cgi dispatch method, this uri stays the same.
File test.pl
#!perl -w
use SOAP::Lite +trace => 'debug';
# Frontier http://www.userland.com/
$s = SOAP::Lite->new(proxy => 'http://superhonker.userland.com:801/',
uri => 'http://superhonker.userland.com/');
#might be http://www.userland.com/
#but I could not test sub-domains
print $s->time()->result;
I'll recycle these two answers for tips:
Client of web service in Perl
Remote function call using SOAP::Lite

HTTP::Daemon and threads

I have the following code on Windows XP and ActiveState ActivePerl 5.8.
What could be the problem with it? Why does it not work?
I tried to set it as a proxy to my IE but when I connect to some URLs from my IE nothing happens. The code enters the thread function and nothing happens.
use HTTP::Daemon;
use threads;
use HTTP::Status;
use LWP::UserAgent;
my $webServer;
my $d = HTTP::Daemon->new(
LocalAddr => '127.0.0.1',
LocalPort => 80,
Listen => 20
) || die;
print "Web Server started!\n";
print "Server Address: ", $d->sockhost(), "\n";
print "Server Port: ", $d->sockport(), "\n";
while (my $c = $d->accept) {
threads->create(\&process_one_req, $c)->detach();
}
sub process_one_req {
STDOUT->autoflush(1);
my $c = shift;
while (my $r = $c->get_request) {
if ($r->method eq "GET") {
print "Session info\n", $r->header('Host');
my $ua = LWP::UserAgent->new;
my $response = $ua->request($r);
$c->send_response($response);
} else {
$c->send_error(RC_FORBIDDEN);
}
}
$c->close;
undef($c);
}
I added the following line to the code before LWP::UserAgent->new and it seems to be working for me (in linux).
$r->uri("http://" . $r->header('Host') . "/" . $r->uri());
The uri that you got from the HTTP::Request object from the original request does not have the hostname. So added it to make it a absolute uri. Tested as follows:
$ curl -D - -o /dev/null -s -H 'Host: www.yahoo.com' http://localhost:8080/
HTTP/1.1 200 OK
Date: Thu, 27 Jan 2011 12:59:56 GMT
Server: libwww-perl-daemon/5.827
Cache-Control: private
Connection: close
Date: Thu, 27 Jan 2011 12:57:15 GMT
Age: 0
---snip--
UPDATE: Looks like I was completely wrong. I didnt need to make the change to URI object. Your original code worked for me as it is in Linux
If I recall correctly, this is because of the threading model in Windows where file handles are not passed between processes unless specifically asked for. This PerlMonks post seems to shed some light on the underlying problem, and may lead to an approach that works for you (I imagine you may be able to call the windows API on the file descriptor of of the client connection to allow access to it within the spawned thread).
Perl threads on Windows generally make my head hurt, while on UNIX-list systems I find them very easy to deal with. Then again, I imagine figuring out how to correctly use forked processes to emulate threads on a system that ONLY supports threads and not forking would make most people's head hurt.

Why does my Perl CGI program fail with "Software error: ..."?

When I try to run my Perl CGI program, the returned web page tells me:
Software error: For help, please send mail to the webmaster (root#localhost), giving this error message and the time and date of the error.
Here is my code in one of the file:
#!/usr/bin/perl
use lib "/home/ecoopr/ecoopr.com/CPAN";
use CGI;
use CGI::FormBuilder;
use CGI::Session;
use CGI::Carp (fatalsToBrowser);
use CGI::Session;
use HTML::Template;
use MIME::Base64 ();
use strict;
require "./db_lib.pl";
require "./config.pl";
my $query = CGI->new;
my $url = $query->url();
my $hostname = $query->url(-base => 1);
my $login_url = $hostname . '/login.pl';
my $redir_url = $login_url . '?d=' . $url;
my $domain_name = get_domain_name();
my $helpful_msg = $query->param('m');
my $new_trusted_user_fname = $query->param('u');
my $action = $query->param('a');
$new_trusted_user_fname = MIME::Base64::decode($new_trusted_user_fname);
####### Colin: Added July 12, 2009 #######
my $view = $query->param('view');
my $offset = $query->param('offset');
####### Colin: Added July , 2009 #######
#print $session->header;
#print $new_trusted_user;
my $helpful_msg_txt = qq[];
my $helpful_msg_div = qq[];
if ($helpful_msg)
The "please send mail to the webmaster" message you see is a generic message that the web server gives you when anything goes wrong and nothing handles it. It's not at all interesting in terms of solving the actual problem. Check the error log to find possible relevant error output from your program.
And, go through my How do I troubleshoot my Perl CGI script? advice on finding the problem.
My guess is that you have a syntax error with that dangling if(). What you have posted isn't a valid Perl program.
Good luck,
is that something related to suexec module
Improper configuration of suExec can cause permission errors
The suEXEC feature provides Apache users the ability to run CGI and SSI programs under user IDs different from the user ID of the calling web server. Normally, when a CGI or SSI program executes, it runs as the same user who is running the web server.
apache recommends that you not consider using suEXEC.
http://httpd.apache.org/docs/2.2/suexec.html
From the StackOverflow page: How to trap program crashes with HTTP error code 500
I see that your include: use CGI::Carp (fatalsToBrowser);
... stifles the HTTP 500 error. Simply removing this will allow the programs to crash "properly".

Why can't I connect to my CAS server with Perl's AuthCAS?

I'm attempting to use an existing CAS server to authenticate login for a Perl CGI web script and am using the AuthCAS Perl module (v 1.3.1). I can connect to the CAS server to get the service ticket but when I try to connect to validate the ticket my script returns with the following error from the IO::Socket::SSL module:
500 Can't connect to [CAS Server]:443 (Bad hostname '[CAS Server]')
([CAS Server] substituted for real server name)
Symptoms/Tests:
If I type the generated URL for the authentication into the web browser's location bar it returns just fine with the expected XML snippet. So it is not a bad host name.
If I generate a script without using the AuthCAS module but using the IO::Socket::SSL module directly to query the CAS server for validation on the generated service ticket the Perl script will run fine from the command line but not in the browser.
If I add the AuthCAS module into the script in item 2, the script no longer works on the command line and still doesn't work in the browser.
Here is the bare-bones script that produces the error:
#!/usr/bin/perl
use strict;
use warnings;
use CGI;
use AuthCAS;
use CGI::Carp qw( fatalsToBrowser );
my $id = $ENV{QUERY_STRING};
my $q = new CGI;
my $target = "http://localhost/cgi-bin/testCAS.cgi";
my $cas = new AuthCAS(casUrl => 'https://cas_server/cas');
if ($id eq ""){
my $login_url = $cas->getServerLoginURL($target);
printf "Location: $login_url\n\n";
exit 0;
} else {
print $q->header();
print "CAS TEST<br>\n";
## When coming back from the CAS server a ticket is provided in the QUERY_STRING
print "QUERY_STRING = " . $id . "</br>\n";
## $ST should contain the received Service Ticket
my $ST = $q->param('ticket');
my $user = $cas->validateST($target, $ST); #### This is what fails
printf "Error: %s\n", &AuthCAS::get_errors() unless (defined $user);
}
Any ideas on where the conflict might be?
The error is coming from the line directly above the snippet Cebjyre quoted namely
$ssl_socket = new IO::Socket::SSL(%ssl_options);
namely the socket creation. All of the input parameters are correct. I had edited the module to put in debug statements and print out all the parameters just before that call and they are all fine. Looks like I'm going to have to dive deeper into the IO::Socket::SSL module.
As usually happens when I post questions like this, I found the problem. It turns out the Crypt::SSLeay module was not installed or at least not up to date. Of course the error messages didn't give me any clues. Updating it and all the problems go away and things are working fine now.
Well, from the module source it looks like that IO::Socket error is coming from get_https2
[...]
unless ($ssl_socket) {
$errors = sprintf "error %s unable to connect https://%s:%s/\n",&IO::Socket::SSL::errstr,$host,$port;
return undef;
}
[...]
which is called by callCAS, which is called by validateST.
One option is to temporarily edit the module file to put some debug statements in if you can, but if I had to guess, I'd say the casUrl you are supplying isn't matching up to the _parse_url regex properly - maybe you have three slashes after the https?