Ansible: Iterate through captured command output - perl

I am trying to convert an existing Perl script to Ansible role. I facing trouble in iterating over a captured command output.
Here is the Perl Script:
# Description: This script will adjust the oom score of all the important system processes to a negative value so that OOM killer does not touch these processes ############
chomp(my $OS = `uname`);
if($OS eq "Linux")
{
my #file = `ps -ef|egrep 'sssd|wdmd|portreserve|autofs|automount|ypbind|rpcbind|rpc.statd|rpc.mountd|rpc.idampd|ntpd|lmgrd|Xvnc|vncconfig|irqblance|rpc.rquotad|metric|nscd|crond|snpslmd|getpwname.pl|mysqld|rsyslogd|xinetd|sendmail|lsf|tigervnc|tightvnc|cfadm' |egrep -ve 'ps|egrep' |awk '{print \$8,\$2}'`;
chomp(#file);
foreach my $element (#file)
{
chomp($element);
(my $process, my $pid) = (split(/\s/,$element))[0,1];
print "($process)($pid)\n";
system("echo -17 > /proc/$pid/oom_adj");
system("cat /proc/$pid/oom_adj");
}
}
else
{
print "The host is a $OS system, so no action taken\n";
}
Here is what I have tried so far in Ansible:
---
- name: Capture uname ouput
shell: "uname"
register: os_type
- name: Adjust OOM to negative so that OOM killer does not kill below processes
shell: 'ps -ef|egrep "sssd|wdmd|portreserve|autofs|automount|ypbind|rpcbind|rpc.statd|rpc.mountd|rpc.idampd|ntpd|lmgrd|Xvnc|vncconfig|irqblance|rpc.rquotad|metric|nscd|crond|snpslmd|getpwname.pl|mysqld|rsyslogd|xinetd|sendmail|lsf|tigervnc|tightvnc|cfadm" |egrep -ve "ps|egrep" |awk "{print \$8,\$2}"'
register: oom
when: os_type.stdout == 'Linux'
- debug: var=oom.stdout_lines
Now, I want to iterate over var and implement this part in Ansible:
foreach my $element (#file)
{
chomp($element);
(my $process, my $pid) = (split(/\s/,$element))[0,1];
print "($process)($pid)\n";
system("echo -17 > /proc/$pid/oom_adj");
system("cat /proc/$pid/oom_adj");
}
Please help.

below worked for me
- hosts: temp
gather_facts: yes
remote_user: root
tasks:
- name: Adjust OOM to negative so that OOM killer does not kill below processes
shell: 'ps -ef|egrep "sssd|wdmd|portreserve|autofs|automount|ypbind|rpcbind|rpc.statd|rpc.mountd|rpc.idampd|ntpd|lmgrd|Xvnc|vncconfig|irqblance|rpc.rquotad|metric|nscd|crond|snpslmd|getpwname.pl|mysqld|rsyslogd|xinetd|sendmail|lsf|tigervnc|tightvnc|cfadm" |egrep -ve "ps|egrep" |awk "{print \$2}"'
register: oom
when: ansible_system == 'Linux'
- debug: var=oom.stdout
- name: update the pid
raw: echo -17 > /proc/{{ item }}/oom_adj
loop: "{{ oom.stdout_lines }}"

I was able to figure this out. Below is the solution that worked for me. Thanks to everyone who tried to help me out. Appreciate it :)
---
- name: Capture uname ouput
shell: "uname"
register: os_type
- name: Gather important processes
shell: 'ps -ef|egrep "sssd|wdmd|portreserve|autofs|automount|ypbind|rpcbind|rpc.statd|rpc.mountd|rpc.idampd|ntpd|lmgrd|Xvnc|vncconfig|irqblance|rpc.rquotad|metric|nscd|crond|snpslmd|getpwname.pl|mysqld|rsyslogd|xinetd|sendmail|lsf|tigervnc|tightvnc|cfadm" |egrep -ve "ps|egrep" |awk "{print \$8,\$2}"'
register: oom
when: os_type.stdout == 'Linux'
- name: Adjust OOM to negative so that OOM killer does not kill important processes
shell: "echo -17 >> /proc/{{ item.split()[1] }}/oom_adj"
loop: "{{ oom.stdout_lines }}"
register: echo
- set_fact:
stdout_lines: []
- set_fact:
stdout_lines: "{{ stdout_lines + item.stdout_lines }}"
with_items: "{{ echo.results }}"
- debug:
msg: "This is a stdout line: {{ item }}"
with_items: "{{ stdout_lines }}"

Related

how to replace a block of text with new block of text?

As the question title specifies , i have to replace a block to text in a file with a new block of text
I have searched all over for this thing but every solution i ever found was just too specific to the question. Isn't it possible to create a function which is flexible/reusable ?
To be very specific i need something which has options like
1) File ( where changes are to be done )
2) Exiting block of text
3) New block of text
( 2nd & 3 option could be either as manually pasted text or cat $somefile)
whereby i could change these 3 and use the script for all cases of text block replacement , i am sure it will help many other people too
As for an example , currently i need to replace the below block of text with one at bottom and say the file is $HOME/block.txt . Although i need the solution which is easily reusable/flexible as mentioned above
- name: Set default_volumes variable
set_fact:
default_volumes:
- "/opt/lidarr:/config"
- "/opt/scripts:/scripts"
- "/mnt:/mnt"
- "/mnt/unionfs/Media/Music:/music"
- name: Set default_volumes variable
set_fact:
default_volumes:
- "/opt/lidarr:/config"
- "/opt/scripts:/scripts"
- "/mnt:/mnt"
- "/mnt/unionfs/Media/Music:/music"
- "/mnt/unionfs/downloads/lidarr:/downloads-amd"
PS / while replacement i need the spacing and indentation to be preserved.
Your data is serialized using YAML. You should treat it as such.
Using yq
yq eval '
.[0].set_fact.default_volumes +=
[ "/mnt/unionfs/downloads/lidarr:/downloads-amd" ]
'
yq doesn't natively support in-place editing, but you can use sponge to achieve the same thing.
yq eval '
.[0].set_fact.default_volumes +=
[ "/mnt/unionfs/downloads/lidarr:/downloads-amd" ]
' a.yaml | sponge a.yaml
Using Perl
perl -MYAML -0777ne'
my $d = Load($_);
push #{ $d->[0]{set_fact}{default_volumes} },
"/mnt/unionfs/downloads/lidarr:/downloads-amd";
print Dump($d);
'
As per specifying file to process to Perl one-liner, editing in place would look like this:
perl -i -MYAML -0777ne'
my $d = Load($_);
push #{ $d->[0]{set_fact}{default_volumes} },
"/mnt/unionfs/downloads/lidarr:/downloads-amd";
print Dump($d);
' file.yaml
Using GNU awk for multi-char RS and ARGIND, this will work for any chars in your old or new text including regexp metachars, delimiters, quotes, and backreferences as it's just doing literal string search and replace:
awk -v RS='^$' -v ORS= '
ARGIND==1 { old=$0; next }
ARGIND==2 { new=$0; next }
s=index($0,old) {
$0 = substr($0,1,s-1) new substr($0,s+length(old))
}
1' old new file
or you can do the same using any awk in any shell on every Unix box with:
awk -v ORS= '
{ rec = (FNR>1 ? rec RS : "") $0 }
FILENAME==ARGV[1] { old=rec; next }
FILENAME==ARGV[2] { new=rec; next }
END {
$0 = rec
if ( s=index($0,old) ) {
$0 = substr($0,1,s-1) new substr($0,s+length(old))
}
print
}
' old new file
For example:
$ head old new file
==> old <==
- name: Set default_volumes variable
set_fact:
default_volumes:
- "/opt/lidarr:/config"
- "/opt/scripts:/scripts"
- "/mnt:/mnt"
- "/mnt/unionfs/Media/Music:/music"
==> new <==
- name: Set default_volumes variable
set_fact:
default_volumes:
- "/opt/lidarr:/config"
- "/opt/scripts:/scripts"
- "/mnt:/mnt"
- "/mnt/unionfs/Media/Music:/music"
- "/mnt/unionfs/downloads/lidarr:/downloads-amd"
==> file <==
foo
- name: Set default_volumes variable
set_fact:
default_volumes:
- "/opt/lidarr:/config"
- "/opt/scripts:/scripts"
- "/mnt:/mnt"
- "/mnt/unionfs/Media/Music:/music"
bar
$ awk -v RS='^$' -v ORS= 'ARGIND==1{old=$0; next} ARGIND==2{new=$0; next} s=index($0,old){ $0=substr($0,1,s-1) new substr($0,s+length(old))} 1' old new file
foo
- name: Set default_volumes variable
set_fact:
default_volumes:
- "/opt/lidarr:/config"
- "/opt/scripts:/scripts"
- "/mnt:/mnt"
- "/mnt/unionfs/Media/Music:/music"
- "/mnt/unionfs/downloads/lidarr:/downloads-amd"
bar
For a task like this, you could just use existing commands rather than
reinventing the wheel:
sed '/some text to change/,/with indentation/d; /a bit more/r new_file' your_file
I used two example files:
# original file
some original text to keep
a bit more
some text to remove
- with indentation
rest of original text
is kept
and:
# replacement text
SOME TEXT TO ADD
- WITH DIFFERENT INDENTATION
- ANOTHER LEVEL
Then the command works by first deleting lines between and including two
lines matching patterns:
sed '/some text to change/,/with indentation/d;
Then reading the replacement text from some other file, using a pattern
matching just where the old text used to start:
/a bit more/r new_file' your_file
To yield the result:
some original text to keep
a bit more
SOME TEXT TO ADD
- WITH DIFFERENT INDENTATION
- ANOTHER LEVEL
rest of original text
is kept
Edit
The above is better than my original way:
sed '/a bit more/q' your_file > composite; cat new_file >> composite; sed -n '/rest of original text/,/$/p' your_file >> composite

