in reply to Best way to write to a file owned by root?

Hello nysus,

Have you tried to execute the script with sudo?

Sample:

sudo hosts.pl

For me the following worked just fine:

#!/usr/bin/perl use strict; use warnings; use Data::Dumper; sub read_hosts { my $file = "/etc/hosts"; open (my $fh, "<", $file) or die "Can't open $file for read: $!"; my @hosts = <$fh>; close $fh or die "Cannot close $file: $!"; return @hosts; } sub write_hosts { my $file = "/etc/hosts"; open (my $fh, ">", $file) or die "Can't open $file for read: $!"; for my $host (@_) { print $fh $host; } # Or print $fh $_ for (@_); close $fh or die "Cannot close $file: $!"; return; } my @hosts = read_hosts; print Dumper \@hosts; $hosts[2] = "Test_host\t127.0.0.4\n"; write_hosts(@hosts); my @updates = read_hosts(); print Dumper \@updates; __END__ sudo perl hosts.pl $VAR1 = [ '127.0.0.1 localhost ', '127.0.1.1 user ', '# The following lines are desirable for IPv6 capable hosts ', '::1 ip6-localhost ip6-loopback ', 'fe00::0 ip6-localnet ', 'ff00::0 ip6-mcastprefix ', 'ff02::1 ip6-allnodes ', 'ff02::2 ip6-allrouters' ]; $VAR1 = [ '127.0.0.1 localhost ', '127.0.1.1 user ', 'Test_host 127.0.0.4 ', '::1 ip6-localhost ip6-loopback ', 'fe00::0 ip6-localnet ', 'ff00::0 ip6-mcastprefix ', 'ff02::1 ip6-allnodes ', 'ff02::2 ip6-allrouters' ];

Update: Adding print $fh $_ for (@_);

Update2: Or use Sudo module:

Make sure the script "hosts.pl" is executable chmod +x hosts.pl

#!/usr/bin/perl use strict; use warnings; use Sudo; my $su; my $name = "root"; my $pass = "password"; $su = Sudo->new( { sudo => '/usr/bin/sudo', #sudo_args => '...', username => $name, password => $pass, program => '/home/user/hosts.pl', #program_args => '...' } ); my $result = $su->sudo_run(); if (exists($result->{error})) { printf "STDERR: %s\n",$result->{error}; #&handle_error($result); } else { printf "STDOUT: %s\n",$result->{stdout}; printf "STDERR: %s\n",$result->{stderr}; printf "return: %s\n",$result->{rc}; } __END__ $ perl sudo.pl STDOUT: $VAR1 = [ '127.0.0.1 localhost ', '127.0.1.1 user ', ' ', '::1 ip6-localhost ip6-loopback ', 'fe00::0 ip6-localnet ', 'ff00::0 ip6-mcastprefix ', 'ff02::1 ip6-allnodes ', 'ff02::2 ip6-allrouters' ]; $VAR1 = [ '127.0.0.1 localhost ', '127.0.1.1 user ', 'Test_host 127.0.0.4 ', '::1 ip6-localhost ip6-loopback ', 'fe00::0 ip6-localnet ', 'ff00::0 ip6-mcastprefix ', 'ff02::1 ip6-allnodes ', 'ff02::2 ip6-allrouters' ]; STDERR: return: 1

Hope this helps.

Seeking for Perl wisdom...on the process of learning...not there...yet!

