Link to home
Start Free TrialLog in
Avatar of James Talvy
James TalvyFlag for United States of America

asked on

Apache environment variables not working in CGI

I am trying to run a Perl CGI on Apache and it is complaining with the following:

[Tue Feb 24 14:42:43 2015] Can't load '/usr/local/lib64/perl5/auto/DBD/Sybase/Sybase.so' for module DBD::Sybase: libsybct64.so: cannot open shared object file: No such file or directory at /usr/lib64/perl5/DynaLoader.pm line 200.\n at /home/gsg/gsg/jt.pl line 4\nCompilation failed in require at /home/gsg/gsg/jt.pl line 4.\nBEGIN failed--compilation aborted at /home/gsg/gsg/jt.pl line 4.\n

I know this is somehow related to the LD_LIBRARY_PATH not being set since I get the same error on the comand line with the following small Perl script:

     #!/usr/local/bin/perl -w
     use DBD::Sybase;

When I set the LD_LIBRARY_PATH to where the sybase libraries are I no longer get an error on the command line.

I set the env variables in the httpd.conf config file via:

   SetEnv SYBASE /appl/sybase
   SetEnv SYBASE_OCS OCS-15_0
   SetEnv LD_LIBRARY_PATH /appl/sybase/OCS-15_0/lib

If I make a small CGI that prints the environment it does show that I have SYBASE, SYBASE_OCS & LD_LIBRARY_PATH set:

   #!/usr/local/bin/perl -w
   use CGI;
 
   my $q = CGI->new;
   print $q->header, $q->start_html ;

   print "<TABLE>\n" ;
   foreach $key (sort keys(%ENV))
   {
     print "<TR><TD>$key</TD><TD>$ENV{$key}</TD></TR>\n" ;
   }
   print "</TABLE>\n" ;

   print $q->end_html;

If I force the system as a whole to find the Sybase libs by putting the path to it in /etc/ld.so.conf.d/ it doesn't help since then the CGI fails since it is complaining it does not know the SYBASE & SYBASE_OCS env variables; even though they display in the test CGI above.
 
Apache Version:
   Apache/2.2.15 (Unix) DAV/2 mod_perl/2.0.4 Perl/v5.10.1 configured

uname -a:
   Linux devtest002 2.6.32-431.29.2.el6.x86_64 #1 SMP Sun Jul 27 15:55:46 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux

I appreciate any help you can provide to resolve this.
 
Thanks!
Avatar of FishMonger
FishMonger
Flag of United States of America image

Perl has multiple phases of execution.  The 2 well known phases are the compilation and runtime, but there are a few others.  Modules loaded via a use statement are loaded during the compilation phase.  I don't know in which phase the env vars are seen/initialized in the script, but it's possible (if not likely) that the env initialization happens after the modules are loaded and prior to the runtime phase (i.e., in the INIT phase).

Typically, the DBD::* modules are not loaded via a use statement.  They normally get loaded by the DBI module via a require statement when executing the connect statement, which occurs during the runtime phase.

Have you tried loading the DBD module via a require statement?  If so, do you experience the same problem?

Have you tried setting the env vars in a BEGIN block prior to loading the DBD module?  If so, do you experience the same problem?
I should also point out that when you receive this type of error, it is often an indication that the module wasn't installed properly.  For example, if the module was simply copied from another location rather than being compiled, or if it was a force install to bypass some build error.  That may not be the case here, but it is often the case.
Avatar of James Talvy

ASKER

It seems as if use/require makes no difference and I receive the same error:

  #!/usr/local/bin/perl -w
  require DBD::Sybase;

Also setting the ENV vars in a BEGIN block has no positive effect:

  #!/usr/local/bin/perl -w

  BEGIN {
      $ENV{'SYBASE'}='/appl/sybase' ;
      $ENV{'SYBASE_OCS'}='OCS-15_0' ;
      $ENV{'LD_LIBRARY_PATH'}='/appl/sybase/OCS-15_0/lib' ;
  }

  use DBD::Sybase;

Only when I set the env vars at the command prompt do I no longer receive an error:

  [jamtal@usx2espdeustd002 PERL]$ export SYBASE=/appl/sybase
  [jamtal@usx2espdeustd002 PERL]$ export SYBASE_OCS=OCS-15_0
  [jamtal@usx2espdeustd002 PERL]$ export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib
  [jamtal@usx2espdeustd002 PERL]$ ./test.pl
  [jamtal@usx2espdeustd002 PERL]$

It is for this reason I believe that the module is built and installed correctly.

Thanks for your consideration.
ASKER CERTIFIED SOLUTION
Avatar of FishMonger
FishMonger
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
$^X is path to the apache webserver at '/usr/sbin/httpd' so that gave an error... When I try and force it to the actual Perl via "exec("/usr/local/bin/perl", $0, @ARGV);" it also fails.