print the outcome of a step in github actions job

I'm trying to upload an artifact that logs the result of a mvn build. the code will explain better:
jobs:
job1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
...
- name: mvn-build
continue-on-error: true
run: |
mvn package ...
# This doesn't work because on mvn fail - the step is terminated with an error signal > 0
STATUS=$?
if [ $STATUS -eq 0 ]; then
echo 1 > runs/log.txt
else
echo 0 > runs/log.txt
fi
# This part does create the file (upload-artifact#v1) but the with an empty content
- name: print-result
env:
OUTCOME: ${{ steps.mvn-build.outcome }}
run: |
echo "$OUTCOME" > runs/log.txt
The job terminates because a command exits with a nonzero code. Just don't run that command at top level and you'll be fine!
jobs:
job1:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout#v2
...
- name: mvn-build
continue-on-error: true
run: |
if mvn package ... ; then
echo 1 > runs/log.txt
else
echo 0 > runs/log.txt
fi
# This part does create the file (upload-artifact#v1) but the with an empty content
- name: print-result
env:
OUTCOME: ${{ steps.mvn-build.outcome }}
run: |
echo "$OUTCOME" > runs/log.txt
more information on this bash behavior here: https://unix.stackexchange.com/a/22728/178425

How can we create two instances of memcached server in same server in different port?

