Chrooting perl for OpenHTTPd

Category: perl

By default, OpenHTTPd runs inside a chroot in the path /var/www. In order to run perl(1) applications, you will need to copy perl and its dependencies into this chroot.

Configuring httpd

First, configure httpd.conf(5). We will use slowcgi(8) to execute CGI scripts. Here is our sample /etc/httpd.conf:

server "www.example.com" {
        listen on * port 80
        root "/htdocs/perl"
        location "*.pl" {  
                fastcgi
        }
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
}

We specify our web document root in /htdocs/perl. Remember that because httpd(1) is chrooted, the path of your actual web documents must be /var/www/htdocs/perl/. The fastcgi directive tells the server to interpret files that end with the extension .pl to be interpreted as CGI scripts.

Reload the web server so it reloads the conf file:

# rcctl reload httpd

Copying perl

To figure out what files need to be copied into the chroot:

$ ldd /usr/bin/perl
/usr/bin/perl:
        Start            End              Type  Open Ref GrpRef Name
        00000c0f12e8d000 00000c0f12e92000 exe   1    0   0      /usr/bin/perl
        00000c12054ca000 00000c1205877000 rlib  0    1   0      /usr/lib/libperl.so.24.0
        00000c118de26000 00000c118de58000 rlib  0    2   0      /usr/lib/libm.so.10.1
        00000c11cc985000 00000c11cca8c000 rlib  0    2   0      /usr/lib/libc.so.100.3
        00000c113a3b6000 00000c113a3b6000 ld.so 0    1   0      /usr/libexec/ld.so

Start with this initial guess to build the chroot:

# mkdir -p /var/www/usr/{bin,lib,libexec}
# cp -p /usr/bin/perl /var/www/usr/bin/
# cp -p /usr/lib/lib{c,m,perl}.so* /var/www/usr/lib/
# cp -p /usr/libexec/ld.so /var/www/usr/libexec/

Next, turn on slowcgi(8):

# rcctl enable slowcgi
# rcctl start slowcgi

Use chroot(8) to see if perl can run inside the chroot:

# chroot -u www -g daemon /var/www perl

Run a test command with perl:

print "All users are created equal\n";

Type ctrl+d to escape; if perl successfully echoes @@All users are created equal@@, we can confirm that this perl command can run inside the chroot.

Next, create the directory to hold web documents:

# mkdir -p /var/www/htdocs/perl/

Create /var/www/htdocs/perl/index.pl to see if the web server loads it:

#!/usr/bin/perl -w
use strict;
print "Content-Type:text/html\n\n";
print "All users are created equal: 17".int(rand(100))."\n";

Make the script executable:

# chmod +x /var/www/htdocs/perl/index.pl

Now, try running this command:

$ ftp -o - http://example.com/index.pl
Trying 192.168.1.2...
Requesting http://example.com/index.pl
ftp: Error retrieving http://example.com/index.pl: 500 Internal Server Error

A 500 Internal Error is often a clue that the web server is unable to properly execute a CGI script. In this case, it means that perl was not set up properly inside the chroot.

Search httpd(1)'s error logs for further clues. By default, errors go to /var/www/logs/error.log:

server example.com, client 1 (1 active), 10.0.1.2:37391 -> 192.168.1.2, empty stdout (500 Internal Server Error)
Can't locate strict.pm in @INC (you may need to install the strict module) (@INC entries checked: /usr/local/libdata/perl5/site_perl/amd64-openbsd /usr/local/libdata/perl5/site_perl /usr/libdata/perl5/amd64-openbsd /usr/libdata/perl5) at /htdocs/perl/index.pl line 2.
BEGIN failed--compilation aborted at /htdocs/perl/index.pl line 2.

This error message tells us that the strict.pm dependency cannot be found inside the chroot. So, we search for which directories on our filesystem contain perl libraries:

# find / -iname '*perl*'

In this case, the missing folder is /usr/libdata/perl5. We definitely need to copy these libraries over:

# mkdir -p /var/www/usr/libdata
# cp -pR /usr/libdata/perl5 /var/www/usr/libdata/

If we continue this trial and error debugging process, we end up with all the dependencies we will need. Here is the complete list:

# mkdir -p /var/www/usr/{bin,lib,libexec,libdata}
# cp -p /usr/bin/perl /var/www/usr/bin/
# cp -p /usr/lib/lib{c,m,perl,pthread,util,z}.so* /var/www/usr/lib/
# cp -p /usr/libexec/ld.so /var/www/usr/libexec/
# cp -pR /usr/libdata/perl5 /var/www/usr/libdata/
# mkdir -p /var/www/{tmp,dev}
# touch /var/www/dev/null

Now the web request should show proper output:

$ ftp -o - http://example.com/index.pl 
Trying 192.168.1.2...
Requesting http://example.com/index.pl
All users are created equal: 1774
28 bytes received in 0.00 seconds (13.49 KB/s)

Adding TLS for serving your perl scripts is left as an exercise.