Replies are listed 'Best First'.
Re^2: Best way to write to a file owned by root?
by nysus (Parson) on Mar 13, 2017 at 22:51 UTC

    I did try running my script as sudo, yes, but I get errors because I have use statements for modules that the root user does not know about. Also, my script outputs files that I want to be owned by my system user, not root.

    The sudo module looks promising. Thanks! I will take a look at it and report my findings when I get a chance. Thanks for your help!

    $PM = "Perl Monk's";
    $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate";
    $nysus = $PM . ' ' . $MCF;
    Click here if you love Perl Monks

      I have use statements for modules that the root user does not know about.

      Using lib or a combination of FindBin and lib in the script should help. For details, see the documentation of those modules.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

      Hello again,

      I was thinking about the case that you where describing. "Best way to write to a file owned by root?"

      I did a bit of search on the web and there is an alternative. Give root privileges to an user just for a specific directory closed, the proposed solution is:

      Taken from the question:

      You do not need root permissions - you need group permissions. For example if the group is www-data, do something along the lines of usermod -a -G www-data misterX and make sure the files are group-writable.

      It is not bad as a solution to add your user in a group strictly to update the /etc/hosts file. But to be honest I do not know how vulnerable it becomes your file regarding security.

      Maybe another monk who has more knowledge regarding security issues can provide more information.

      But this could be a solution in avoiding using Sudo module.

      Hope this is a better solution.

      No matter what you decide at the end, post your solution here, it is interesting. :D

      Seeking for Perl wisdom...on the process of learning...not there...yet!

        System files like /etc/hosts are not owned by user root, group root (or bin) by chance, but intentionally. Making them writable for the webserver (or any other group) opens a way for remote file modification. One stupid bug in a CGI or a PHP script might be sufficient.

        A much cleaner approach would be a dedicated service (i.e. a daemon) whose sole purpose is to modify files as root. An unprivileged program (like a CGI, or a user program) contacts the service, and passes either a modification command or a completely new file. Typically, this would be done via a named pipe (FIFO) or a unix domain socket. The service does three things:

        1. Check if the client is allowed to change the file
        2. Check if the modification command / new file is valid
        3. Modify / replace the file as root

        The first check may prevent any user but the administrative webserver's run accound to modify files; it also prevents access to arbitary files. You usually don't want to allow anybody to overwrite /etc/passwd.

        The second check prevents garbage files that may make the system unusable.

        Together, this prevents direct and unverified modification by arbitary programs, without giving out privileges to an entire group of programs.

        A quite large system that uses this technique (Privilege separation) is postfix. Unlike sendmail, which runs as a monolithic setuid root binary, postfix uses various services, with as few privileges as possible.

        Update: Found a related PDF by Theo de Raadt of OpenBSD regarding privilege separation, enforced by the OpenBSD kernel.

        Alexander

        --
        Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        OK, thanks for your help. Below is what I came up with. However, it only works if I only recently entered my password with sudo from the command line. If it's been a while since logging in with sudo, I get this output from the script:

        STDOUT: STDERR: Password: return: STDOUT: STDERR: Password: return: STDOUT: STDERR: Password: return:

        So it doesn't look like the Sudo module is submitting the password properly. I'm on Mac.

        UPDATE! I fixed it by putting in a newline character after the password. It works! Thanks so much for your help. Code below modified to reflect the fix.

        use Modern::Perl; use Sudo; use File::Copy; my $tmp_file = '/Users/my_home/tmp/hosts'; my $target = '/etc/hosts'; my $user = 'root'; my $pass = "hardtoremember\n"; my $sudo = '/usr/bin/sudo'; my $uid = (stat $target)[4]; my $file_user = (getpwuid $uid)[0]; my $gid = (stat $target)[5]; my $file_group = (getgrgid $gid)[0]; my $mode = (stat($target))[2]; my $perms = sprintf("%04o", $mode & 07777); # copy file to tmp file unlink $tmp_file; copy ('/etc/hosts', $tmp_file); # append tmp file system ("echo '10.0.1.17 site.com' >> $tmp_file"); # change permissions my $su = create_cmd('/bin/chmod', "$perms $tmp_file"); check_output($su->sudo_run); # change file ownership $su = create_cmd('/usr/sbin/chown', "$file_user:$file_group $tmp_file" +); check_output($su->sudo_run); # copy modified file back $su = create_cmd('/bin/cp', "$tmp_file $target"); check_output($su->sudo_run); # cleanup unlink $tmp_file; sub check_output { my $result = shift; if (exists($result->{error})) { print $result->{error}; } else { printf "STDOUT: %s\n",$result->{stdout}; printf "STDERR: %s\n",$result->{stderr}; printf "return: %s\n",$result->{rc}; } } sub create_cmd { my $cmd = shift; my $args = shift; return Sudo->new( { sudo => $sudo, username => $user, password => $pass, program => $cmd, program_args => $args, } ); }

        $PM = "Perl Monk's";
        $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate";
        $nysus = $PM . ' ' . $MCF;
        Click here if you love Perl Monks