This is sooooo painful since the reason I am doing this is to migrate to Linux.

On our ancient Solaris 10 Sparc (yes Sparc) system we simple put this in the Apache httpd.conf and all works without a hickup...

  LoadModule perl_module modules/mod_perl.so
 
  Alias   /perl/gsg       /appl/gsg/gsg

  SetEnv SYBASE /appl/sybase/OCS1251
  SetEnv SYBASE_OCS OCS-12_5
  SetEnv LD_LIBRARY_PATH /usr/ucblib

  PerlModule ModPerl::Registry
  PerlModule DBI
  PerlModule Apache2::compat

  <Location       /perl>
        SetHandler perl-script
        PerlResponseHandler +ModPerl::Registry
        Options +ExecCGI
        #PerlSendHeader On
        PerlOptions +ParseHeaders
        # PerlInitHandler Apache::StatINC
        PerlSetVar StatINCDebug On
        Allow from all
  </Location>

Thanks!
Try this in httpd.conf
PerlSetEnv SYBASE /appl/sybase/OCS1251
PerlSetEnv SYBASE_OCS OCS-12_5
PerlSetEnv LD_LIBRARY_PATH /usr/ucblib

Open in new window


Reference:
Apache PerlSetEnv and PerlPassEnv documentation

EDIT:
I just noticed that the link I posted is for mod_perl 1.0.

Here's the link to mod_perl 2.0 (I don't see any difference in the usage of the directive).
http://perl.apache.org/docs/2.0/user/config/config.html#C_PerlSetEnv_mod_perl 2.0 doc
Unfortunately I already tried the PerlSetEnv command and it had no impact.

Again they show up in the %ENV hash but the usg DBD::Sybase yield the same error I reported.

This is a killer :-(
Well, I'm running out of suggestions.

Based on the results of numerous other people having this issue, at least several of the suggestions I've made are known proven solutions.  I don't know why they're not working for you.

This may seem like a silly question, but did you restart apache after changing the conf file to use PerlSetEnv?  I ask because my reading of the documentation indicates that PerlSetEnv is needed in a mod_perl environment and SetEnv is used outside of the mod_perl environment.
Indeed I restart the httpd service every time I make a config change.

And when I set the PerlSetEnv my perl code will display the %ENV hash with the variables set.
I don't see why this is not working for you and since I don't have Sybase or Oracle, I can't do any testing.

Sorry, I can't think of anything else to suggest.
I suspect that it is something weird with the Perl...

Since eliminating Apache... yields the same error... this does not work...

#!/usr/local/bin/perl -w

BEGIN {
  $ENV{'SYBASE'}='/appl/sybase' ;
  $ENV{'SYBASE_OCS'}='OCS-15_0' ;
  $ENV{'LD_LIBRARY_PATH'}='/appl/sybase/OCS-15_0/lib' ;
}

use DBD::Sybase;

Open in new window


But setting in the env before invoking the perl interpreter works...

> export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib
> export SYBASE=/appl/sybase
> export SYBASE_OCS=OCS-15_0
> ./test.pl

Is there a way to force env vars for every process in the system?

When you specify User/Group in the httpd.conf does it inherit the variables from that user?

Also bugs me that the httpd process does not seem to inherit the environment variables set prior to starting it.

Note it is a service started as "service httpd start"
I would not not expect that BEGIN block to work based on what I learned and shared in post ID: 40631697.

Is there a way to force env vars for every process in the system?
Possibly, but I haven't tried it.  Try putting these vars in the /etc/environment file.
LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib
SYBASE=/appl/sybase
SYBASE_OCS=OCS-15_0

Open in new window


When you specify User/Group in the httpd.conf does it inherit the variables from that user?
Since the apache user account is (or should be) a "nologin" account and normally doesn't have a home directory, there would be no user vars available since they would need to be configured/exported from the login scripts (i.e., .bash_profile or .bashrc).
ok... so I put the variables in the /etc/environment and it worked when running a simple perl script from the command line.

I run the apache server as root via "service httpd start"... and still does not work...

As root I set the env variables manually in my shell and then I start httpd via "/usr/sbin/httpd" and it FINALLY works...

> cd /usr/sbin
> export SYBASE=/appl/sybase
> export SYBASE_OCS=OCS-15_0
> export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib
> ./httpd

Open in new window


So now it seems that when starting it with the service command it does not get the /etc/environment settings (neither does it seem to be in the root user either)...

I tried adding
export SYBASE=/appl/sybase
export SYBASE_OCS=OCS-15_0
export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib

Open in new window


to the /etc/rc.d/init.d/httpd file start() section but it still did not help...
I did a little more research and found that if mod_perl is setuid/setgid, then LD_LIBRARY_PATH gets ignored.

The only solution that I've come across which we haven't tried is to rebuild DBD::Oracle with the -rpath option.  An additional suggestion for the rebuild option is to edit the Makefile, serach for the LD_RUN_PATH= and specify the path in that statement.
That could make sense since on our old Solaris Sparc system we have apache installed under a user account called www and listening on port 8080.  User/Group setting in httpd.conf is also www.

The thing was that it was not only the LD_LIBRARY_PATH that mod_perl ignores but also the SYBASE and SYBASE_OCS variables.
That's odd.  All of the issues I came across indicate that the problem was only with LD_LIBRARY_PATH.  Are you sure mod_perl is also ignoring the other vars?

At this point I have no other ideas on what to try.
Yes since when I added a file under /etc/ld.so.conf.d/ and ran ldconfig it effectively eliminated the LD_LIBRARY_PATH issue but then I received and error in the error_log that stated it didn't know /home/sybase directory.... SYBASE env var being set to /appl/sybase
Well, I'm completely out of ideas.

The only thing I can suggest is to post this issue on the official apache mod_perl maililst.  Their archive is where I found (via google search) most of the possible solutions.
Since we haven't been able to figure out how to get the loader to follow the path (because we're not catching it early enough or because of permissions), perhaps we could finesse the problem by creating a symlink named