I tried to add in the way
-l 11211
-l 11212
in memcached conf file. But it is just listening to first one i.e 1121
First I used mikewied's solution, but then I bumped into the problem of auto starting the daemon. Another confusing thing in that solution is that it doesn't use the config from etc. I was about to create my own start up scripts in /etc/init.d but then I looked into /etc/init.d/memcached file and saw this beautiful solution
# Usage:
# cp /etc/memcached.conf /etc/memcached_server1.conf
# cp /etc/memcached.conf /etc/memcached_server2.conf
# start all instances:
# /etc/init.d/memcached start
# start one instance:
# /etc/init.d/memcached start server1
# stop all instances:
# /etc/init.d/memcached stop
# stop one instance:
# /etc/init.d/memcached stop server1
# There is no "status" command.
Basically readers of this question just need to read the /etc/init.d/memcached file.
Cheers
Here's what memcached says the -l command is for:
-l <addr> interface to listen on (default: INADDR_ANY, all addresses)
<addr> may be specified as host:port. If you don't specify
a port number, the value you specified with -p or -U is
used. You may specify multiple addresses separated by comma
or by using -l multiple times
First off you need to specify the interface you want memcached to listen on if you are using the -l flag. Use 0.0.0.0 for all interfaces and use 127.0.0.1 is you just want to be able to access memcached from localhost. Second, don't use two -l flags. Use only one and separate each address by a comma. The command below should do what you want.
memcached -l 0.0.0.0:11211,0.0.0.0:11212
Keep in mind that this will have one memcached instance listen on two ports. To have two memcached instances on one machine run these two commands.
memcached -p 11211 -d
memcached -p 11212 -d
The answer from David Dzhagayev is the best one. If you don't have the correct version of memcache init script, here is the one he is talking about:
It should work with any linux distro using init.
#! /bin/bash
### BEGIN INIT INFO
# Provides: memcached
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Should-Start: $local_fs
# Should-Stop: $local_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Start memcached daemon
# Description: Start up memcached, a high-performance memory caching daemon
### END INIT INFO
# Usage:
# cp /etc/memcached.conf /etc/memcached_server1.conf
# cp /etc/memcached.conf /etc/memcached_server2.conf
# start all instances:
# /etc/init.d/memcached start
# start one instance:
# /etc/init.d/memcached start server1
# stop all instances:
# /etc/init.d/memcached stop
# stop one instance:
# /etc/init.d/memcached stop server1
# There is no "status" command.
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/usr/bin/memcached
DAEMONNAME=memcached
DAEMONBOOTSTRAP=/usr/share/memcached/scripts/start-memcached
DESC=memcached
test -x $DAEMON || exit 0
test -x $DAEMONBOOTSTRAP || exit 0
set -e
. /lib/lsb/init-functions
# Edit /etc/default/memcached to change this.
ENABLE_MEMCACHED=no
test -r /etc/default/memcached && . /etc/default/memcached
FILES=(/etc/memcached_*.conf)
# check for alternative config schema
if [ -r "${FILES[0]}" ]; then
CONFIGS=()
for FILE in "${FILES[#]}";
do
# remove prefix
NAME=${FILE#/etc/}
# remove suffix
NAME=${NAME%.conf}
# check optional second param
if [ $# -ne 2 ];
then
# add to config array
CONFIGS+=($NAME)
elif [ "memcached_$2" == "$NAME" ];
then
# use only one memcached
CONFIGS=($NAME)
break;
fi;
done;
if [ ${#CONFIGS[#]} == 0 ];
then
echo "Config not exist for: $2" >&2
exit 1
fi;
else
CONFIGS=(memcached)
fi;
CONFIG_NUM=${#CONFIGS[#]}
for ((i=0; i < $CONFIG_NUM; i++)); do
NAME=${CONFIGS[${i}]}
PIDFILE="/var/run/${NAME}.pid"
case "$1" in
start)
echo -n "Starting $DESC: "
if [ $ENABLE_MEMCACHED = yes ]; then
start-stop-daemon --start --quiet --exec "$DAEMONBOOTSTRAP" -- /etc/${NAME}.conf $PIDFILE
echo "$NAME."
else
echo "$NAME disabled in /etc/default/memcached."
fi
;;
stop)
echo -n "Stopping $DESC: "
start-stop-daemon --stop --quiet --oknodo --retry 5 --pidfile $PIDFILE --exec $DAEMON
echo "$NAME."
rm -f $PIDFILE
;;
restart|force-reload)
#
# If the "reload" option is implemented, move the "force-reload"
# option to the "reload" entry above. If not, "force-reload" is
# just the same as "restart".
#
echo -n "Restarting $DESC: "
start-stop-daemon --stop --quiet --oknodo --retry 5 --pidfile $PIDFILE
rm -f $PIDFILE
if [ $ENABLE_MEMCACHED = yes ]; then
start-stop-daemon --start --quiet --exec "$DAEMONBOOTSTRAP" -- /etc/${NAME}.conf $PIDFILE
echo "$NAME."
else
echo "$NAME disabled in /etc/default/memcached."
fi
;;
status)
status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
;;
*)
N=/etc/init.d/$NAME
echo "Usage: $N {start|stop|restart|force-reload|status}" >&2
exit 1
;;
esac
done;
exit 0
In case someone else stumbles upon this question, there is a bug on the debian distribution of memcached (which means flavours like Ubuntu would also be affected).
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=784357
Because of this bug, even when you have separate configuration files, when you run sudo service memcached restart, only the default configuration file in /etc/memcached.conf will be loaded.
As mentioned in the comment here, the temporary solution is to
Remove /lib/systemd/system/memcached.service
Run sudo systemctl daemon-reload (don't worry, it is safe to do
so)
Finally, run sudo service memcached restart if you are okay with losing all cache information. If not, run sudo service memcached force-reload
Ok, very good answer, Tristan CHARBONNIER.
Please replace code into file /usr/share/memcached/scripts/start-memcached:
#!/usr/bin/perl -w
# start-memcached
# 2003/2004 - Jay Bonci
# This script handles the parsing of the /etc/memcached.conf file
# and was originally created for the Debian distribution.
# Anyone may use this little script under the same terms as
# memcached itself.
use strict;
if($> != 0 and $< != 0)
{
print STDERR "Only root wants to run start-memcached.\n";
exit;
}
my $params; my $etchandle; my $etcfile = "/etc/memcached.conf";
# This script assumes that memcached is located at /usr/bin/memcached, and
# that the pidfile is writable at /var/run/memcached.pid
my $memcached = "/usr/bin/memcached";
my $pidfile = "/var/run/memcached.pid";
if (scalar(#ARGV) == 2) {
$etcfile = shift(#ARGV);
$pidfile = shift(#ARGV);
}
# If we don't get a valid logfile parameter in the /etc/memcached.conf file,
# we'll just throw away all of our in-daemon output. We need to re-tie it so
# that non-bash shells will not hang on logout. Thanks to Michael Renner for
# the tip
my $fd_reopened = "/dev/null";
sub handle_logfile
{
my ($logfile) = #_;
$fd_reopened = $logfile;
}
sub reopen_logfile
{
my ($logfile) = #_;
open *STDERR, ">>$logfile";
open *STDOUT, ">>$logfile";
open *STDIN, ">>/dev/null";
$fd_reopened = $logfile;
}
# This is set up in place here to support other non -[a-z] directives
my $conf_directives = {
"logfile" => \&handle_logfile,
};
if(open $etchandle, $etcfile)
{
foreach my $line (< $etchandle>)
{
$line ||= "";
$line =~ s/\#.*//g;
$line =~ s/\s+$//g;
$line =~ s/^\s+//g;
next unless $line;
next if $line =~ /^\-[dh]/;
if($line =~ /^[^\-]/)
{
my ($directive, $arg) = $line =~ /^(.*?)\s+(.*)/;
$conf_directives->{$directive}->($arg);
next;
}
push #$params, $line;
}
}else{
$params = [];
}
push #$params, "-u root" unless(grep "-u", #$params);
$params = join " ", #$params;
if(-e $pidfile)
{
open PIDHANDLE, "$pidfile";
my $localpid = <PIDHANDLE>;
close PIDHANDLE;
chomp $localpid;
if(-d "/proc/$localpid")
{
print STDERR "memcached is already running.\n";
exit;
}else{
`rm -f $localpid`;
}
}
my $pid = fork();
if($pid == 0)
{
reopen_logfile($fd_reopened);
exec "$memcached $params";
exit(0);
}else{
if(open PIDHANDLE,">$pidfile")
{
print PIDHANDLE $pid;
close PIDHANDLE;
}else{
print STDERR "Can't write pidfile to $pidfile.\n";
}
}
Simple solution to Centos 6
First copy /etc/sysconfig/memcached to /etc/sysconfig/memcached2 and write new settings to the new file.
Then copy /etc/init.d/memcached to /etc/init.d/memcached2 and change in the new file:
PORT to your new port (it should be reset from /etc/sysconfig/memcached2, so we do it just in case)
/etc/sysconfig/memcached to /etc/sysconfig/memcached2
/var/run/memcached/memcached.pid to /var/run/memcached/memcached2.pid
/var/lock/subsys/memcached to /var/lock/subsys/memcached2
Now you can use service memcached2 start, service memcached2 stop etc. Don't forget chkconfig memcached2 on to run it when machine boots up.
in /etc/memcached.conf you can just edit like below
-l 192.168.112.22,127.0.0.1
must use comma between two ip address

Daemon network process (perl) under Redhat RHEL5 is denying network connection

I wrote a program that is using the Perl POE Framework to realize a json webservice.
So far so good. I have no problems running that application under debian systems. But when i run my application under RHEL5 the network connection is refused. I have no setuid so the service is running as root and the port (9991) is out of the serviceport-range.
My workaround ist to start the application as nondaemon with nohup $CMD &. Thats more than stupid. But I've absolute no idea
my init.d script
DEBIAN:
#!/bin/sh -e
### BEGIN INIT INFO
# Provides: jobserver
# Required-Start: $local_fs $network $syslog
# Required-Stop: $local_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: start/stop jobserver
### END INIT INFO
APPLICATION_CONFIG="/etc/jobserver/jobserver.conf"
test -f $APPLICATION_CONFIG && . $APPLICATION_CONFIG
set -e
if [ ! -x $APPLICATION_PATH ] ; then
echo "No jobserver installed"
exit 0
fi
#load init.d helper functions
. /lib/lsb/init-functions
PIDFILE=$APPLICATION_PIDFILE
CONF=$APPLICATION_CONFIG
DAEMON=$APPLICATION_DAEMON
PARAMETER="--configuration $CONF --daemon"
if [ -z "$PIDFILE" ] ; then
echo "ERROR: APPLICATION_PIDFILE needs to be defined in application config" >&2
exit 2
fi
jobserver_start() {
#log_daemon_msg "Starting Jobserver Daemon"
log_success_msg "Starting Jobserver Daemon"
start-stop-daemon --start --quiet --oknodo --make-pidfile --pidfile "$PIDFILE" --exec "$DAEMON" -- $PARAMETER
#log_end_msg $?
}
jobserver_stop() {
log_success_msg "Stopping Jobserver Daemon"
start-stop-daemon --stop --quiet --oknodo --pidfile "${PIDFILE}"
rm -f "${PIDFILE}"
#log_end_msg $?
}
case $1 in
start)
jobserver_start
;;
stop)
jobserver_stop
;;
restart)
jobserver_stop
jobserver_start
;;
*)
log_success_msg "Usage: /etc/init.d/jobserver {start|stop|restart}"
exit 1
;;
esac
exit $?;
REDHAT:
# Source function library.
. /etc/rc.d/init.d/functions
APPLICATION_CONFIG="/etc/jobserver/jobserver.conf"
test -f $APPLICATION_CONFIG && . $APPLICATION_CONFIG
PIDFILE=$APPLICATION_PIDFILE
CONF=$APPLICATION_CONFIG
DAEMON=$APPLICATION_DAEMON
PARAMETER="--configuration $CONF --daemon"
RETVAL=0
if [ -z "$PIDFILE" ] ; then
echo "ERROR: APPLICATION_PIDFILE needs to be defined in application config" >&2
exit 2
fi
jobserver_start() {
echo -n $"Starting jobserver daemon: "
daemon --pidfile=$PIDFILE $DAEMON $PARAMETER
RETVAL=$?
if [ $RETVAL -ne 0 ]; then
failure;
fi;
echo
return $RETVAL
}
jobserver_stop() {
echo -n $"Stopping jobserver daemon: "
if [ ! -f $PIDFILE ]; then
echo -n $"Jobserver daemon is not running: ";
else
killproc -p $PIDFILE
RETVAL=$?
fi
echo
return $RETVAL;
}
case $1 in
start)
jobserver_start
;;
stop)
jobserver_stop
;;
restart)
jobserver_stop
jobserver_start
;;
*)
echo "Usage: /etc/init.d/jobserver {start|stop|restart}"
exit 1
;;
esac
exit $?;
Daemonprocess:
sub daemonize {
# start a new child process
if (fork()) {
exit(0);
}
# become process group leader
unless (POSIX::setsid) {
die("POSIX setsid failed: $!");
}
# change to root dir
chdir("/");
foreach (0 .. (POSIX::sysconf(&POSIX::_SC_OPEN_MAX) || 1024)) {
POSIX::close($_);
}
# allow only user based io
umask(077);
# reopen pipes
open(STDIN, "<", "/dev/null");
open(STDOUT, ">", "/dev/null");
open(STDERR, ">", "/dev/null");
# Advisory. Fork one more time; this is not "necessary" for most toolserver
# daemons but it is a best practice as there are situations where
# forking twice is needed to avoid zombies. A second fork also
# prevents the daemon from ever re-acquiring a terminal, by making
# the main daemon process not be the process group leader
if (fork()) {
exit(0);
}
# write pidfile
my $pidfile = "/var/run/jobserverd.pid";
open(PIDFILE, ">$pidfile");
print(PIDFILE "$$");
close(PIDFILE);
}
Serverfront:
sub listen {
my $self = shift;
logInfo("Starting webservice. Listening to port ".$self->{'_port'}, 1);
# Spawn the webservice
POE::Component::Server::HTTP->new (
Port => $self->{'_port'},
ContentHandler => {
"/json/" => \&dispatchJSONService
},
Headers => {
"Server" => "Perl JobServer version ".$self->{'_version'}
},
);
$poe_kernel->run();
}
Problem solved. The Problem was the double fork. Thanks to all for reading. Never trust advisories! ;)

