# ex:ts=8 sw=4: # $OpenBSD: Shell.pm,v 1.20 2023/05/06 05:20:31 espie Exp $ # # Copyright (c) 2010-2014 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use v5.36; # the shell package is used to exec commands. # It works together with the Host class that actually # contains various properties (distant/local host, chrooted, running as root) # synopsis: # $host->shell->tweaks...->exec('cmd', 'args'...) # with tweaks being # ->chdir('dir') # ->env(VAR=>value...) # ->as_root(bool) # defaults to "true" # ->run_as('user') # ->nochroot # note that we're dealing with exec, so we can modify the object/context # itself with abandon package DPB::Shell::Abstract; # actual creation is done through # $host->shellclass->new($host) sub new($class, $host) { $host //= {}; # this makes it possible to build "localhost" shells # without any prop. return bless {as_root => 0, prop => $host->{prop}}, $class; } # the abstract class doesn't know how to exec anything # but it has accessors and tweakers sub prop($self) { return $self->{prop}; } sub is_alive($) { return 1; } sub stringize_master_pid($) { return ""; } sub chdir($self, $dir) { $self->{dir} = $dir; return $self; } sub env($self, %h) { while (my ($k, $v) = each %h) { $self->{env}{$k} = $v; } return $self; } sub as_root($self, $val = 1) { $self->{as_root} = $val; return $self; } sub run_as($self, $user) { $self->{user} = $user; return $self; } sub nochroot($self) { $self->{nochroot} = 1; return $self; } package DPB::Shell::Local; our @ISA = qw(DPB::Shell::Abstract); sub chdir($self, $dir) { CORE::chdir($dir) or DPB::Util->die_bang("Can't chdir to $dir"); return $self; } sub env($self, %h) { while (my ($k, $v) = each %h) { $ENV{$k} = $v; } return $self; } sub exec($self, @argv) { if ($self->{as_root}) { unshift(@argv, OpenBSD::Paths->doas); } if (-t STDIN) { close(STDIN); open STDIN, 'prop->{chroot}; if ($self->{nochroot}) { undef $chroot; } if ($self->prop->{iamroot}) { $chroot //= '/'; } unshift @argv, 'exec' unless $self->{as_root} && !$chroot; if ($self->{env}) { while (my ($k, $v) = each %{$self->{env}}) { $v //= ''; unshift @argv, "$k=\'$v\'"; } } if ($self->{as_root} && !$chroot) { unshift(@argv, 'exec', OpenBSD::Paths->doas, OpenBSD::Paths->env); } my $cmd = join(' ', @argv); if ($self->{dir}) { $cmd = "cd $self->{dir} && $cmd"; } if (defined $self->prop->{umask}) { my $umask = $self->prop->{umask}; $cmd = "umask $umask && $cmd"; } $self->{user} //= $self->prop->{build_user}; if ($chroot) { my @cmd2 = (OpenBSD::Paths->chroot); if (!$self->prop->{iamroot}) { unshift(@cmd2, OpenBSD::Paths->doas); } if (!$self->{as_root} && defined $self->{user}) { push(@cmd2, "-u", $self->{user}->user); } $self->_run(@cmd2, $chroot, "/bin/sh", "-c", $self->quote($cmd)); } else { $self->_run($cmd); } } package DPB::Shell::Local::Chroot; our @ISA = qw(DPB::Shell::Chroot); sub _run($self, @argv) { if (-t STDIN) { close(STDIN); open STDIN, ' = 0; $) = 0; $self->SUPER::exec(@argv); } sub nochroot($self) { $self->{nochroot} = 1; return $self; } 1;