/usr/local/lib64/perl5/auto/DBD/Sybase/Sybase.so

that points to

/appl/sybase/OCS-15_0/lib/Sybase.so

You may have to do the same thing for other .so files. I'm pretty sure that will at least get us a different error and put us one step closer to understanding what is not working the way we expect it to.

(Or just make copies if you don't like the idea of symlinks.)
That would resolve the shared library issue but then I am left with the SYBASE & SYBASE_OCS issue...

See: https://www.experts-exchange.com/questions/28624162/Apache-environment-variables-not-working-in-CGI.html?anchorAnswerId=40633725#a40633725

I have to set the env vars and then start manually...

I do not know why when changing the /etc/rc.d/init.d/httpd start() section to the following it does not work... I env put an echo in to test that the env vars were set.

start() {
        echo -n $"Starting $prog: "

        export SYBASE=/appl/sybase
        export SYBASE_OCS=OCS-15_0
        export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib

        echo Environment Variables...
        env
        echo

        LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS
        RETVAL=$?
        echo
        [ $RETVAL = 0 ] && touch ${lockfile}
        return $RETVAL
}

Open in new window

It looks like 'daemon' may strip environment variables. I tried looking at various manpages for the daemon command and got mixed results whether this is by design, so it may depend on the particular flavor and vintage of your Linux.

At least some versions of the daemon command allow you to specify an --env option that tells it which environment variables are to be passed in (and only those). You should check out your manpage for daemon and see if there's something about environment variables that would clarify how yours behaves.

Also look for an --inherit switch.

I'm sure environment stripping is intended as a sort of security measure, since many hacks are based off on messing with the environment of a privileged program, particularly with feeding it a hacked load path.
Aparentling in my Linux the "daemon" command is defined in the "/etc/rc.d/init.d/functions" file.

uname -a:
    Linux devtest002 2.6.32-431.29.2.el6.x86_64 #1 SMP Sun Jul 27 15:55:46 EDT 2014 x86_64 x86_64 x86_64 GNU/Linux

Unfort I am not that skilled and I do not know how to change this to make sure the env vars are inherited...

# A function to start a program.
daemon() {
        # Test syntax.
        local gotbase= force= nicelevel corelimit
        local pid base= user= nice= bg= pid_file=
        local cgroup=
        nicelevel=0
        while [ "$1" != "${1##[-+]}" ]; do
          case $1 in
            '')    echo $"$0: Usage: daemon [+/-nicelevel] {program}"
                   return 1;;
            --check)
                   base=$2
                   gotbase="yes"
                   shift 2
                   ;;
            --check=?*)
                   base=${1#--check=}
                   gotbase="yes"
                   shift
                   ;;
            --user)
                   user=$2
                   shift 2
                   ;;
            --user=?*)
                   user=${1#--user=}
                   shift
                   ;;
            --pidfile)
                   pid_file=$2
                   shift 2
                   ;;
            --pidfile=?*)
                   pid_file=${1#--pidfile=}
                   shift
                   ;;
            --force)
                   force="force"
                  shift
                   ;;
            [-+][0-9]*)
                   nice="nice -n $1"
                   shift
                   ;;
            *)     echo $"$0: Usage: daemon [+/-nicelevel] {program}"
                   return 1;;
          esac
        done

        # Save basename.
        [ -z "$gotbase" ] && base=${1##*/}

        # See if it's already running. Look *only* at the pid file.
        __pids_var_run "$base" "$pid_file"

        [ -n "$pid" -a -z "$force" ] && return

        # make sure it doesn't core dump anywhere unless requested
        corelimit="ulimit -S -c ${DAEMON_COREFILE_LIMIT:-0}"

        # if they set NICELEVEL in /etc/sysconfig/foo, honor it
        [ -n "${NICELEVEL:-}" ] && nice="nice -n $NICELEVEL"

        # if they set CGROUP_DAEMON in /etc/sysconfig/foo, honor it
        if [ -n "${CGROUP_DAEMON}" ]; then
                if [ ! -x /bin/cgexec ]; then
                        echo -n "Cgroups not installed"; warning
                        echo
                else
                        cgroup="/bin/cgexec";
                        for i in $CGROUP_DAEMON; do
                                cgroup="$cgroup -g $i";
                        done
                fi
        fi

        # Echo daemon
        [ "${BOOTUP:-}" = "verbose" -a -z "${LSB:-}" ] && echo -n " $base"

        # And start it up.
        if [ -z "$user" ]; then
           $cgroup $nice /bin/bash -c "$corelimit >/dev/null 2>&1 ; $*"
        else
           $cgroup $nice runuser -s /bin/bash $user -c "$corelimit >/dev/null 2>&1 ; $*"
        fi

        [ "$?" -eq 0 ] && success $"$base startup" || failure $"$base startup"
}