taint-mode perl: preserve suid when running external program via system()

I'm trying to add a feature to a legacy script. The script is suid, and uses perl -T (taint mode: man perlsec), for extra security. The feature I need to add is implemented in Python.
My problem is that I can't convince perlsec to preserve the suid permissions, no matter how much I launder the environment and my command lines.
This is frustrating, since it preserves the suid for other binaries (such as /bin/id). Is there a undocumented special case for /usr/bin/perl? This seems unlikely.
Does anyone know a way to make this work? (As-is: We don't have the resources to re-architect this whole thing.)
Solution: (as per #gbacon)
# use the -p option to bash
system('/bin/bash', '-p', '-c', '/usr/bin/id -un');
# or set real user and group ids
$< = $>;
$( = $);
system('/usr/bin/python', '-c', 'import os; os.system("/usr/bin/id -un")');
Gives the desired results!
Here's a cut-down version of my script, which still shows my problem.
#!/usr/bin/perl -T
## This is an SUID script: man perlsec
%ENV = ( "PATH" => "" );
##### PERLSEC HELPERS #####
sub tainted (#) {
# Prevent errors, stringifying
local(#_, $#, $^W) = #_;
#let eval catch the DIE signal
$SIG{__DIE__} = '';
my $retval = not eval { join("",#_), kill 0; 1 };
$SIG{__DIE__} = 'myexit';
return $retval
}
sub show_taint {
foreach (#_) {
my $arg = $_; #prevent "read-only variable" nonsense
chomp $arg;
if ( tainted($arg) ) {
print "TAINT:'$arg'";
} else {
print "ok:'$arg'";
}
print ", ";
}
print "\n";
}
### END PERLSEC HELPERS ###
# Are we SUID ? man perlsec
my $uid = `/usr/bin/id --user` ;
chomp $uid;
my $reluser = "dt-pdrel";
my $reluid = `/usr/bin/id --user $reluser 2> /dev/null`;
chomp $reluid;
if ( $uid ne $reluid ) {
# what ? we are not anymore SUID ? somebody must do a chmod u+s $current_script
print STDERR "chmod 4555 $myname\n";
exit(14);
}
# comment this line if you don't want to autoflush after every print
$| = 1;
# now, we're safe, single & SUID
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# BEGIN of main code itself
print "\nENVIRON UNDER BASH:\n";
run('/bin/bash', '-c', '/bin/env');
print "\nTAINT DEMO:\n";
print "\#ARGV: ";
show_taint(#ARGV);
print "\%ENV: ";
show_taint(values %ENV);
print "`cat`: ";
show_taint(`/bin/cat /etc/host.conf`);
print "\nworks:\n";
run('/usr/bin/id', '-un');
run('/usr/bin/id -un');
print "\ndoesn't work:\n";
run('/bin/bash', '-c', '/usr/bin/id -un');
run('/bin/bash', '-c', '/bin/date >> /home/dt-pdrel/date');
run('/bin/date >> /home/dt-pdrel/date');
run('/usr/bin/python', '-c', 'import os; os.system("/usr/bin/id -un")');
run('/usr/bin/python', '-c', 'import os; os.system("/usr/bin/id -un")');
sub run {
my #cmd = #_;
print "\tCMD: '#cmd'\n";
print "\tSEC: ";
show_taint(#cmd);
print "\tOUT: ";
system #cmd ;
print "\n";
}
And here's the output:
$ id -un
bukzor
$ ls -l /proj/test/test.pl
-rwsr-xr-x 1 testrel asic 1976 Jul 22 14:34 /proj/test/test.pl*
$ /proj/test/test.pl foo bar
ENVIRON UNDER BASH:
CMD: '/bin/bash -c /bin/env'
SEC: ok:'/bin/bash', ok:'-c', ok:'/bin/env',
OUT: PATH=
PWD=/proj/test2/bukzor/test_dir/
SHLVL=1
_=/bin/env
TAINT DEMO:
#ARGV: TAINT:'foo', TAINT:'bar',
%ENV: ok:'',
`cat`: TAINT:'order hosts,bind',
works:
CMD: '/usr/bin/id -un'
SEC: ok:'/usr/bin/id', ok:'-un',
OUT: testrel
CMD: '/usr/bin/id -un'
SEC: ok:'/usr/bin/id -un',
OUT: testrel
doesn't work:
CMD: '/bin/bash -c /usr/bin/id -un'
SEC: ok:'/bin/bash', ok:'-c', ok:'/usr/bin/id -un',
OUT: bukzor
CMD: '/bin/bash -c /bin/date >> /home/testrel/date'
SEC: ok:'/bin/bash', ok:'-c', ok:'/bin/date >> /home/testrel/date',
OUT: /bin/bash: /home/testrel/date: Permission denied
CMD: '/bin/date >> /home/testrel/date'
SEC: ok:'/bin/date >> /home/testrel/date',
OUT: sh: /home/testrel/date: Permission denied
CMD: '/usr/bin/python -c import os; os.system("/usr/bin/id -un")'
SEC: ok:'/usr/bin/python', ok:'-c', ok:'import os; os.system("/usr/bin/id -un")',
OUT: bukzor
CMD: '/usr/bin/python -c import os; os.system("/usr/bin/id -un")'
SEC: ok:'/usr/bin/python', ok:'-c', ok:'import os; os.system("/usr/bin/id -un")',
OUT: bukzor
You need to set your real userid to the effective (suid-ed) one. You probably want to do the same for your real group id:
#! /usr/bin/perl -T
use warnings;
use strict;
$ENV{PATH} = "/bin:/usr/bin";
system "id -un";
system "/bin/bash", "-c", "id -un";
# set real user and group ids
$< = $>;
$( = $);
system "/bin/bash", "-c", "id -un";
Sample run:
$ ls -l suid.pl
-rwsr-sr-x 1 nobody nogroup 177 2010-07-22 20:33 suid.pl
$ ./suid.pl
nobody
gbacon
nobody
What you're seeing is documented bash behavior:
-p
Turn on privileged mode. In this mode, the $BASH_ENV and $ENV files are not processed, shell functions are not inherited from the environment, and the SHELLOPTS, BASHOPTS, CDPATH and GLOBIGNORE variables, if they appear in the environment, are ignored. If the shell is started with the effective user (group) id not equal to the real user (group) id, and the -p option is not supplied, these actions are taken and the effective user id is set to the real user id. If the -p option is supplied at startup, the effective user id is not reset. Turning this option off causes the effective user and group ids to be set to the real user and group ids.
This means you could also
#! /usr/bin/perl -T
use warnings;
use strict;
$ENV{PATH} = "/bin:/usr/bin";
system "/bin/bash", "-p", "-c", "id -un";
to get
nobody
Recall that passing multiple arguments to system bypasses the shell. A single argument does go to the shell, but probably not bash—look at the output of perl -MConfig -le 'print $Config{sh}'.