Open in new window

Apparently ultimately this simply runs the following command:

    /bin/bash -c "ulimit -S -c 0 >/dev/null 2>&1 ; /usr/sbin/httpd"

So I am not sure why it does not ingerit the env variables... unless something before it removed them
So there's something at work that we can't see from this code.

I see Fishmonger mentioned trying a version of the BEGIN block with a re-exec. You noted that

$^X is path to the apache webserver at '/usr/sbin/httpd'

So that leads one to think that httpd, presumably for efficiency reasons, is not doing a normal exec of perl to create a new process for mod_perl but is instead simulating that action within its own address space somehow. And that means the re-exec method isn't an available, albeit inefficient, possible fix.

Some threads I've found have mentioned SELinux (Security Enhanced Linux) as a possible factor. Do you know if that might apply to you?
I don't have Security Enhanced Linux.

What I dont understand that if the daemon just ultimately calls bash to run a command via the -c parameter what is preventing it from inheriting the env vars?

It is just ugly that I have to start the web server manually instead of using the service command.
It is just ugly that I have to start the web server manually instead of using the service command.
I might have missed something in one of your prior posts, but are you saying that it works correctly if you manually start the httpd service via /usr/sbin/apachectl but fails when you start it via /sbin/service httpd?
Interesting. In the manpage for Apace mod_env, LD_LIBRARY_PATH is mentioned as the example for PassEnv.

http://httpd.apache.org/docs/current/mod/mod_env.html

And in this thread,

http://stackoverflow.com/questions/2550504/setting-ld-library-path-in-apache-passenv-setenv-still-cant-find-library

It's mentioned that under FastCGI, you have to set up the environment variables with DefaultInitEnv or FcgidInitialEnv.

We continue to look for something that's intervening to strip out environment variables.
When I run it via "service httpd start" it fails but if I do:

> export SYBASE=/appl/sybase
> export SYBASE_OCS=OCS-15_0
> export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib
> cd /usr/sbin
> httpd

Open in new window


It worked...

So I finally change the "/etc/rc.d/init.d/httpd" to:

start() {
        echo -n $"Starting $prog: "

        export SYBASE=/appl/sybase
        export SYBASE_OCS=OCS-15_0
        export LD_LIBRARY_PATH=/appl/sybase/OCS-15_0/lib

        # LANG=$HTTPD_LANG daemon --pidfile=${pidfile} $httpd $OPTIONS

        echo The environment contains...
        env

        cd /usr/sbin
        ./httpd

        RETVAL=$?
        echo
        [ $RETVAL = 0 ] && touch ${lockfile}
        return $RETVAL
}

Open in new window


Now "service httpd start" works and the server comes up when the system boots.
Appreciate all the research... still can't understand why it doesn't perform as documented....

My Final resolution is at the bottom.
So in the end, you just avoided using the "daemon" call, it looks like.
Indeed... Although it is the "daemon()" bash function in "/etc/rc.d/init.d/functions" which boiled down to:

     /bin/bash -c "ulimit -S -c 0 >/dev/null 2>&1 ; /usr/sbin/httpd"

So I don't know why it didn't work.... the new bash shell lost the variables...