Add support for the arbitrary named jail parameters used by jail_set(2)
and jail_get(2). Jail(8) can now create jails using a "name=value" format instead of just specifying a limited set of fixed parameters; it can also modify parameters of existing jails. Jls(8) can display all parameters of jails, or a specified set of parameters. The available parameters are gathered from the kernel, and not hard-coded into these programs. Small patches on killall(1) and jexec(8) to support jail names with jail_get(2). Approved by: bz (mentor)
This commit is contained in:
parent
0304c73163
commit
73d0971bf2
Notes:
svn2git
2020-12-20 02:59:44 +00:00
svn path=/head/; revision=192896
@ -86,17 +86,9 @@ struct xprison {
|
||||
};
|
||||
#define XPRISON_VERSION 3
|
||||
|
||||
static const struct prison_state {
|
||||
int pr_state;
|
||||
const char * state_name;
|
||||
} prison_states[] = {
|
||||
#define PRISON_STATE_INVALID 0
|
||||
{ PRISON_STATE_INVALID, "INVALID" },
|
||||
#define PRISON_STATE_ALIVE 1
|
||||
{ PRISON_STATE_ALIVE, "ALIVE" },
|
||||
#define PRISON_STATE_DYING 2
|
||||
{ PRISON_STATE_DYING, "DYING" },
|
||||
};
|
||||
|
||||
/*
|
||||
* Flags for jail_set and jail_get.
|
||||
|
@ -24,7 +24,7 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd November 9, 2007
|
||||
.Dd May 27, 2009
|
||||
.Os
|
||||
.Dt KILLALL 1
|
||||
.Sh NAME
|
||||
@ -34,7 +34,7 @@
|
||||
.Nm
|
||||
.Op Fl delmsvz
|
||||
.Op Fl help
|
||||
.Op Fl j Ar jid
|
||||
.Op Fl j Ar jail
|
||||
.Op Fl u Ar user
|
||||
.Op Fl t Ar tty
|
||||
.Op Fl c Ar procname
|
||||
@ -91,9 +91,9 @@ The signal may be specified either as a name
|
||||
(with or without a leading
|
||||
.Dq Li SIG ) ,
|
||||
or numerically.
|
||||
.It Fl j Ar jid
|
||||
Kill processes in the jail specified by
|
||||
.Ar jid .
|
||||
.It Fl j Ar jail
|
||||
Kill processes in the specified
|
||||
.Ar jail .
|
||||
.It Fl u Ar user
|
||||
Limit potentially matching processes to those belonging to
|
||||
the specified
|
||||
|
@ -31,6 +31,7 @@ __FBSDID("$FreeBSD$");
|
||||
#include <sys/param.h>
|
||||
#include <sys/jail.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <fcntl.h>
|
||||
@ -51,7 +52,7 @@ static void __dead2
|
||||
usage(void)
|
||||
{
|
||||
|
||||
fprintf(stderr, "usage: killall [-delmsvz] [-help] [-j jid]\n");
|
||||
fprintf(stderr, "usage: killall [-delmsvz] [-help] [-j jail]\n");
|
||||
fprintf(stderr,
|
||||
" [-u user] [-t tty] [-c cmd] [-SIGNAL] [cmd]...\n");
|
||||
fprintf(stderr, "At least one option or argument to specify processes must be given.\n");
|
||||
@ -100,6 +101,7 @@ nosig(char *name)
|
||||
int
|
||||
main(int ac, char **av)
|
||||
{
|
||||
struct iovec jparams[2];
|
||||
struct kinfo_proc *procs = NULL, *newprocs;
|
||||
struct stat sb;
|
||||
struct passwd *pw;
|
||||
@ -159,12 +161,21 @@ main(int ac, char **av)
|
||||
}
|
||||
jflag++;
|
||||
if (*av == NULL)
|
||||
errx(1, "must specify jid");
|
||||
jid = strtol(*av, &ep, 10);
|
||||
if (!*av || *ep)
|
||||
errx(1, "illegal jid: %s", *av);
|
||||
errx(1, "must specify jail");
|
||||
jid = strtoul(*av, &ep, 10);
|
||||
if (!**av || *ep) {
|
||||
*(const void **)&jparams[0].iov_base =
|
||||
"name";
|
||||
jparams[0].iov_len = sizeof("name");
|
||||
jparams[1].iov_base = *av;
|
||||
jparams[1].iov_len = strlen(*av) + 1;
|
||||
jid = jail_get(jparams, 2, 0);
|
||||
if (jid < 0)
|
||||
errx(1, "unknown jail: %s",
|
||||
*av);
|
||||
}
|
||||
if (jail_attach(jid) == -1)
|
||||
err(1, "jail_attach(): %d", jid);
|
||||
err(1, "jail_attach(%d)", jid);
|
||||
break;
|
||||
case 'u':
|
||||
++*av;
|
||||
|
@ -1,5 +1,6 @@
|
||||
.\"
|
||||
.\" Copyright (c) 2000, 2003 Robert N. M. Watson
|
||||
.\" Copyright (c) 2008 James Gritton
|
||||
.\" All rights reserved.
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
@ -33,49 +34,67 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd January 24, 2009
|
||||
.Dd May 27, 2009
|
||||
.Dt JAIL 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm jail
|
||||
.Nd "imprison process and its descendants"
|
||||
.Nd "create or modify a system jail"
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dhi
|
||||
.Op Fl J Ar jid_file
|
||||
.Op Fl l u Ar username | Fl U Ar username
|
||||
.Op Fl c | m
|
||||
.Op Ar parameter=value ...
|
||||
.Br
|
||||
.Nm
|
||||
.Op Fl hi
|
||||
.Op Fl n Ar jailname
|
||||
.Op Fl J Ar jid_file
|
||||
.Op Fl s Ar securelevel
|
||||
.Op Fl l u Ar username | Fl U Ar username
|
||||
.Ar path hostname [ip[,..]] command ...
|
||||
.Op Ar path hostname [ip[,..]] command ...
|
||||
.Br
|
||||
.Nm
|
||||
.Op Fl r Ar jail
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
utility imprisons a process and all future descendants.
|
||||
utility creates a new jail or modifies an existing jail, optionally
|
||||
imprisoning the current process (and future descendants) inside it.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width ".Fl u Ar username"
|
||||
.Bl -tag -width indent
|
||||
.It Fl d
|
||||
Allow making changes to a
|
||||
.Va
|
||||
dying jail.
|
||||
.It Fl h
|
||||
Resolve
|
||||
.Va hostname
|
||||
Resolve the
|
||||
.Va host.hostname
|
||||
parameter (or
|
||||
.Va hostname )
|
||||
and add all IP addresses returned by the resolver
|
||||
to the list of
|
||||
.Va ip-addresses
|
||||
for this prison.
|
||||
.Va ip
|
||||
addresses for this prison.
|
||||
This may affect default address selection for outgoing IPv4 connections
|
||||
of prisons.
|
||||
The address first returned by the resolver for each address family
|
||||
will be used as primary address.
|
||||
See
|
||||
.Va ip-addresses
|
||||
further down for details.
|
||||
See the
|
||||
.Va ip4.addr
|
||||
and
|
||||
.Va ip6.addr
|
||||
parameters further down for details.
|
||||
.It Fl i
|
||||
Output the jail identifier of the newly created jail.
|
||||
.It Fl n Ar jailname
|
||||
Assign and administrative name to the jail that can be used for management
|
||||
or auditing purposes.
|
||||
The system will
|
||||
.Sy not enforce
|
||||
the name to be unique.
|
||||
Set the jail's name.
|
||||
This is deprecated and is equivalent to setting the
|
||||
.Va name
|
||||
parameter.
|
||||
.It Fl J Ar jid_file
|
||||
Write a
|
||||
.Ar jid_file
|
||||
@ -98,9 +117,12 @@ is imported from the current environment.
|
||||
The environment variables from the login class capability database for the
|
||||
target login are also set.
|
||||
.It Fl s Ar securelevel
|
||||
Sets the
|
||||
Set the
|
||||
.Va kern.securelevel
|
||||
sysctl variable to the specified value inside the newly created jail.
|
||||
MIB entry to the specified value inside the newly created jail.
|
||||
This is deprecated and is equivalent to setting the
|
||||
.Va securelevel
|
||||
parameter.
|
||||
.It Fl u Ar username
|
||||
The user name from host environment as whom the
|
||||
.Ar command
|
||||
@ -109,20 +131,241 @@ should run.
|
||||
The user name from jailed environment as whom the
|
||||
.Ar command
|
||||
should run.
|
||||
.It Ar path
|
||||
.It Fl c
|
||||
Create a new jail.
|
||||
The
|
||||
.Va jid
|
||||
and
|
||||
.Va name
|
||||
parameters (if specified) must not refer to an existing jail.
|
||||
.It Fl m
|
||||
Modify an existing jail.
|
||||
One of the
|
||||
.Va jid
|
||||
or
|
||||
.Va name
|
||||
parameters must exist and refer to an existing jail.
|
||||
.It Fl cm
|
||||
Create a jail if it does not exist, or modify a jail if it does exist.
|
||||
.It Fl r
|
||||
Remove the
|
||||
.Ar jail
|
||||
specified by jid or name.
|
||||
All jailed processes are killed, and all children of this jail are also
|
||||
removed.
|
||||
.El
|
||||
.Pp
|
||||
At least one of the
|
||||
.Fl c ,
|
||||
.Fl m
|
||||
or
|
||||
.Fl r
|
||||
options must be specified.
|
||||
.Pp
|
||||
.Ar Parameters
|
||||
are listed in
|
||||
.Dq name=value
|
||||
form, following the options.
|
||||
Some parameters are boolean, and do not have a value but are set by the
|
||||
name alone with or without a
|
||||
.Dq no
|
||||
prefix, e.g.
|
||||
.Va persist
|
||||
or
|
||||
.Va nopersist .
|
||||
Any parameters not set will be given default values, often based on the
|
||||
current environment.
|
||||
.Pp
|
||||
The pseudo-parameter
|
||||
.Va command
|
||||
specifies that the current process should enter the new (or modified) jail,
|
||||
and run the specified command.
|
||||
It must be the last parameter specified, because it includes not only
|
||||
the value following the
|
||||
.Sq =
|
||||
sign, but also passes the rest of the arguments to the command.
|
||||
.Pp
|
||||
Instead of supplying named
|
||||
.Ar parameters ,
|
||||
four fixed parameters may be supplied in order on the command line:
|
||||
.Ar path ,
|
||||
.Ar hostname ,
|
||||
.Ar ip ,
|
||||
and
|
||||
.Ar command .
|
||||
As the
|
||||
.Va jid
|
||||
and
|
||||
.Va name
|
||||
parameters aren't in this list, this mode will always create a new jail, and
|
||||
the
|
||||
.Fl c
|
||||
and
|
||||
.Fl o
|
||||
options don't apply (and must not exist).
|
||||
.Pp
|
||||
Jails have a set a core parameters, and modules can add their own jail
|
||||
parameters.
|
||||
The current set of available parameters can be retrieved via
|
||||
.Dq Nm sysctl Fl d Va security.jail.param .
|
||||
The core parameters are:
|
||||
.Bl -tag -width indent
|
||||
.It Va jid
|
||||
The jail identifier.
|
||||
This will be assigned automatically to a new jail (or can be explicitly
|
||||
set), and can be used to identify the jail for later modification, or
|
||||
for such commands as
|
||||
.Xr jls 8
|
||||
or
|
||||
.Xr jexec 8 .
|
||||
.It Va name
|
||||
The jail name.
|
||||
This is an arbitrary string that identifies a jail (except it may not
|
||||
contain a
|
||||
.Sq \&. ) .
|
||||
Like the
|
||||
.Va jid ,
|
||||
it can be passed to later
|
||||
.Nm
|
||||
commands, or to
|
||||
.Xr jls 8
|
||||
or
|
||||
.Xr jexec 8 .
|
||||
If no
|
||||
.Va name
|
||||
is supplied, a default is assumed that is the same as the
|
||||
.Va jid .
|
||||
.It Va path
|
||||
Directory which is to be the root of the prison.
|
||||
.It Ar hostname
|
||||
Hostname of the prison.
|
||||
.It Ar ip-addresses
|
||||
None, one or more IPv4 and IPv6 addresses assigned to the prison.
|
||||
The first address of each address family that was assigned to the jail will
|
||||
be used as the source address in case source address selection on unbound
|
||||
sockets cannot find a better match.
|
||||
The
|
||||
.Va command
|
||||
(if any) is run from this directory, as are commands from
|
||||
.Xr jexec 8 .
|
||||
.It Va ip4.addr
|
||||
A comma-separated list of IPv4 addresses assigned to the prison.
|
||||
If this is set, the jail is restricted to using only these address.
|
||||
Any attempts to use other addresses fail, and attempts to use wildcard
|
||||
addresses silently use the jailed address instead.
|
||||
For IPv4 the first address given will be kept used as the source address
|
||||
in case source address selection on unbound sockets cannot find a better
|
||||
match.
|
||||
It is only possible to start multiple jails with the same IP address,
|
||||
if none of the jails has more than this single overlapping IP address
|
||||
assigned to itself for the address family in question.
|
||||
.It Ar command
|
||||
Pathname of the program which is to be executed.
|
||||
assigned to itself.
|
||||
.Pp
|
||||
A list of zero elements (an empty string) will stop the jail from using IPv4
|
||||
entirely; setting the boolean parameter
|
||||
.Ar noip4
|
||||
will not restrict the jail at all.
|
||||
.It Va ip6.addr
|
||||
A list of IPv6 addresses assigned to the prison, the counterpart to
|
||||
.Ar ip4.addr
|
||||
above.
|
||||
.It Va host.hostname
|
||||
Hostname of the prison.
|
||||
If not specified, a jail will use the system hostname.
|
||||
.It Va securelevel
|
||||
The value of the jail's
|
||||
.Va kern.securelevel
|
||||
sysctl.
|
||||
A jail never has a lower securelevel than the default system, but by
|
||||
setting this parameter it may have a higher one.
|
||||
If the system securelevel is changed, any jail securelevels will be at
|
||||
least as secure.
|
||||
.It Va enforce_statfs
|
||||
This determines which information processes in a jail are able to get
|
||||
about mount points.
|
||||
It affects the behaviour of the following syscalls:
|
||||
.Xr statfs 2 ,
|
||||
.Xr fstatfs 2 ,
|
||||
.Xr getfsstat 2
|
||||
and
|
||||
.Xr fhstatfs 2
|
||||
(as well as similar compatibility syscalls).
|
||||
When set to 0, all mount points are available without any restrictions.
|
||||
When set to 1, only mount points below the jail's chroot directory are
|
||||
visible.
|
||||
In addition to that, the path to the jail's chroot directory is removed
|
||||
from the front of their pathnames.
|
||||
When set to 2 (default), above syscalls can operate only on a mount-point
|
||||
where the jail's chroot directory is located.
|
||||
.It Va persist
|
||||
Setting this boolean parameter allows a jail to exist without any
|
||||
processes.
|
||||
Normally, a jail is destroyed as its last process exits.
|
||||
A new jail must have either the
|
||||
.Va persist
|
||||
parameter or
|
||||
.Va command
|
||||
pseudo-parameter set.
|
||||
.It Va cpuset
|
||||
The ID of the cpuset associated with this jail (read-only).
|
||||
.It Va dying
|
||||
This is true if the jail is in the process of shutting down (read-only).
|
||||
.It Va parent
|
||||
The
|
||||
.Va jid
|
||||
of the parent of this jail, or zero if this is a top-level jail
|
||||
(read-only).
|
||||
.It Va allow.*
|
||||
Some restrictions of the jail environment may be set on a per-jail
|
||||
basis.
|
||||
With the exception of
|
||||
.Va allow.set_hostname ,
|
||||
these boolean parameters are off by default.
|
||||
.Bl -tag -width indent
|
||||
.It Va allow.set_hostname
|
||||
The jail's hostname may be changed via
|
||||
.Xr hostname 1
|
||||
or
|
||||
.Xr sethostname 3 .
|
||||
.It Va allow.sysvipc
|
||||
A process within the jail has access to System V IPC primitives.
|
||||
In the current jail implementation, System V primitives share a single
|
||||
namespace across the host and jail environments, meaning that processes
|
||||
within a jail would be able to communicate with (and potentially interfere
|
||||
with) processes outside of the jail, and in other jails.
|
||||
.It Va allow.raw_sockets
|
||||
The prison root is allowed to create raw sockets.
|
||||
Setting this parameter allows utilities like
|
||||
.Xr ping 8
|
||||
and
|
||||
.Xr traceroute 8
|
||||
to operate inside the prison.
|
||||
If this is set, the source IP addresses are enforced to comply
|
||||
with the IP address bound to the jail, regardless of whether or not
|
||||
the
|
||||
.Dv IP_HDRINCL
|
||||
flag has been set on the socket.
|
||||
Since raw sockets can be used to configure and interact with various
|
||||
network subsystems, extra caution should be used where privileged access
|
||||
to jails is given out to untrusted parties.
|
||||
.It Va allow.chflags
|
||||
Normally, priveleged users inside a jail are treated as unprivileged by
|
||||
.Xr chflags 2 .
|
||||
When this parameter is set, such users are treated as privileged, and
|
||||
may manipulate system file flags subject to the usual constraints on
|
||||
.Va kern.securelevel .
|
||||
.It Va allow.mount
|
||||
privileged users inside the jail will be able to mount and unmount file
|
||||
system types marked as jail-friendly.
|
||||
The
|
||||
.Xr lsvfs 1
|
||||
command can be used to find file system types available for mount from
|
||||
within a jail.
|
||||
.It Va allow.quotas
|
||||
The prison root may administer quotas on the jail's filesystem(s).
|
||||
This includes filesystems that the jail may share with other jails or
|
||||
with non-jailed parts of the system.
|
||||
.It Va allow.socket_af
|
||||
Sockets within a jail are normally restricted to IPv4, IPv6, local
|
||||
(UNIX), and route. This allows access to other protocol stacks that
|
||||
have not had jail functionality added to them.
|
||||
.It Va allow.jails
|
||||
The prison root may create child jails under this jail. See the
|
||||
.Va "Hierarchical Jails"
|
||||
section for more information.
|
||||
.El
|
||||
.El
|
||||
.Pp
|
||||
Jails are typically set up using one of two philosophies: either to
|
||||
@ -142,10 +385,6 @@ process.
|
||||
This manual page documents the configuration steps necessary to support
|
||||
either of these steps, although the configuration steps may be
|
||||
refined based on local requirements.
|
||||
.Pp
|
||||
Please see the
|
||||
.Xr jail 2
|
||||
man page for further details.
|
||||
.Sh EXAMPLES
|
||||
.Ss "Setting up a Jail Directory Tree"
|
||||
To set up a jail directory tree containing an entire
|
||||
@ -289,7 +528,10 @@ or for running a virtual server.
|
||||
.Pp
|
||||
Start a shell in the jail:
|
||||
.Pp
|
||||
.Dl "jail /data/jail/192.0.2.100 testhostname 192.0.2.100 /bin/sh"
|
||||
.Bd -literal -offset indent
|
||||
jail path=/data/jail/192.0.2.100 host.hostname=testhostname \\
|
||||
ip4.addr=192.0.2.100 command=/bin/sh
|
||||
.Ed
|
||||
.Pp
|
||||
Assuming no errors, you will end up with a shell prompt within the jail.
|
||||
You can now run
|
||||
@ -359,20 +601,11 @@ To do this, first bring up the
|
||||
virtual host interface, and then start the jail's
|
||||
.Pa /etc/rc
|
||||
script from within the jail.
|
||||
.Pp
|
||||
NOTE: If you plan to allow untrusted users to have root access inside the
|
||||
jail, you may wish to consider setting the
|
||||
.Va security.jail.set_hostname_allowed
|
||||
sysctl variable to 0.
|
||||
Please see the management discussion later in this document as to why this
|
||||
may be a good idea.
|
||||
If you do decide to set this variable,
|
||||
it must be set before starting any jails, and once each boot.
|
||||
.Bd -literal -offset indent
|
||||
ifconfig ed0 inet alias 192.0.2.100/32
|
||||
mount -t procfs proc /data/jail/192.0.2.100/proc
|
||||
jail /data/jail/192.0.2.100 testhostname 192.0.2.100 \\
|
||||
/bin/sh /etc/rc
|
||||
jail path=/data/jail/192.0.2.100 host.hostname=testhostname \\
|
||||
ip4=addr.192.0.2.100 command=/bin/sh /etc/rc
|
||||
.Ed
|
||||
.Pp
|
||||
A few warnings will be produced, because most
|
||||
@ -442,10 +675,15 @@ To kill processes from outside the jail, use the
|
||||
utility in conjunction with the one of the
|
||||
.Xr kill 1
|
||||
commands above.
|
||||
You may also remove the jail with
|
||||
.Nm
|
||||
.Ar -r ,
|
||||
which will killall the jail's processes with
|
||||
.Dv SIGKILL .
|
||||
.Pp
|
||||
The
|
||||
.Pa /proc/ Ns Ar pid Ns Pa /status
|
||||
file contains, as its last field, the hostname of the jail in which the
|
||||
file contains, as its last field, the name of the jail in which the
|
||||
process runs, or
|
||||
.Dq Li -
|
||||
to indicate that the process is not running within a jail.
|
||||
@ -454,20 +692,6 @@ The
|
||||
command also shows a
|
||||
.Ql J
|
||||
flag for processes in a jail.
|
||||
However, the hostname for a jail may be, by
|
||||
default, modified from within the jail, so the
|
||||
.Pa /proc
|
||||
status entry is unreliable by default.
|
||||
To disable the setting of the hostname
|
||||
from within a jail, set the
|
||||
.Va security.jail.set_hostname_allowed
|
||||
sysctl variable in the host environment to 0, which will affect all jails.
|
||||
You can have this sysctl set on each boot using
|
||||
.Xr sysctl.conf 5 .
|
||||
Just add the following line to
|
||||
.Pa /etc/sysctl.conf :
|
||||
.Pp
|
||||
.Dl security.jail.set_hostname_allowed=0
|
||||
.Pp
|
||||
You can also list/kill processes based on their jail ID.
|
||||
To show processes and their jail ID, use the following command:
|
||||
@ -488,12 +712,9 @@ It is not possible to
|
||||
or
|
||||
.Xr umount 8
|
||||
any file system inside a jail unless the file system is marked
|
||||
jail-friendly.
|
||||
See
|
||||
.Va security.jail.mount_allowed
|
||||
in the
|
||||
.Va "Sysctl MIB Entries"
|
||||
section.
|
||||
jail-friendly and the jail's
|
||||
.Va allow.mount
|
||||
parameter is set.
|
||||
.Pp
|
||||
Multiple jails sharing the same file system can influence each other.
|
||||
For example a user in one jail can fill the file system also
|
||||
@ -506,132 +727,77 @@ This means the same user ID in two jails share the same file
|
||||
system quota.
|
||||
One would need to use one file system per jail to make this working.
|
||||
.Ss "Sysctl MIB Entries"
|
||||
Certain aspects of the jail containments environment may be modified from
|
||||
the host environment using
|
||||
.Xr sysctl 8
|
||||
MIB variables.
|
||||
Currently, these variables affect all jails on the system, although in
|
||||
the future this functionality may be finer grained.
|
||||
.Bl -tag -width XXX
|
||||
.It Va security.jail.allow_raw_sockets
|
||||
This MIB entry determines whether or not prison root is allowed to
|
||||
create raw sockets.
|
||||
Setting this MIB to 1 allows utilities like
|
||||
.Xr ping 8
|
||||
and
|
||||
.Xr traceroute 8
|
||||
to operate inside the prison.
|
||||
If this MIB
|
||||
is set, the source IP addresses are enforced to comply
|
||||
with the IP address bound to the jail, regardless of whether or not
|
||||
the
|
||||
.Dv IP_HDRINCL
|
||||
flag has been set on the socket.
|
||||
Since raw sockets can be used to configure
|
||||
and interact with various network subsystems, extra caution should be used
|
||||
where privileged access to jails is given out to untrusted parties.
|
||||
As such,
|
||||
by default this option is disabled.
|
||||
.It Va security.jail.enforce_statfs
|
||||
This MIB entry determines which information processes in a jail are
|
||||
able to get about mount-points.
|
||||
It affects the behaviour of the following syscalls:
|
||||
.Xr statfs 2 ,
|
||||
.Xr fstatfs 2 ,
|
||||
.Xr getfsstat 2
|
||||
and
|
||||
.Xr fhstatfs 2
|
||||
(as well as similar compatibility syscalls).
|
||||
When set to 0, all mount-points are available without any restrictions.
|
||||
When set to 1, only mount-points below the jail's chroot directory are
|
||||
visible.
|
||||
In addition to that, the path to the jail's chroot directory is removed
|
||||
from the front of their pathnames.
|
||||
When set to 2 (default), above syscalls can operate only on a mount-point
|
||||
where the jail's chroot directory is located.
|
||||
.It Va security.jail.set_hostname_allowed
|
||||
This MIB entry determines whether or not processes within a jail are
|
||||
allowed to change their hostname via
|
||||
.Xr hostname 1
|
||||
or
|
||||
.Xr sethostname 3 .
|
||||
In the current jail implementation, the ability to set the hostname from
|
||||
within the jail can impact management tools relying on the accuracy of jail
|
||||
information in
|
||||
.Pa /proc .
|
||||
As such, this should be disabled in environments where privileged access to
|
||||
jails is given out to untrusted parties.
|
||||
.It Va security.jail.socket_unixiproute_only
|
||||
The jail functionality binds an IPv4 address to each jail, and limits
|
||||
access to other network addresses in the IPv4 space that may be available
|
||||
in the host environment.
|
||||
However, jail is not currently able to limit access to other network
|
||||
protocol stacks that have not had jail functionality added to them.
|
||||
As such, by default, processes within jails may only access protocols
|
||||
in the following domains:
|
||||
.Dv PF_LOCAL , PF_INET ,
|
||||
and
|
||||
.Dv PF_ROUTE ,
|
||||
permitting them access to
|
||||
.Ux
|
||||
domain sockets,
|
||||
IPv4 addresses, and routing sockets.
|
||||
To enable access to other domains, this MIB variable may be set to
|
||||
0.
|
||||
.It Va security.jail.sysvipc_allowed
|
||||
This MIB entry determines whether or not processes within a jail have access
|
||||
to System V IPC primitives.
|
||||
In the current jail implementation, System V primitives share a single
|
||||
namespace across the host and jail environments, meaning that processes
|
||||
within a jail would be able to communicate with (and potentially interfere
|
||||
with) processes outside of the jail, and in other jails.
|
||||
As such, this functionality is disabled by default, but can be enabled
|
||||
by setting this MIB entry to 1.
|
||||
.It Va security.jail.chflags_allowed
|
||||
This MIB entry determines how a privileged user inside a jail will be
|
||||
treated by
|
||||
.Xr chflags 2 .
|
||||
If zero, such users are treated as unprivileged, and are unable to set
|
||||
or clear system file flags; if non-zero, such users are treated as
|
||||
privileged, and may manipulate system file flags subject to the usual
|
||||
constraints on
|
||||
.Va kern.securelevel .
|
||||
.It Va security.jail.mount_allowed
|
||||
This MIB entry determines if a privileged user inside a jail will be
|
||||
able to mount and unmount file system types marked as jail-friendly.
|
||||
The
|
||||
.Xr lsvfs 1
|
||||
command can be used to find file system types available for mount from within
|
||||
a jail.
|
||||
This functionality is disabled by default, but can be enabled by setting this
|
||||
MIB entry to 1.
|
||||
.It Va security.jail.jail_max_af_ips
|
||||
This MIB entry determines how may address per address family a prison
|
||||
may have. The default is 255.
|
||||
.El
|
||||
.Pp
|
||||
The read-only sysctl variable
|
||||
The read-only entry
|
||||
.Va security.jail.jailed
|
||||
can be used to determine if a process is running inside a jail (value
|
||||
is one) or not (value is zero).
|
||||
.Pp
|
||||
The
|
||||
.Va security.jail.list
|
||||
MIB entry is read-only and it returns an array of
|
||||
.Vt "struct xprison"
|
||||
defined in
|
||||
.In sys/jail.h .
|
||||
It is recommended to use the
|
||||
.Xr jls 8
|
||||
utility to see current active list of jails.
|
||||
The variable
|
||||
.Va security.jail.max_af_ips
|
||||
determines how may address per address family a prison may have.
|
||||
The default is 255.
|
||||
.Pp
|
||||
There are currently two MIB related variables that have per-jail settings.
|
||||
There are currently two MIB variables that have per-jail settings.
|
||||
Changes to these variables by a jailed process do not effect the host
|
||||
environment, only the jail environment.
|
||||
The variables are
|
||||
.Va kern.securelevel
|
||||
and
|
||||
.Va kern.hostname .
|
||||
.Ss "Hierarchical Jails"
|
||||
By setting a jail's
|
||||
.Va allow.jails
|
||||
parameter, processes within a jail may be able to create jails of their own.
|
||||
These child jails are kept in a hierarchy, with jails only able to see and/or
|
||||
modify the jails they created (or those jails' children).
|
||||
Each jail has a read-only
|
||||
.Va parent
|
||||
parameter, containing the
|
||||
.Va jid
|
||||
of the jail that created it; a
|
||||
.Va jid
|
||||
of 0 indicates the jail is a child of the current jail (or is a top-level
|
||||
jail if the current process isn't jailed).
|
||||
.Pp
|
||||
Jailed processes are not allowed to confer greater permissions than they
|
||||
themselves are given, e.g. if a jail is created with
|
||||
.Va allow.nomount ,
|
||||
it is not able to create a jail with
|
||||
.Va allow.mount
|
||||
set.
|
||||
Similarly, such restrictions as
|
||||
.Va ip4.addr
|
||||
and
|
||||
.Va securelevel
|
||||
may not be bypassed in child jails.
|
||||
.Pp
|
||||
A child jail may in turn create its own child jails if its own
|
||||
.Va allow.jails
|
||||
parameter is set (remember it is off by default).
|
||||
These jails are visible to and can be modified by their parent and all
|
||||
ancestors.
|
||||
.Pp
|
||||
Jail names reflect this hierarchy, with a full name being an MIB-type string
|
||||
separated by dots.
|
||||
For example, if a base system process creates a jail
|
||||
.Dq foo ,
|
||||
and a process under that jail creates another jail
|
||||
.Dq bar ,
|
||||
then the second jail will be seen as
|
||||
.Dq foo.bar
|
||||
in the base system (though it is only seen as
|
||||
.Dq bar
|
||||
to any processes inside jail
|
||||
.Dq foo ) .
|
||||
Jids on the other hand exist in a single space, and each jail must have a
|
||||
unique jid.
|
||||
.Pp
|
||||
Like the names, a child jail's
|
||||
.Va path
|
||||
is relative to its creator's own
|
||||
.Va path .
|
||||
This is by virtue of the child jail being created in the chrooted
|
||||
environment of the first jail.
|
||||
.Sh SEE ALSO
|
||||
.Xr killall 1 ,
|
||||
.Xr lsvfs 1 ,
|
||||
@ -641,7 +807,7 @@ and
|
||||
.Xr ps 1 ,
|
||||
.Xr quota 1 ,
|
||||
.Xr chroot 2 ,
|
||||
.Xr jail 2 ,
|
||||
.Xr jail_set 2 ,
|
||||
.Xr jail_attach 2 ,
|
||||
.Xr procfs 5 ,
|
||||
.Xr rc.conf 5 ,
|
||||
@ -665,6 +831,8 @@ The
|
||||
.Nm
|
||||
utility appeared in
|
||||
.Fx 4.0 .
|
||||
Hierarchical/extensible jails were introduced in
|
||||
.Fx 8.0 .
|
||||
.Sh AUTHORS
|
||||
.An -nosplit
|
||||
The jail feature was written by
|
||||
@ -683,6 +851,9 @@ added multi-IP jail support for IPv4 and IPv6 based on a patch
|
||||
originally done by
|
||||
.An Pawel Jakub Dawidek
|
||||
for IPv4.
|
||||
.Pp
|
||||
.An James Gritton
|
||||
added the extensible jail parameters and hierchical jails.
|
||||
.Sh BUGS
|
||||
Jail currently lacks the ability to allow access to
|
||||
specific jail information via
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*-
|
||||
* Copyright (c) 1999 Poul-Henning Kamp.
|
||||
* Copyright (c) 2009 James Gritton
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -29,51 +30,54 @@ __FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/jail.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <grp.h>
|
||||
#include <login_cap.h>
|
||||
#include <netdb.h>
|
||||
#include <paths.h>
|
||||
#include <pwd.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static void usage(void);
|
||||
static int add_addresses(struct addrinfo *);
|
||||
static struct in_addr *copy_addr4(void);
|
||||
#define SJPARAM "security.jail.param"
|
||||
#define ERRMSG_SIZE 256
|
||||
|
||||
struct param {
|
||||
struct iovec name;
|
||||
struct iovec value;
|
||||
};
|
||||
|
||||
static struct param *params;
|
||||
static char **param_values;
|
||||
static int nparams;
|
||||
|
||||
static char *ip4_addr;
|
||||
#ifdef INET6
|
||||
static struct in6_addr *copy_addr6(void);
|
||||
static char *ip6_addr;
|
||||
#endif
|
||||
|
||||
static void add_ip_addr(char **addrp, char *newaddr);
|
||||
#ifdef INET6
|
||||
static void add_ip_addr46(char *newaddr);
|
||||
#endif
|
||||
static void add_ip_addrinfo(int ai_flags, char *value);
|
||||
static void quoted_print(FILE *fp, char *str);
|
||||
static void set_param(const char *name, char *value);
|
||||
static void usage(void);
|
||||
|
||||
extern char **environ;
|
||||
|
||||
struct addr4entry {
|
||||
STAILQ_ENTRY(addr4entry) addr4entries;
|
||||
struct in_addr ip4;
|
||||
int count;
|
||||
};
|
||||
struct addr6entry {
|
||||
STAILQ_ENTRY(addr6entry) addr6entries;
|
||||
#ifdef INET6
|
||||
struct in6_addr ip6;
|
||||
#endif
|
||||
int count;
|
||||
};
|
||||
STAILQ_HEAD(addr4head, addr4entry) addr4 = STAILQ_HEAD_INITIALIZER(addr4);
|
||||
STAILQ_HEAD(addr6head, addr6entry) addr6 = STAILQ_HEAD_INITIALIZER(addr6);
|
||||
|
||||
#define GET_USER_INFO do { \
|
||||
pwd = getpwnam(username); \
|
||||
if (pwd == NULL) { \
|
||||
@ -94,25 +98,28 @@ int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
login_cap_t *lcap = NULL;
|
||||
struct jail j;
|
||||
struct iovec rparams[2];
|
||||
struct passwd *pwd = NULL;
|
||||
gid_t groups[NGROUPS];
|
||||
int ch, error, i, ngroups, securelevel;
|
||||
int hflag, iflag, Jflag, lflag, uflag, Uflag;
|
||||
char path[PATH_MAX], *jailname, *ep, *username, *JidFile, *ip;
|
||||
int ch, cmdarg, i, jail_set_flags, jid, ngroups;
|
||||
int hflag, iflag, Jflag, lflag, rflag, uflag, Uflag;
|
||||
char *ep, *jailname, *securelevel, *username, *JidFile;
|
||||
char errmsg[ERRMSG_SIZE];
|
||||
static char *cleanenv;
|
||||
const char *shell, *p = NULL;
|
||||
long ltmp;
|
||||
FILE *fp;
|
||||
struct addrinfo hints, *res0;
|
||||
|
||||
hflag = iflag = Jflag = lflag = uflag = Uflag = 0;
|
||||
securelevel = -1;
|
||||
jailname = username = JidFile = cleanenv = NULL;
|
||||
hflag = iflag = Jflag = lflag = rflag = uflag = Uflag =
|
||||
jail_set_flags = 0;
|
||||
cmdarg = jid = -1;
|
||||
jailname = securelevel = username = JidFile = cleanenv = NULL;
|
||||
fp = NULL;
|
||||
|
||||
while ((ch = getopt(argc, argv, "hiln:s:u:U:J:")) != -1) {
|
||||
while ((ch = getopt(argc, argv, "cdhilmn:r:s:u:U:J:")) != -1) {
|
||||
switch (ch) {
|
||||
case 'd':
|
||||
jail_set_flags |= JAIL_DYING;
|
||||
break;
|
||||
case 'h':
|
||||
hflag = 1;
|
||||
break;
|
||||
@ -127,10 +134,7 @@ main(int argc, char **argv)
|
||||
jailname = optarg;
|
||||
break;
|
||||
case 's':
|
||||
ltmp = strtol(optarg, &ep, 0);
|
||||
if (*ep || ep == optarg || ltmp > INT_MAX || !ltmp)
|
||||
errx(1, "invalid securelevel: `%s'", optarg);
|
||||
securelevel = ltmp;
|
||||
securelevel = optarg;
|
||||
break;
|
||||
case 'u':
|
||||
username = optarg;
|
||||
@ -143,13 +147,39 @@ main(int argc, char **argv)
|
||||
case 'l':
|
||||
lflag = 1;
|
||||
break;
|
||||
case 'c':
|
||||
jail_set_flags |= JAIL_CREATE;
|
||||
break;
|
||||
case 'm':
|
||||
jail_set_flags |= JAIL_UPDATE;
|
||||
break;
|
||||
case 'r':
|
||||
jid = strtoul(optarg, &ep, 10);
|
||||
if (!*optarg || *ep) {
|
||||
*(const void **)&rparams[0].iov_base = "name";
|
||||
rparams[0].iov_len = sizeof("name");
|
||||
rparams[1].iov_base = optarg;
|
||||
rparams[1].iov_len = strlen(optarg) + 1;
|
||||
jid = jail_get(rparams, 2, 0);
|
||||
if (jid < 0)
|
||||
errx(1, "unknown jail: %s", optarg);
|
||||
}
|
||||
rflag = 1;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
if (argc < 4)
|
||||
if (rflag) {
|
||||
if (argc > 0 || iflag || Jflag || lflag || uflag || Uflag)
|
||||
usage();
|
||||
if (jail_remove(jid) < 0)
|
||||
err(1, "jail_remove");
|
||||
exit (0);
|
||||
}
|
||||
if (argc == 0)
|
||||
usage();
|
||||
if (uflag && Uflag)
|
||||
usage();
|
||||
@ -157,92 +187,118 @@ main(int argc, char **argv)
|
||||
usage();
|
||||
if (uflag)
|
||||
GET_USER_INFO;
|
||||
if (realpath(argv[0], path) == NULL)
|
||||
err(1, "realpath: %s", argv[0]);
|
||||
if (chdir(path) != 0)
|
||||
err(1, "chdir: %s", path);
|
||||
/* Initialize struct jail. */
|
||||
memset(&j, 0, sizeof(j));
|
||||
j.version = JAIL_API_VERSION;
|
||||
j.path = path;
|
||||
j.hostname = argv[1];
|
||||
if (jailname != NULL)
|
||||
j.jailname = jailname;
|
||||
|
||||
/* Handle IP addresses. If requested resolve hostname too. */
|
||||
bzero(&hints, sizeof(struct addrinfo));
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if (JAIL_API_VERSION < 2)
|
||||
hints.ai_family = PF_INET;
|
||||
else
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
/* Handle hostname. */
|
||||
if (hflag != 0) {
|
||||
error = getaddrinfo(j.hostname, NULL, &hints, &res0);
|
||||
if (error != 0)
|
||||
errx(1, "failed to handle hostname: %s",
|
||||
gai_strerror(error));
|
||||
error = add_addresses(res0);
|
||||
freeaddrinfo(res0);
|
||||
if (error != 0)
|
||||
errx(1, "failed to add addresses.");
|
||||
/*
|
||||
* If the first argument (path) starts with a slash, and the third
|
||||
* argument (IP address) starts with a digit, it is likely to be
|
||||
* an old-style fixed-parameter command line.
|
||||
*/
|
||||
if (jailname)
|
||||
set_param("name", jailname);
|
||||
if (securelevel)
|
||||
set_param("securelevel", securelevel);
|
||||
if (jail_set_flags) {
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (!strncmp(argv[i], "command=", 8)) {
|
||||
cmdarg = i;
|
||||
argv[cmdarg] += 8;
|
||||
jail_set_flags |= JAIL_ATTACH;
|
||||
break;
|
||||
}
|
||||
/* Handle IP addresses. */
|
||||
hints.ai_flags = AI_NUMERICHOST;
|
||||
ip = strtok(argv[2], ",");
|
||||
while (ip != NULL) {
|
||||
error = getaddrinfo(ip, NULL, &hints, &res0);
|
||||
if (error != 0)
|
||||
errx(1, "failed to handle ip: %s", gai_strerror(error));
|
||||
error = add_addresses(res0);
|
||||
freeaddrinfo(res0);
|
||||
if (error != 0)
|
||||
errx(1, "failed to add addresses.");
|
||||
ip = strtok(NULL, ",");
|
||||
}
|
||||
/* Count IP addresses and add them to struct jail. */
|
||||
if (!STAILQ_EMPTY(&addr4)) {
|
||||
j.ip4s = STAILQ_FIRST(&addr4)->count;
|
||||
j.ip4 = copy_addr4();
|
||||
if (j.ip4s > 0 && j.ip4 == NULL)
|
||||
errx(1, "copy_addr4()");
|
||||
if (hflag) {
|
||||
if (!strncmp(argv[i], "ip4.addr=", 9)) {
|
||||
add_ip_addr(&ip4_addr, argv[i] + 9);
|
||||
break;
|
||||
}
|
||||
#ifdef INET6
|
||||
if (!STAILQ_EMPTY(&addr6)) {
|
||||
j.ip6s = STAILQ_FIRST(&addr6)->count;
|
||||
j.ip6 = copy_addr6();
|
||||
if (j.ip6s > 0 && j.ip6 == NULL)
|
||||
errx(1, "copy_addr6()");
|
||||
if (!strncmp(argv[i], "ip6.addr=", 9)) {
|
||||
add_ip_addr(&ip6_addr, argv[i] + 9);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
if (!strncmp(argv[i], "host.hostname=", 14))
|
||||
add_ip_addrinfo(0, argv[i] + 14);
|
||||
}
|
||||
set_param(NULL, argv[i]);
|
||||
}
|
||||
} else {
|
||||
if (argc < 4 || argv[0][0] != '/')
|
||||
errx(1, "%s\n%s",
|
||||
"no -c or -m, so this must be an old-style command.",
|
||||
"But it doesn't look like one.");
|
||||
set_param("path", argv[0]);
|
||||
set_param("host.hostname", argv[1]);
|
||||
if (hflag)
|
||||
add_ip_addrinfo(0, argv[1]);
|
||||
#ifdef INET6
|
||||
add_ip_addr46(argv[2]);
|
||||
#else
|
||||
add_ip_addr(&ip4_addr, argv[2]);
|
||||
#endif
|
||||
cmdarg = 3;
|
||||
}
|
||||
if (ip4_addr != NULL)
|
||||
set_param("ip4.addr", ip4_addr);
|
||||
#ifdef INET6
|
||||
if (ip6_addr != NULL)
|
||||
set_param("ip6.addr", ip6_addr);
|
||||
#endif
|
||||
errmsg[0] = 0;
|
||||
set_param("errmsg", errmsg);
|
||||
|
||||
if (Jflag) {
|
||||
fp = fopen(JidFile, "w");
|
||||
if (fp == NULL)
|
||||
errx(1, "Could not create JidFile: %s", JidFile);
|
||||
}
|
||||
i = jail(&j);
|
||||
if (i == -1)
|
||||
err(1, "syscall failed with");
|
||||
jid = jail_set(¶ms->name, 2 * nparams,
|
||||
jail_set_flags ? jail_set_flags : JAIL_CREATE | JAIL_ATTACH);
|
||||
if (jid < 0) {
|
||||
if (errmsg[0] != '\0')
|
||||
errx(1, "%s", errmsg);
|
||||
err(1, "jail_set");
|
||||
}
|
||||
if (iflag) {
|
||||
printf("%d\n", i);
|
||||
printf("%d\n", jid);
|
||||
fflush(stdout);
|
||||
}
|
||||
if (Jflag) {
|
||||
if (fp != NULL) {
|
||||
fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
|
||||
i, j.path, j.hostname, argv[2], argv[3]);
|
||||
(void)fclose(fp);
|
||||
if (jail_set_flags) {
|
||||
fprintf(fp, "jid=%d", jid);
|
||||
for (i = 0; i < nparams; i++)
|
||||
if (strcmp(params[i].name.iov_base, "jid") &&
|
||||
strcmp(params[i].name.iov_base, "errmsg")) {
|
||||
fprintf(fp, " %s",
|
||||
(char *)params[i].name.iov_base);
|
||||
if (param_values[i]) {
|
||||
putc('=', fp);
|
||||
quoted_print(fp,
|
||||
param_values[i]);
|
||||
}
|
||||
}
|
||||
fprintf(fp, "\n");
|
||||
} else {
|
||||
errx(1, "Could not write JidFile: %s", JidFile);
|
||||
for (i = 0; i < nparams; i++)
|
||||
if (!strcmp(params[i].name.iov_base, "path"))
|
||||
break;
|
||||
#ifdef INET6
|
||||
fprintf(fp, "%d\t%s\t%s\t%s%s%s\t%s\n",
|
||||
jid, i < nparams
|
||||
? (char *)params[i].value.iov_base : argv[0],
|
||||
argv[1], ip4_addr ? ip4_addr : "",
|
||||
ip4_addr && ip4_addr[0] && ip6_addr && ip6_addr[0]
|
||||
? "," : "", ip6_addr ? ip6_addr : "", argv[3]);
|
||||
#else
|
||||
fprintf(fp, "%d\t%s\t%s\t%s\t%s\n",
|
||||
jid, i < nparams
|
||||
? (char *)params[i].value.iov_base : argv[0],
|
||||
argv[1], ip4_addr ? ip4_addr : "", argv[3]);
|
||||
#endif
|
||||
}
|
||||
(void)fclose(fp);
|
||||
}
|
||||
if (securelevel > 0) {
|
||||
if (sysctlbyname("kern.securelevel", NULL, 0, &securelevel,
|
||||
sizeof(securelevel)))
|
||||
err(1, "Can not set securelevel to %d", securelevel);
|
||||
}
|
||||
if (cmdarg < 0)
|
||||
exit(0);
|
||||
if (username != NULL) {
|
||||
if (Uflag)
|
||||
GET_USER_INFO;
|
||||
@ -272,158 +328,328 @@ main(int argc, char **argv)
|
||||
if (p)
|
||||
setenv("TERM", p, 1);
|
||||
}
|
||||
if (execv(argv[3], argv + 3) != 0)
|
||||
err(1, "execv: %s", argv[3]);
|
||||
exit(0);
|
||||
execvp(argv[cmdarg], argv + cmdarg);
|
||||
err(1, "execvp: %s", argv[cmdarg]);
|
||||
}
|
||||
|
||||
static void
|
||||
add_ip_addr(char **addrp, char *value)
|
||||
{
|
||||
int addrlen;
|
||||
char *addr;
|
||||
|
||||
if (!*addrp) {
|
||||
*addrp = strdup(value);
|
||||
if (!*addrp)
|
||||
err(1, "malloc");
|
||||
} else if (value[0]) {
|
||||
addrlen = strlen(*addrp) + strlen(value) + 2;
|
||||
addr = malloc(addrlen);
|
||||
if (!addr)
|
||||
err(1, "malloc");
|
||||
snprintf(addr, addrlen, "%s,%s", *addrp, value);
|
||||
free(*addrp);
|
||||
*addrp = addr;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef INET6
|
||||
static void
|
||||
add_ip_addr46(char *value)
|
||||
{
|
||||
char *p, *np;
|
||||
|
||||
if (!value[0]) {
|
||||
add_ip_addr(&ip4_addr, value);
|
||||
add_ip_addr(&ip6_addr, value);
|
||||
return;
|
||||
}
|
||||
for (p = value;; p = np + 1)
|
||||
{
|
||||
np = strchr(p, ',');
|
||||
if (np)
|
||||
*np = '\0';
|
||||
add_ip_addrinfo(AI_NUMERICHOST, p);
|
||||
if (!np)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
add_ip_addrinfo(int ai_flags, char *value)
|
||||
{
|
||||
struct addrinfo hints, *ai0, *ai;
|
||||
struct in_addr addr4;
|
||||
int error;
|
||||
char avalue4[INET_ADDRSTRLEN];
|
||||
#ifdef INET6
|
||||
struct in6_addr addr6;
|
||||
char avalue6[INET6_ADDRSTRLEN];
|
||||
#endif
|
||||
|
||||
/* Look up the hostname (or get the address) */
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
#ifdef INET6
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
#else
|
||||
hints.ai_family = PF_INET;
|
||||
#endif
|
||||
hints.ai_flags = ai_flags;
|
||||
error = getaddrinfo(value, NULL, &hints, &ai0);
|
||||
if (error != 0)
|
||||
errx(1, "hostname %s: %s", value, gai_strerror(error));
|
||||
|
||||
/* Convert the addresses to ASCII so set_param can convert them back. */
|
||||
for (ai = ai0; ai; ai = ai->ai_next)
|
||||
switch (ai->ai_family) {
|
||||
case AF_INET:
|
||||
memcpy(&addr4, &((struct sockaddr_in *)
|
||||
(void *)ai->ai_addr)->sin_addr, sizeof(addr4));
|
||||
if (inet_ntop(AF_INET, &addr4, avalue4,
|
||||
INET_ADDRSTRLEN) == NULL)
|
||||
err(1, "inet_ntop");
|
||||
add_ip_addr(&ip4_addr, avalue4);
|
||||
break;
|
||||
#ifdef INET6
|
||||
case AF_INET6:
|
||||
memcpy(&addr6, &((struct sockaddr_in6 *)
|
||||
(void *)ai->ai_addr)->sin6_addr, sizeof(addr6));
|
||||
if (inet_ntop(AF_INET6, &addr6, avalue6,
|
||||
INET6_ADDRSTRLEN) == NULL)
|
||||
err(1, "inet_ntop");
|
||||
add_ip_addr(&ip6_addr, avalue6);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
freeaddrinfo(ai0);
|
||||
}
|
||||
|
||||
static void
|
||||
quoted_print(FILE *fp, char *str)
|
||||
{
|
||||
int c, qc;
|
||||
char *p = str;
|
||||
|
||||
/* An empty string needs quoting. */
|
||||
if (!*p) {
|
||||
fputs("\"\"", fp);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The value will be surrounded by quotes if it contains spaces
|
||||
* or quotes.
|
||||
*/
|
||||
qc = strchr(p, '\'') ? '"'
|
||||
: strchr(p, '"') ? '\''
|
||||
: strchr(p, ' ') || strchr(p, '\t') ? '"'
|
||||
: 0;
|
||||
if (qc)
|
||||
putc(qc, fp);
|
||||
while ((c = *p++)) {
|
||||
if (c == '\\' || c == qc)
|
||||
putc('\\', fp);
|
||||
putc(c, fp);
|
||||
}
|
||||
if (qc)
|
||||
putc(qc, fp);
|
||||
}
|
||||
|
||||
static void
|
||||
set_param(const char *name, char *value)
|
||||
{
|
||||
struct param *param;
|
||||
char *ep, *p;
|
||||
size_t buflen, mlen;
|
||||
int i, nval, mib[CTL_MAXNAME];
|
||||
struct {
|
||||
int i;
|
||||
char s[MAXPATHLEN];
|
||||
} buf;
|
||||
|
||||
static int paramlistsize;
|
||||
|
||||
/* Separate the name from the value, if not done already. */
|
||||
if (name == NULL) {
|
||||
name = value;
|
||||
if ((value = strchr(value, '=')))
|
||||
*value++ = '\0';
|
||||
}
|
||||
|
||||
/* Check for repeat parameters */
|
||||
for (i = 0; i < nparams; i++)
|
||||
if (!strcmp(name, params[i].name.iov_base)) {
|
||||
memcpy(params + i, params + i + 1,
|
||||
(--nparams - i) * sizeof(struct param));
|
||||
break;
|
||||
}
|
||||
|
||||
/* Make sure there is room for the new param record. */
|
||||
if (!nparams) {
|
||||
paramlistsize = 32;
|
||||
params = malloc(paramlistsize * sizeof(*params));
|
||||
param_values = malloc(paramlistsize * sizeof(*param_values));
|
||||
if (params == NULL || param_values == NULL)
|
||||
err(1, "malloc");
|
||||
} else if (nparams >= paramlistsize) {
|
||||
paramlistsize *= 2;
|
||||
params = realloc(params, paramlistsize * sizeof(*params));
|
||||
param_values = realloc(param_values,
|
||||
paramlistsize * sizeof(*param_values));
|
||||
if (params == NULL)
|
||||
err(1, "realloc");
|
||||
}
|
||||
|
||||
/* Look up the paramter. */
|
||||
param_values[nparams] = value;
|
||||
param = params + nparams++;
|
||||
*(const void **)¶m->name.iov_base = name;
|
||||
param->name.iov_len = strlen(name) + 1;
|
||||
/* Trivial values - no value or errmsg. */
|
||||
if (value == NULL) {
|
||||
param->value.iov_base = NULL;
|
||||
param->value.iov_len = 0;
|
||||
return;
|
||||
}
|
||||
if (!strcmp(name, "errmsg")) {
|
||||
param->value.iov_base = value;
|
||||
param->value.iov_len = ERRMSG_SIZE;
|
||||
return;
|
||||
}
|
||||
mib[0] = 0;
|
||||
mib[1] = 3;
|
||||
snprintf(buf.s, sizeof(buf.s), SJPARAM ".%s", name);
|
||||
mlen = sizeof(mib) - 2 * sizeof(int);
|
||||
if (sysctl(mib, 2, mib + 2, &mlen, buf.s, strlen(buf.s)) < 0)
|
||||
errx(1, "unknown parameter: %s", name);
|
||||
mib[1] = 4;
|
||||
buflen = sizeof(buf);
|
||||
if (sysctl(mib, (mlen / sizeof(int)) + 2, &buf, &buflen, NULL, 0) < 0)
|
||||
err(1, "sysctl(0.4.%s)", name);
|
||||
/*
|
||||
* See if this is an array type.
|
||||
* Treat non-arrays as an array of one.
|
||||
*/
|
||||
p = strchr(buf.s, '\0');
|
||||
nval = 1;
|
||||
if (p - 2 >= buf.s && !strcmp(p - 2, ",a")) {
|
||||
if (value[0] == '\0' ||
|
||||
(value[0] == '-' && value[1] == '\0')) {
|
||||
param->value.iov_base = value;
|
||||
param->value.iov_len = 0;
|
||||
return;
|
||||
}
|
||||
p[-2] = 0;
|
||||
for (p = strchr(value, ','); p; p = strchr(p + 1, ',')) {
|
||||
*p = '\0';
|
||||
nval++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the values according to the parameter type. */
|
||||
switch (buf.i & CTLTYPE) {
|
||||
case CTLTYPE_INT:
|
||||
case CTLTYPE_UINT:
|
||||
param->value.iov_len = nval * sizeof(int);
|
||||
break;
|
||||
case CTLTYPE_LONG:
|
||||
case CTLTYPE_ULONG:
|
||||
param->value.iov_len = nval * sizeof(long);
|
||||
break;
|
||||
case CTLTYPE_STRUCT:
|
||||
if (!strcmp(buf.s, "S,in_addr"))
|
||||
param->value.iov_len = nval * sizeof(struct in_addr);
|
||||
#ifdef INET6
|
||||
else if (!strcmp(buf.s, "S,in6_addr"))
|
||||
param->value.iov_len = nval * sizeof(struct in6_addr);
|
||||
#endif
|
||||
else
|
||||
errx(1, "%s: unknown parameter structure (%s)",
|
||||
name, buf.s);
|
||||
break;
|
||||
case CTLTYPE_STRING:
|
||||
if (!strcmp(name, "path")) {
|
||||
param->value.iov_base = malloc(MAXPATHLEN);
|
||||
if (param->value.iov_base == NULL)
|
||||
err(1, "malloc");
|
||||
if (realpath(value, param->value.iov_base) == NULL)
|
||||
err(1, "%s: realpath(%s)", name, value);
|
||||
if (chdir(param->value.iov_base) != 0)
|
||||
err(1, "chdir: %s",
|
||||
(char *)param->value.iov_base);
|
||||
} else
|
||||
param->value.iov_base = value;
|
||||
param->value.iov_len = strlen(param->value.iov_base) + 1;
|
||||
return;
|
||||
default:
|
||||
errx(1, "%s: unknown parameter type %d (%s)",
|
||||
name, buf.i, buf.s);
|
||||
}
|
||||
param->value.iov_base = malloc(param->value.iov_len);
|
||||
for (i = 0; i < nval; i++) {
|
||||
switch (buf.i & CTLTYPE) {
|
||||
case CTLTYPE_INT:
|
||||
((int *)param->value.iov_base)[i] =
|
||||
strtol(value, &ep, 10);
|
||||
if (ep[0] != '\0')
|
||||
errx(1, "%s: non-integer value \"%s\"",
|
||||
name, value);
|
||||
break;
|
||||
case CTLTYPE_UINT:
|
||||
((unsigned *)param->value.iov_base)[i] =
|
||||
strtoul(value, &ep, 10);
|
||||
if (ep[0] != '\0')
|
||||
errx(1, "%s: non-integer value \"%s\"",
|
||||
name, value);
|
||||
break;
|
||||
case CTLTYPE_LONG:
|
||||
((long *)param->value.iov_base)[i] =
|
||||
strtol(value, &ep, 10);
|
||||
if (ep[0] != '\0')
|
||||
errx(1, "%s: non-integer value \"%s\"",
|
||||
name, value);
|
||||
break;
|
||||
case CTLTYPE_ULONG:
|
||||
((unsigned long *)param->value.iov_base)[i] =
|
||||
strtoul(value, &ep, 10);
|
||||
if (ep[0] != '\0')
|
||||
errx(1, "%s: non-integer value \"%s\"",
|
||||
name, value);
|
||||
break;
|
||||
case CTLTYPE_STRUCT:
|
||||
if (!strcmp(buf.s, "S,in_addr")) {
|
||||
if (inet_pton(AF_INET, value,
|
||||
&((struct in_addr *)
|
||||
param->value.iov_base)[i]) != 1)
|
||||
errx(1, "%s: not an IPv4 address: %s",
|
||||
name, value);
|
||||
}
|
||||
#ifdef INET6
|
||||
else if (!strcmp(buf.s, "S,in6_addr")) {
|
||||
if (inet_pton(AF_INET6, value,
|
||||
&((struct in6_addr *)
|
||||
param->value.iov_base)[i]) != 1)
|
||||
errx(1, "%s: not an IPv6 address: %s",
|
||||
name, value);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (i > 0)
|
||||
value[-1] = ',';
|
||||
value = strchr(value, '\0') + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
{
|
||||
|
||||
(void)fprintf(stderr, "%s%s%s\n",
|
||||
"usage: jail [-hi] [-n jailname] [-J jid_file] ",
|
||||
"[-s securelevel] [-l -u username | -U username] ",
|
||||
"path hostname [ip[,..]] command ...");
|
||||
(void)fprintf(stderr,
|
||||
"usage: jail [-d] [-h] [-i] [-J jid_file] "
|
||||
"[-l -u username | -U username]\n"
|
||||
" [-c | -m] param=value ... [command=command ...]\n"
|
||||
" jail [-r jail]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static int
|
||||
add_addresses(struct addrinfo *res0)
|
||||
{
|
||||
int error;
|
||||
struct addrinfo *res;
|
||||
struct addr4entry *a4p;
|
||||
struct sockaddr_in *sai;
|
||||
#ifdef INET6
|
||||
struct addr6entry *a6p;
|
||||
struct sockaddr_in6 *sai6;
|
||||
#endif
|
||||
int count;
|
||||
|
||||
error = 0;
|
||||
for (res = res0; res && error == 0; res = res->ai_next) {
|
||||
switch (res->ai_family) {
|
||||
case AF_INET:
|
||||
sai = (struct sockaddr_in *)(void *)res->ai_addr;
|
||||
STAILQ_FOREACH(a4p, &addr4, addr4entries) {
|
||||
if (bcmp(&sai->sin_addr, &a4p->ip4,
|
||||
sizeof(struct in_addr)) == 0) {
|
||||
err(1, "Ignoring duplicate IPv4 address.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
a4p = (struct addr4entry *) malloc(
|
||||
sizeof(struct addr4entry));
|
||||
if (a4p == NULL) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
bzero(a4p, sizeof(struct addr4entry));
|
||||
bcopy(&sai->sin_addr, &a4p->ip4,
|
||||
sizeof(struct in_addr));
|
||||
if (!STAILQ_EMPTY(&addr4))
|
||||
count = STAILQ_FIRST(&addr4)->count;
|
||||
else
|
||||
count = 0;
|
||||
STAILQ_INSERT_TAIL(&addr4, a4p, addr4entries);
|
||||
STAILQ_FIRST(&addr4)->count = count + 1;
|
||||
break;
|
||||
#ifdef INET6
|
||||
case AF_INET6:
|
||||
sai6 = (struct sockaddr_in6 *)(void *)res->ai_addr;
|
||||
STAILQ_FOREACH(a6p, &addr6, addr6entries) {
|
||||
if (bcmp(&sai6->sin6_addr, &a6p->ip6,
|
||||
sizeof(struct in6_addr)) == 0) {
|
||||
err(1, "Ignoring duplicate IPv6 address.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
a6p = (struct addr6entry *) malloc(
|
||||
sizeof(struct addr6entry));
|
||||
if (a6p == NULL) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
bzero(a6p, sizeof(struct addr6entry));
|
||||
bcopy(&sai6->sin6_addr, &a6p->ip6,
|
||||
sizeof(struct in6_addr));
|
||||
if (!STAILQ_EMPTY(&addr6))
|
||||
count = STAILQ_FIRST(&addr6)->count;
|
||||
else
|
||||
count = 0;
|
||||
STAILQ_INSERT_TAIL(&addr6, a6p, addr6entries);
|
||||
STAILQ_FIRST(&addr6)->count = count + 1;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
err(1, "Address family %d not supported. Ignoring.\n",
|
||||
res->ai_family);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (error);
|
||||
}
|
||||
|
||||
static struct in_addr *
|
||||
copy_addr4(void)
|
||||
{
|
||||
size_t len;
|
||||
struct in_addr *ip4s, *p, ia;
|
||||
struct addr4entry *a4p;
|
||||
|
||||
if (STAILQ_EMPTY(&addr4))
|
||||
return NULL;
|
||||
|
||||
len = STAILQ_FIRST(&addr4)->count * sizeof(struct in_addr);
|
||||
|
||||
ip4s = p = (struct in_addr *)malloc(len);
|
||||
if (ip4s == NULL)
|
||||
return (NULL);
|
||||
|
||||
bzero(p, len);
|
||||
|
||||
while (!STAILQ_EMPTY(&addr4)) {
|
||||
a4p = STAILQ_FIRST(&addr4);
|
||||
STAILQ_REMOVE_HEAD(&addr4, addr4entries);
|
||||
ia.s_addr = a4p->ip4.s_addr;
|
||||
bcopy(&ia, p, sizeof(struct in_addr));
|
||||
p++;
|
||||
free(a4p);
|
||||
}
|
||||
|
||||
return (ip4s);
|
||||
}
|
||||
|
||||
#ifdef INET6
|
||||
static struct in6_addr *
|
||||
copy_addr6(void)
|
||||
{
|
||||
size_t len;
|
||||
struct in6_addr *ip6s, *p;
|
||||
struct addr6entry *a6p;
|
||||
|
||||
if (STAILQ_EMPTY(&addr6))
|
||||
return NULL;
|
||||
|
||||
len = STAILQ_FIRST(&addr6)->count * sizeof(struct in6_addr);
|
||||
|
||||
ip6s = p = (struct in6_addr *)malloc(len);
|
||||
if (ip6s == NULL)
|
||||
return (NULL);
|
||||
|
||||
bzero(p, len);
|
||||
|
||||
while (!STAILQ_EMPTY(&addr6)) {
|
||||
a6p = STAILQ_FIRST(&addr6);
|
||||
STAILQ_REMOVE_HEAD(&addr6, addr6entries);
|
||||
bcopy(&a6p->ip6, p, sizeof(struct in6_addr));
|
||||
p++;
|
||||
free(a6p);
|
||||
}
|
||||
|
||||
return (ip6s);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -6,6 +6,4 @@ DPADD= ${LIBUTIL}
|
||||
LDADD= -lutil
|
||||
WARNS?= 6
|
||||
|
||||
CFLAGS+= -DSUPPORT_OLD_XPRISON
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
|
@ -25,7 +25,7 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd November 29, 2008
|
||||
.Dd May 27, 2009
|
||||
.Dt JEXEC 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -34,36 +34,18 @@
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl u Ar username | Fl U Ar username
|
||||
.Op Fl n Ar jailname
|
||||
.Ar jid command ...
|
||||
.Ar jail command ...
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
utility executes
|
||||
.Ar command
|
||||
inside the jail identified by either
|
||||
.Ar jailname
|
||||
or
|
||||
.Ar jid
|
||||
or both.
|
||||
.Pp
|
||||
If the jail cannot be identified uniquely by the given parameters,
|
||||
an error message is printed.
|
||||
.Nm
|
||||
will also check the state of the jail (once supported) to be
|
||||
.Dv ALIVE
|
||||
and ignore jails in other states.
|
||||
The mandatory argument
|
||||
.Ar jid
|
||||
is the unique jail identifier as given by
|
||||
.Xr jls 8 .
|
||||
In case you only want to match on other criteria, give an empty string.
|
||||
inside the
|
||||
.Ar jail
|
||||
identified by its jid or name.
|
||||
.Pp
|
||||
The following options are available:
|
||||
.Bl -tag -width indent
|
||||
.It Fl n Ar jailname
|
||||
The name of the jail, if given upon creation of the jail.
|
||||
This is not the hostname of the jail.
|
||||
.It Fl u Ar username
|
||||
The user name from host environment as whom the
|
||||
.Ar command
|
||||
|
@ -29,12 +29,16 @@
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/jail.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <login_cap.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -44,153 +48,6 @@
|
||||
|
||||
static void usage(void);
|
||||
|
||||
#ifdef SUPPORT_OLD_XPRISON
|
||||
static
|
||||
char *lookup_xprison_v1(void *p, char *end, int *id)
|
||||
{
|
||||
struct xprison_v1 *xp;
|
||||
|
||||
if (id == NULL)
|
||||
errx(1, "Internal error. Invalid ID pointer.");
|
||||
|
||||
if ((char *)p + sizeof(struct xprison_v1) > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
|
||||
xp = (struct xprison_v1 *)p;
|
||||
|
||||
*id = xp->pr_id;
|
||||
return ((char *)(xp + 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
static
|
||||
char *lookup_xprison_v3(void *p, char *end, int *id, char *jailname)
|
||||
{
|
||||
struct xprison *xp;
|
||||
char *q;
|
||||
int ok;
|
||||
|
||||
if (id == NULL)
|
||||
errx(1, "Internal error. Invalid ID pointer.");
|
||||
|
||||
if ((char *)p + sizeof(struct xprison) > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
|
||||
xp = (struct xprison *)p;
|
||||
ok = 1;
|
||||
|
||||
/* Jail state and name. */
|
||||
if (xp->pr_state < 0 || xp->pr_state >=
|
||||
(int)((sizeof(prison_states) / sizeof(struct prison_state))))
|
||||
errx(1, "Invalid jail state.");
|
||||
else if (xp->pr_state != PRISON_STATE_ALIVE)
|
||||
ok = 0;
|
||||
if (jailname != NULL) {
|
||||
if (xp->pr_name[0] == '\0')
|
||||
ok = 0;
|
||||
else if (strcmp(jailname, xp->pr_name) != 0)
|
||||
ok = 0;
|
||||
}
|
||||
|
||||
q = (char *)(xp + 1);
|
||||
/* IPv4 addresses. */
|
||||
q += (xp->pr_ip4s * sizeof(struct in_addr));
|
||||
if ((char *)q > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
/* IPv6 addresses. */
|
||||
q += (xp->pr_ip6s * sizeof(struct in6_addr));
|
||||
if ((char *)q > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
|
||||
if (ok)
|
||||
*id = xp->pr_id;
|
||||
return (q);
|
||||
}
|
||||
|
||||
static int
|
||||
lookup_jail(int jid, char *jailname)
|
||||
{
|
||||
size_t i, j, len;
|
||||
void *p, *q;
|
||||
int version, id, xid, count;
|
||||
|
||||
if (sysctlbyname("security.jail.list", NULL, &len, NULL, 0) == -1)
|
||||
err(1, "sysctlbyname(): security.jail.list");
|
||||
|
||||
j = len;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (len == 0)
|
||||
return (-1);
|
||||
p = q = malloc(len);
|
||||
if (p == NULL)
|
||||
err(1, "malloc()");
|
||||
|
||||
if (sysctlbyname("security.jail.list", q, &len, NULL, 0) == -1) {
|
||||
if (errno == ENOMEM) {
|
||||
free(p);
|
||||
p = NULL;
|
||||
len += j;
|
||||
continue;
|
||||
}
|
||||
err(1, "sysctlbyname(): security.jail.list");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (p == NULL)
|
||||
err(1, "sysctlbyname(): security.jail.list");
|
||||
if (len < sizeof(int))
|
||||
errx(1, "This is no prison. Kernel and userland out of sync?");
|
||||
version = *(int *)p;
|
||||
if (version > XPRISON_VERSION)
|
||||
errx(1, "Sci-Fi prison. Kernel/userland out of sync?");
|
||||
|
||||
count = 0;
|
||||
xid = -1;
|
||||
for (; q != NULL && (char *)q + sizeof(int) < (char *)p + len;) {
|
||||
version = *(int *)q;
|
||||
if (version > XPRISON_VERSION)
|
||||
errx(1, "Sci-Fi prison. Kernel/userland out of sync?");
|
||||
id = -1;
|
||||
switch (version) {
|
||||
#ifdef SUPPORT_OLD_XPRISON
|
||||
case 1:
|
||||
if (jailname != NULL)
|
||||
errx(1, "Version 1 prisons did not "
|
||||
"support jail names.");
|
||||
q = lookup_xprison_v1(q, (char *)p + len, &id);
|
||||
break;
|
||||
case 2:
|
||||
errx(1, "Version 2 was used by multi-IPv4 jail "
|
||||
"implementations that never made it into the "
|
||||
"official kernel.");
|
||||
/* NOTREACHED */
|
||||
break;
|
||||
#endif
|
||||
case 3:
|
||||
q = lookup_xprison_v3(q, (char *)p + len, &id, jailname);
|
||||
break;
|
||||
default:
|
||||
errx(1, "Prison unknown. Kernel/userland out of sync?");
|
||||
/* NOTREACHED */
|
||||
break;
|
||||
}
|
||||
/* Possible match; see if we have a jail ID to match as well. */
|
||||
if (id > 0 && (jid <= 0 || id == jid)) {
|
||||
xid = id;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
free(p);
|
||||
|
||||
if (count == 1)
|
||||
return (xid);
|
||||
else if (count > 1)
|
||||
errx(1, "Could not uniquely identify the jail.");
|
||||
else
|
||||
return (-1);
|
||||
}
|
||||
|
||||
#define GET_USER_INFO do { \
|
||||
pwd = getpwnam(username); \
|
||||
if (pwd == NULL) { \
|
||||
@ -210,21 +67,20 @@ lookup_jail(int jid, char *jailname)
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct iovec params[2];
|
||||
int jid;
|
||||
login_cap_t *lcap = NULL;
|
||||
struct passwd *pwd = NULL;
|
||||
gid_t groups[NGROUPS];
|
||||
int ch, ngroups, uflag, Uflag;
|
||||
char *jailname, *username;
|
||||
|
||||
char *ep, *username;
|
||||
ch = uflag = Uflag = 0;
|
||||
jailname = username = NULL;
|
||||
jid = -1;
|
||||
username = NULL;
|
||||
|
||||
while ((ch = getopt(argc, argv, "i:n:u:U:")) != -1) {
|
||||
while ((ch = getopt(argc, argv, "nu:U:")) != -1) {
|
||||
switch (ch) {
|
||||
case 'n':
|
||||
jailname = optarg;
|
||||
/* Specified name, now unused */
|
||||
break;
|
||||
case 'u':
|
||||
username = optarg;
|
||||
@ -242,22 +98,20 @@ main(int argc, char *argv[])
|
||||
argv += optind;
|
||||
if (argc < 2)
|
||||
usage();
|
||||
if (strlen(argv[0]) > 0) {
|
||||
jid = (int)strtol(argv[0], NULL, 10);
|
||||
if (errno)
|
||||
err(1, "Unable to parse jail ID.");
|
||||
}
|
||||
if (jid <= 0 && jailname == NULL) {
|
||||
fprintf(stderr, "Neither jail ID nor jail name given.\n");
|
||||
usage();
|
||||
}
|
||||
if (uflag && Uflag)
|
||||
usage();
|
||||
if (uflag)
|
||||
GET_USER_INFO;
|
||||
jid = lookup_jail(jid, jailname);
|
||||
if (jid <= 0)
|
||||
errx(1, "Cannot identify jail.");
|
||||
jid = strtoul(argv[0], &ep, 10);
|
||||
if (!*argv[0] || *ep) {
|
||||
*(const void **)¶ms[0].iov_base = "name";
|
||||
params[0].iov_len = sizeof("name");
|
||||
params[1].iov_base = argv[0];
|
||||
params[1].iov_len = strlen(argv[0]) + 1;
|
||||
jid = jail_get(params, 2, 0);
|
||||
if (jid < 0)
|
||||
errx(1, "Unknown jail: %s", argv[0]);
|
||||
}
|
||||
if (jail_attach(jid) == -1)
|
||||
err(1, "jail_attach(): %d", jid);
|
||||
if (chdir("/") == -1)
|
||||
@ -283,8 +137,7 @@ static void
|
||||
usage(void)
|
||||
{
|
||||
|
||||
fprintf(stderr, "%s%s\n",
|
||||
"usage: jexec [-u username | -U username]",
|
||||
" [-n jailname] jid command ...");
|
||||
fprintf(stderr, "%s\n",
|
||||
"usage: jexec [-u username | -U username] jail command ...");
|
||||
exit(1);
|
||||
}
|
||||
|
@ -4,6 +4,4 @@ PROG= jls
|
||||
MAN= jls.8
|
||||
WARNS?= 6
|
||||
|
||||
CFLAGS+= -DSUPPORT_OLD_XPRISON
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
|
@ -25,7 +25,7 @@
|
||||
.\"
|
||||
.\" $FreeBSD$
|
||||
.\"
|
||||
.Dd November 29, 2008
|
||||
.Dd May 27, 2009
|
||||
.Dt JLS 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
@ -33,38 +33,68 @@
|
||||
.Nd "list jails"
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl av
|
||||
.Op Fl dhnqsv
|
||||
.Op Fl j Ar jail
|
||||
.Op Ar parameter ...
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
utility lists all jails.
|
||||
By default only active jails are listed.
|
||||
utility lists all active jails, or the specified jail.
|
||||
Each jail is represented by one row which contains space-separated values of
|
||||
the listed
|
||||
.Ar parameters ,
|
||||
including the pseudo-parameter
|
||||
.Va all
|
||||
which will show all available jail parameters.
|
||||
A list of available parameters can be retrieved via
|
||||
.Dq Nm sysctl Fl d Va security.jail.param .
|
||||
See
|
||||
.Xr jail 8
|
||||
for a description of some core parameters.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width ".Fl a"
|
||||
.It Fl a
|
||||
Show jails in all states, not only active ones.
|
||||
If no
|
||||
.Ar parameters
|
||||
are given, the following four columns will be printed:
|
||||
jail identifier (jid), IP address (ip4.addr), hostname (host.hostname),
|
||||
and path (path).
|
||||
.Pp
|
||||
The following options are available:
|
||||
.Bl -tag -width indent
|
||||
.It Fl d
|
||||
List
|
||||
.Va dying
|
||||
as well as active jails.
|
||||
.It Fl h
|
||||
Print a header line containing the parameters listed.
|
||||
If no parameters are given on the command line, the default output always
|
||||
contains a header.
|
||||
.It Fl n
|
||||
Print parameters in
|
||||
.Dq name=value
|
||||
format, where each parameter is preceded by its name.
|
||||
This option is ignored for the default four-column output.
|
||||
.It Fl q
|
||||
Put quotes around parameters if they contain spaces or quotes, or are
|
||||
the empty string.
|
||||
.It Fl c
|
||||
Print parameters suitable for passing to
|
||||
.Xr jail 8 ,
|
||||
skipping read-only and unused parameters.
|
||||
Implies
|
||||
.Fl nq .
|
||||
.It Fl v
|
||||
Show more verbose information.
|
||||
This also lists cpusets, jail state, multi-IP, etc. instead of the
|
||||
classic single-IP jail output.
|
||||
.El
|
||||
.Pp
|
||||
Each jail is represented by rows which, depending on
|
||||
.Fl v ,
|
||||
contain the following columns:
|
||||
.Bl -item -offset indent -compact
|
||||
.It
|
||||
jail identifier (JID), hostname and path
|
||||
.It
|
||||
jail state and name
|
||||
.It
|
||||
jail cpuset
|
||||
.It
|
||||
followed by one IP adddress per line.
|
||||
Print a multiple-line summary per jail, with the following parameters:
|
||||
jail identifier (jid), hostname (host.hostname), path (path),
|
||||
jail name (name), jail state (dying), cpuset ID (cpuset),
|
||||
IP address(es) (ip4.addr and ip6.addr).
|
||||
.It Fl j Ar jail
|
||||
The jid or name of the
|
||||
.Ar jail
|
||||
to list.
|
||||
Without this option, all active jails will be listed.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr jail 2 ,
|
||||
.Xr jail_get 2 ,
|
||||
.Xr jail 8 ,
|
||||
.Xr jexec 8
|
||||
.Sh HISTORY
|
||||
@ -72,3 +102,5 @@ The
|
||||
.Nm
|
||||
utility was added in
|
||||
.Fx 5.1 .
|
||||
Extensible jail parameters were introduced in
|
||||
.Fx 8.0 .
|
||||
|
@ -1,6 +1,7 @@
|
||||
/*-
|
||||
* Copyright (c) 2003 Mike Barcroft <mike@FreeBSD.org>
|
||||
* Copyright (c) 2008 Bjoern A. Zeeb <bz@FreeBSD.org>
|
||||
* Copyright (c) 2009 James Gritton <jamie@FreeBSD.org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@ -23,18 +24,20 @@
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* $FreeBSD$
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/jail.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/jail.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
@ -43,215 +46,718 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define FLAG_A 0x00001
|
||||
#define FLAG_V 0x00002
|
||||
#define SJPARAM "security.jail.param"
|
||||
#define ARRAY_SLOP 5
|
||||
|
||||
#ifdef SUPPORT_OLD_XPRISON
|
||||
static
|
||||
char *print_xprison_v1(void *p, char *end, unsigned flags)
|
||||
#define CTLTYPE_BOOL (CTLTYPE + 1)
|
||||
#define CTLTYPE_NOBOOL (CTLTYPE + 2)
|
||||
#define CTLTYPE_IPADDR (CTLTYPE + 3)
|
||||
#define CTLTYPE_IP6ADDR (CTLTYPE + 4)
|
||||
|
||||
#define PARAM_KEY 0x01
|
||||
#define PARAM_USER 0x02
|
||||
#define PARAM_ARRAY 0x04
|
||||
#define PARAM_OPT 0x08
|
||||
#define PARAM_WR 0x10
|
||||
|
||||
#define PRINT_DEFAULT 0x01
|
||||
#define PRINT_HEADER 0x02
|
||||
#define PRINT_NAMEVAL 0x04
|
||||
#define PRINT_QUOTED 0x08
|
||||
#define PRINT_SKIP 0x10
|
||||
#define PRINT_VERBOSE 0x20
|
||||
|
||||
struct param {
|
||||
char *name;
|
||||
void *value;
|
||||
size_t size;
|
||||
int type;
|
||||
unsigned flags;
|
||||
int noparent;
|
||||
};
|
||||
|
||||
struct iovec2 {
|
||||
struct iovec name;
|
||||
struct iovec value;
|
||||
};
|
||||
|
||||
static struct param *params;
|
||||
static int nparams;
|
||||
static char errmsg[256];
|
||||
|
||||
static int add_param(const char *name, void *value, unsigned flags);
|
||||
static int get_param(const char *name, struct param *param);
|
||||
static int sort_param(const void *a, const void *b);
|
||||
static char *noname(const char *name);
|
||||
static char *nononame(const char *name);
|
||||
static int print_jail(int pflags, int jflags);
|
||||
static void quoted_print(char *str, int len);
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
struct xprison_v1 *xp;
|
||||
struct in_addr in;
|
||||
char *dot, *ep, *jname, *nname;
|
||||
int c, i, jflags, jid, lastjid, pflags, spc;
|
||||
|
||||
if ((char *)p + sizeof(struct xprison_v1) > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
jname = NULL;
|
||||
pflags = jflags = jid = 0;
|
||||
while ((c = getopt(argc, argv, "adj:hnqsv")) >= 0)
|
||||
switch (c) {
|
||||
case 'a':
|
||||
case 'd':
|
||||
jflags |= JAIL_DYING;
|
||||
break;
|
||||
case 'j':
|
||||
jid = strtoul(optarg, &ep, 10);
|
||||
if (!*optarg || *ep)
|
||||
jname = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
pflags = (pflags & ~PRINT_SKIP) | PRINT_HEADER;
|
||||
break;
|
||||
case 'n':
|
||||
pflags = (pflags & ~PRINT_VERBOSE) | PRINT_NAMEVAL;
|
||||
break;
|
||||
case 'q':
|
||||
pflags |= PRINT_QUOTED;
|
||||
break;
|
||||
case 's':
|
||||
pflags = (pflags & ~(PRINT_HEADER | PRINT_VERBOSE)) |
|
||||
PRINT_NAMEVAL | PRINT_QUOTED | PRINT_SKIP;
|
||||
break;
|
||||
case 'v':
|
||||
pflags = (pflags & ~(PRINT_NAMEVAL | PRINT_SKIP)) |
|
||||
PRINT_VERBOSE;
|
||||
break;
|
||||
default:
|
||||
errx(1, "usage: jls [-dhnqv] [-j jail] [param ...]");
|
||||
}
|
||||
|
||||
xp = (struct xprison_v1 *)p;
|
||||
if (flags & FLAG_V) {
|
||||
printf("%6d %-29.29s %.74s\n",
|
||||
xp->pr_id, xp->pr_host, xp->pr_path);
|
||||
/* We are not printing an empty line here for state and name. */
|
||||
/* We are not printing an empty line here for cpusetid. */
|
||||
/* IPv4 address. */
|
||||
in.s_addr = htonl(xp->pr_ip);
|
||||
printf("%6s %-15.15s\n", "", inet_ntoa(in));
|
||||
/* Add the parameters to print. */
|
||||
if (optind == argc) {
|
||||
if (pflags & PRINT_VERBOSE) {
|
||||
add_param("jid", NULL, PARAM_USER);
|
||||
add_param("host.hostname", NULL, PARAM_USER);
|
||||
add_param("path", NULL, PARAM_USER);
|
||||
add_param("name", NULL, PARAM_USER);
|
||||
add_param("dying", NULL, PARAM_USER);
|
||||
add_param("cpuset", NULL, PARAM_USER);
|
||||
add_param("ip4.addr", NULL, PARAM_USER);
|
||||
add_param("ip6.addr", NULL, PARAM_USER | PARAM_OPT);
|
||||
} else {
|
||||
printf("%6d %-15.15s %-29.29s %.74s\n",
|
||||
xp->pr_id, inet_ntoa(in), xp->pr_host, xp->pr_path);
|
||||
pflags = (pflags &
|
||||
~(PRINT_NAMEVAL | PRINT_SKIP | PRINT_VERBOSE)) |
|
||||
PRINT_DEFAULT;
|
||||
add_param("jid", NULL, PARAM_USER);
|
||||
add_param("ip4.addr", NULL, PARAM_USER);
|
||||
add_param("host.hostname", NULL, PARAM_USER);
|
||||
add_param("path", NULL, PARAM_USER);
|
||||
}
|
||||
} else
|
||||
while (optind < argc)
|
||||
add_param(argv[optind++], NULL, PARAM_USER);
|
||||
|
||||
if (pflags & PRINT_SKIP) {
|
||||
/* Check for parameters with boolean parents. */
|
||||
for (i = 0; i < nparams; i++) {
|
||||
if ((params[i].flags & PARAM_USER) &&
|
||||
(dot = strchr(params[i].name, '.'))) {
|
||||
*dot = 0;
|
||||
nname = noname(params[i].name);
|
||||
*dot = '.';
|
||||
params[i].noparent =
|
||||
add_param(nname, NULL, PARAM_OPT);
|
||||
free(nname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ((char *)(xp + 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
static
|
||||
char *print_xprison_v3(void *p, char *end, unsigned flags)
|
||||
{
|
||||
struct xprison *xp;
|
||||
struct in_addr *iap, in;
|
||||
struct in6_addr *ia6p;
|
||||
char buf[INET6_ADDRSTRLEN];
|
||||
const char *state;
|
||||
char *q;
|
||||
uint32_t i;
|
||||
|
||||
if ((char *)p + sizeof(struct xprison) > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
xp = (struct xprison *)p;
|
||||
|
||||
if (xp->pr_state < 0 || xp->pr_state >= (int)
|
||||
((sizeof(prison_states) / sizeof(struct prison_state))))
|
||||
state = "(bogus)";
|
||||
/* Add the index key and errmsg parameters. */
|
||||
if (jid != 0)
|
||||
add_param("jid", &jid, PARAM_KEY);
|
||||
else if (jname != NULL)
|
||||
add_param("name", jname, PARAM_KEY);
|
||||
else
|
||||
state = prison_states[xp->pr_state].state_name;
|
||||
add_param("lastjid", &lastjid, PARAM_KEY);
|
||||
add_param("errmsg", errmsg, PARAM_KEY);
|
||||
|
||||
/* See if we should print non-ACTIVE jails. No? */
|
||||
if ((flags & FLAG_A) == 0 && strcmp(state, "ALIVE")) {
|
||||
q = (char *)(xp + 1);
|
||||
q += (xp->pr_ip4s * sizeof(struct in_addr));
|
||||
if (q > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
q += (xp->pr_ip6s * sizeof(struct in6_addr));
|
||||
if (q > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
return (q);
|
||||
/* Print a header line if requested. */
|
||||
if (pflags & PRINT_VERBOSE)
|
||||
printf(" JID Hostname Path\n"
|
||||
" Name State\n"
|
||||
" CPUSetID\n"
|
||||
" IP Address(es)\n");
|
||||
else if (pflags & PRINT_DEFAULT)
|
||||
printf(" JID IP Address "
|
||||
"Hostname Path\n");
|
||||
else if (pflags & PRINT_HEADER) {
|
||||
for (i = spc = 0; i < nparams; i++)
|
||||
if (params[i].flags & PARAM_USER) {
|
||||
if (spc)
|
||||
putchar(' ');
|
||||
else
|
||||
spc = 1;
|
||||
fputs(params[i].name, stdout);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
if (flags & FLAG_V)
|
||||
printf("%6d %-29.29s %.74s\n",
|
||||
xp->pr_id, xp->pr_host, xp->pr_path);
|
||||
|
||||
/* Jail state and name. */
|
||||
if (flags & FLAG_V)
|
||||
printf("%6s %-29.29s %.74s\n",
|
||||
"", (xp->pr_name[0] != '\0') ? xp->pr_name : "", state);
|
||||
|
||||
/* cpusetid. */
|
||||
if (flags & FLAG_V)
|
||||
printf("%6s %-6d\n",
|
||||
"", xp->pr_cpusetid);
|
||||
|
||||
q = (char *)(xp + 1);
|
||||
/* IPv4 addresses. */
|
||||
iap = (struct in_addr *)(void *)q;
|
||||
q += (xp->pr_ip4s * sizeof(struct in_addr));
|
||||
if (q > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
in.s_addr = 0;
|
||||
for (i = 0; i < xp->pr_ip4s; i++) {
|
||||
if (i == 0 || flags & FLAG_V)
|
||||
in.s_addr = iap[i].s_addr;
|
||||
if (flags & FLAG_V)
|
||||
printf("%6s %-15.15s\n", "", inet_ntoa(in));
|
||||
/* Fetch the jail(s) and print the paramters. */
|
||||
if (jid != 0 || jname != NULL) {
|
||||
if (print_jail(pflags, jflags) < 0) {
|
||||
if (errmsg[0])
|
||||
errx(1, "%s", errmsg);
|
||||
err(1, "jail_get");
|
||||
}
|
||||
/* IPv6 addresses. */
|
||||
ia6p = (struct in6_addr *)(void *)q;
|
||||
q += (xp->pr_ip6s * sizeof(struct in6_addr));
|
||||
if (q > end)
|
||||
errx(1, "Invalid length for jail");
|
||||
for (i = 0; i < xp->pr_ip6s; i++) {
|
||||
if (flags & FLAG_V) {
|
||||
inet_ntop(AF_INET6, &ia6p[i], buf, sizeof(buf));
|
||||
printf("%6s %s\n", "", buf);
|
||||
} else {
|
||||
for (lastjid = 0;
|
||||
(lastjid = print_jail(pflags, jflags)) >= 0; )
|
||||
;
|
||||
if (errno != 0 && errno != ENOENT) {
|
||||
if (errmsg[0])
|
||||
errx(1, "%s", errmsg);
|
||||
err(1, "jail_get");
|
||||
}
|
||||
}
|
||||
|
||||
/* If requested print the old style single line version. */
|
||||
if (!(flags & FLAG_V))
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
add_param(const char *name, void *value, unsigned flags)
|
||||
{
|
||||
struct param *param;
|
||||
char *nname;
|
||||
size_t mlen1, mlen2, buflen;
|
||||
int mib1[CTL_MAXNAME], mib2[CTL_MAXNAME - 2];
|
||||
int i, tnparams;
|
||||
char buf[MAXPATHLEN];
|
||||
|
||||
static int paramlistsize;
|
||||
|
||||
/* The pseudo-parameter "all" scans the list of available parameters. */
|
||||
if (!strcmp(name, "all")) {
|
||||
tnparams = nparams;
|
||||
mib1[0] = 0;
|
||||
mib1[1] = 2;
|
||||
mlen1 = CTL_MAXNAME - 2;
|
||||
if (sysctlnametomib(SJPARAM, mib1 + 2, &mlen1) < 0)
|
||||
err(1, "sysctlnametomib(" SJPARAM ")");
|
||||
for (;;) {
|
||||
/* Get the next parameter. */
|
||||
mlen2 = sizeof(mib2);
|
||||
if (sysctl(mib1, mlen1 + 2, mib2, &mlen2, NULL, 0) < 0)
|
||||
err(1, "sysctl(0.2)");
|
||||
if (mib2[0] != mib1[2] || mib2[1] != mib1[3] ||
|
||||
mib2[2] != mib1[4])
|
||||
break;
|
||||
/* Convert it to an ascii name. */
|
||||
memcpy(mib1 + 2, mib2, mlen2);
|
||||
mlen1 = mlen2 / sizeof(int);
|
||||
mib1[1] = 1;
|
||||
buflen = sizeof(buf);
|
||||
if (sysctl(mib1, mlen1 + 2, buf, &buflen, NULL, 0) < 0)
|
||||
err(1, "sysctl(0.1)");
|
||||
add_param(buf + sizeof(SJPARAM), NULL, flags);
|
||||
/*
|
||||
* Convert nobool parameters to bool if their
|
||||
* counterpart is a node, ortherwise discard them.
|
||||
*/
|
||||
param = ¶ms[nparams - 1];
|
||||
if (param->type == CTLTYPE_NOBOOL) {
|
||||
nname = nononame(param->name);
|
||||
if (get_param(nname, param) >= 0 &&
|
||||
param->type != CTLTYPE_NODE) {
|
||||
free(nname);
|
||||
nparams--;
|
||||
} else {
|
||||
free(param->name);
|
||||
param->name = nname;
|
||||
param->type = CTLTYPE_BOOL;
|
||||
param->size = sizeof(int);
|
||||
param->value = NULL;
|
||||
}
|
||||
}
|
||||
mib1[1] = 2;
|
||||
}
|
||||
|
||||
qsort(params + tnparams, (size_t)(nparams - tnparams),
|
||||
sizeof(struct param), sort_param);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Check for repeat parameters. */
|
||||
for (i = 0; i < nparams; i++)
|
||||
if (!strcmp(name, params[i].name)) {
|
||||
params[i].value = value;
|
||||
params[i].flags |= flags;
|
||||
return i;
|
||||
}
|
||||
|
||||
/* Make sure there is room for the new param record. */
|
||||
if (!nparams) {
|
||||
paramlistsize = 32;
|
||||
params = malloc(paramlistsize * sizeof(*params));
|
||||
if (params == NULL)
|
||||
err(1, "malloc");
|
||||
} else if (nparams >= paramlistsize) {
|
||||
paramlistsize *= 2;
|
||||
params = realloc(params, paramlistsize * sizeof(*params));
|
||||
if (params == NULL)
|
||||
err(1, "realloc");
|
||||
}
|
||||
|
||||
/* Look up the parameter. */
|
||||
param = params + nparams++;
|
||||
memset(param, 0, sizeof *param);
|
||||
param->name = strdup(name);
|
||||
if (param->name == NULL)
|
||||
err(1, "strdup");
|
||||
param->flags = flags;
|
||||
param->noparent = -1;
|
||||
/* We have to know about pseudo-parameters without asking. */
|
||||
if (!strcmp(param->name, "lastjid")) {
|
||||
param->type = CTLTYPE_INT;
|
||||
param->size = sizeof(int);
|
||||
goto got_type;
|
||||
}
|
||||
if (!strcmp(param->name, "errmsg")) {
|
||||
param->type = CTLTYPE_STRING;
|
||||
param->size = sizeof(errmsg);
|
||||
goto got_type;
|
||||
}
|
||||
if (get_param(name, param) < 0) {
|
||||
if (errno != ENOENT)
|
||||
err(1, "sysctl(0.3.%s)", name);
|
||||
/* See if this the "no" part of an existing boolean. */
|
||||
if ((nname = nononame(name))) {
|
||||
i = get_param(nname, param);
|
||||
free(nname);
|
||||
if (i >= 0 && param->type == CTLTYPE_BOOL) {
|
||||
param->type = CTLTYPE_NOBOOL;
|
||||
goto got_type;
|
||||
}
|
||||
}
|
||||
if (flags & PARAM_OPT) {
|
||||
nparams--;
|
||||
return -1;
|
||||
}
|
||||
errx(1, "unknown parameter: %s", name);
|
||||
}
|
||||
if (param->type == CTLTYPE_NODE) {
|
||||
/*
|
||||
* A node isn't normally a parameter, but may be a boolean
|
||||
* if its "no" counterpart exists.
|
||||
*/
|
||||
nname = noname(name);
|
||||
i = get_param(nname, param);
|
||||
free(nname);
|
||||
if (i >= 0 && param->type == CTLTYPE_NOBOOL) {
|
||||
param->type = CTLTYPE_BOOL;
|
||||
goto got_type;
|
||||
}
|
||||
errx(1, "unknown parameter: %s", name);
|
||||
}
|
||||
|
||||
got_type:
|
||||
param->value = value;
|
||||
return param - params;
|
||||
}
|
||||
|
||||
static int
|
||||
get_param(const char *name, struct param *param)
|
||||
{
|
||||
char *p;
|
||||
size_t buflen, mlen;
|
||||
int mib[CTL_MAXNAME];
|
||||
struct {
|
||||
int i;
|
||||
char s[MAXPATHLEN];
|
||||
} buf;
|
||||
|
||||
/* Look up the MIB. */
|
||||
mib[0] = 0;
|
||||
mib[1] = 3;
|
||||
snprintf(buf.s, sizeof(buf.s), SJPARAM ".%s", name);
|
||||
mlen = sizeof(mib) - 2 * sizeof(int);
|
||||
if (sysctl(mib, 2, mib + 2, &mlen, buf.s, strlen(buf.s)) < 0)
|
||||
return (-1);
|
||||
/* Get the type and size. */
|
||||
mib[1] = 4;
|
||||
buflen = sizeof(buf);
|
||||
if (sysctl(mib, (mlen / sizeof(int)) + 2, &buf, &buflen, NULL, 0) < 0)
|
||||
err(1, "sysctl(0.4.%s)", name);
|
||||
param->type = buf.i & CTLTYPE;
|
||||
if (buf.i & (CTLFLAG_WR | CTLFLAG_TUN))
|
||||
param->flags |= PARAM_WR;
|
||||
p = strchr(buf.s, '\0');
|
||||
if (p - 2 >= buf.s && !strcmp(p - 2, ",a")) {
|
||||
p[-2] = 0;
|
||||
param->flags |= PARAM_ARRAY;
|
||||
}
|
||||
switch (param->type) {
|
||||
case CTLTYPE_INT:
|
||||
/* An integer parameter might be a boolean. */
|
||||
if (buf.s[0] == 'B')
|
||||
param->type = buf.s[1] == 'N'
|
||||
? CTLTYPE_NOBOOL : CTLTYPE_BOOL;
|
||||
case CTLTYPE_UINT:
|
||||
param->size = sizeof(int);
|
||||
break;
|
||||
case CTLTYPE_LONG:
|
||||
case CTLTYPE_ULONG:
|
||||
param->size = sizeof(long);
|
||||
break;
|
||||
case CTLTYPE_STRUCT:
|
||||
if (!strcmp(buf.s, "S,in_addr")) {
|
||||
param->type = CTLTYPE_IPADDR;
|
||||
param->size = sizeof(struct in_addr);
|
||||
} else if (!strcmp(buf.s, "S,in6_addr")) {
|
||||
param->type = CTLTYPE_IP6ADDR;
|
||||
param->size = sizeof(struct in6_addr);
|
||||
}
|
||||
break;
|
||||
case CTLTYPE_STRING:
|
||||
buf.s[0] = 0;
|
||||
sysctl(mib + 2, mlen / sizeof(int), buf.s, &buflen, NULL, 0);
|
||||
param->size = strtoul(buf.s, NULL, 10);
|
||||
if (param->size == 0)
|
||||
param->size = BUFSIZ;
|
||||
}
|
||||
return (0);
|
||||
}
|
||||
|
||||
static int
|
||||
sort_param(const void *a, const void *b)
|
||||
{
|
||||
const struct param *parama, *paramb;
|
||||
char *ap, *bp;
|
||||
|
||||
/* Put top-level parameters first. */
|
||||
parama = a;
|
||||
paramb = b;
|
||||
ap = strchr(parama->name, '.');
|
||||
bp = strchr(paramb->name, '.');
|
||||
if (ap && !bp)
|
||||
return (1);
|
||||
if (bp && !ap)
|
||||
return (-1);
|
||||
return (strcmp(parama->name, paramb->name));
|
||||
}
|
||||
|
||||
static char *
|
||||
noname(const char *name)
|
||||
{
|
||||
char *nname, *p;
|
||||
|
||||
nname = malloc(strlen(name) + 3);
|
||||
if (nname == NULL)
|
||||
err(1, "malloc");
|
||||
p = strrchr(name, '.');
|
||||
if (p != NULL)
|
||||
sprintf(nname, "%.*s.no%s", (int)(p - name), name, p + 1);
|
||||
else
|
||||
sprintf(nname, "no%s", name);
|
||||
return nname;
|
||||
}
|
||||
|
||||
static char *
|
||||
nononame(const char *name)
|
||||
{
|
||||
char *nname, *p;
|
||||
|
||||
p = strrchr(name, '.');
|
||||
if (strncmp(p ? p + 1 : name, "no", 2))
|
||||
return NULL;
|
||||
nname = malloc(strlen(name) - 1);
|
||||
if (nname == NULL)
|
||||
err(1, "malloc");
|
||||
if (p != NULL)
|
||||
sprintf(nname, "%.*s.%s", (int)(p - name), name, p + 3);
|
||||
else
|
||||
strcpy(nname, name + 2);
|
||||
return nname;
|
||||
}
|
||||
|
||||
static int
|
||||
print_jail(int pflags, int jflags)
|
||||
{
|
||||
char *nname;
|
||||
int i, ai, jid, count, sanity, spc;
|
||||
char ipbuf[INET6_ADDRSTRLEN];
|
||||
|
||||
static struct iovec2 *iov, *aiov;
|
||||
static int narray, nkey;
|
||||
|
||||
/* Set up the parameter list(s) the first time around. */
|
||||
if (iov == NULL) {
|
||||
iov = malloc(nparams * sizeof(struct iovec2));
|
||||
if (iov == NULL)
|
||||
err(1, "malloc");
|
||||
for (i = narray = 0; i < nparams; i++) {
|
||||
iov[i].name.iov_base = params[i].name;
|
||||
iov[i].name.iov_len = strlen(params[i].name) + 1;
|
||||
iov[i].value.iov_base = params[i].value;
|
||||
iov[i].value.iov_len =
|
||||
params[i].type == CTLTYPE_STRING &&
|
||||
params[i].value != NULL &&
|
||||
((char *)params[i].value)[0] != '\0'
|
||||
? strlen(params[i].value) + 1 : params[i].size;
|
||||
if (params[i].flags & (PARAM_KEY | PARAM_ARRAY)) {
|
||||
narray++;
|
||||
if (params[i].flags & PARAM_KEY)
|
||||
nkey++;
|
||||
}
|
||||
}
|
||||
if (narray > nkey) {
|
||||
aiov = malloc(narray * sizeof(struct iovec2));
|
||||
if (aiov == NULL)
|
||||
err(1, "malloc");
|
||||
for (i = ai = 0; i < nparams; i++)
|
||||
if (params[i].flags &
|
||||
(PARAM_KEY | PARAM_ARRAY))
|
||||
aiov[ai++] = iov[i];
|
||||
}
|
||||
}
|
||||
/* If there are array parameters, find their sizes. */
|
||||
if (aiov != NULL) {
|
||||
for (ai = 0; ai < narray; ai++)
|
||||
if (aiov[ai].value.iov_base == NULL)
|
||||
aiov[ai].value.iov_len = 0;
|
||||
if (jail_get((struct iovec *)aiov, 2 * narray, jflags) < 0)
|
||||
return (-1);
|
||||
}
|
||||
/* Allocate storage for all parameters. */
|
||||
for (i = ai = 0; i < nparams; i++) {
|
||||
if (params[i].flags & (PARAM_KEY | PARAM_ARRAY)) {
|
||||
if (params[i].flags & PARAM_ARRAY) {
|
||||
iov[i].value.iov_len = aiov[ai].value.iov_len +
|
||||
ARRAY_SLOP * params[i].size;
|
||||
iov[i].value.iov_base =
|
||||
malloc(iov[i].value.iov_len);
|
||||
}
|
||||
ai++;
|
||||
} else
|
||||
iov[i].value.iov_base = malloc(params[i].size);
|
||||
if (iov[i].value.iov_base == NULL)
|
||||
err(1, "malloc");
|
||||
if (params[i].value == NULL)
|
||||
memset(iov[i].value.iov_base, 0, iov[i].value.iov_len);
|
||||
}
|
||||
/*
|
||||
* Get the actual prison. If there are array elements, retry a few
|
||||
* times in case the size changed from under us.
|
||||
*/
|
||||
if ((jid = jail_get((struct iovec *)iov, 2 * nparams, jflags)) < 0) {
|
||||
if (errno != EINVAL || aiov == NULL || errmsg[0])
|
||||
return (-1);
|
||||
for (sanity = 0;; sanity++) {
|
||||
if (sanity == 10)
|
||||
return (-1);
|
||||
for (ai = 0; ai < narray; ai++)
|
||||
if (params[i].flags & PARAM_ARRAY)
|
||||
aiov[ai].value.iov_len = 0;
|
||||
if (jail_get((struct iovec *)iov, 2 * narray, jflags) <
|
||||
0)
|
||||
return (-1);
|
||||
for (i = ai = 0; i < nparams; i++) {
|
||||
if (!(params[i].flags &
|
||||
(PARAM_KEY | PARAM_ARRAY)))
|
||||
continue;
|
||||
if (params[i].flags & PARAM_ARRAY) {
|
||||
iov[i].value.iov_len =
|
||||
aiov[ai].value.iov_len +
|
||||
ARRAY_SLOP * params[i].size;
|
||||
iov[i].value.iov_base =
|
||||
realloc(iov[i].value.iov_base,
|
||||
iov[i].value.iov_len);
|
||||
if (iov[i].value.iov_base == NULL)
|
||||
err(1, "malloc");
|
||||
}
|
||||
ai++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pflags & PRINT_VERBOSE) {
|
||||
printf("%6d %-29.29s %.74s\n"
|
||||
"%6s %-29.29s %.74s\n"
|
||||
"%6s %-6d\n",
|
||||
*(int *)iov[0].value.iov_base,
|
||||
(char *)iov[1].value.iov_base,
|
||||
(char *)iov[2].value.iov_base,
|
||||
"",
|
||||
(char *)iov[3].value.iov_base,
|
||||
*(int *)iov[4].value.iov_base ? "DYING" : "ACTIVE",
|
||||
"",
|
||||
*(int *)iov[5].value.iov_base);
|
||||
count = iov[6].value.iov_len / sizeof(struct in_addr);
|
||||
for (ai = 0; ai < count; ai++)
|
||||
if (inet_ntop(AF_INET,
|
||||
&((struct in_addr *)iov[6].value.iov_base)[ai],
|
||||
ipbuf, sizeof(ipbuf)) == NULL)
|
||||
err(1, "inet_ntop");
|
||||
else
|
||||
printf("%6s %-15.15s\n", "", ipbuf);
|
||||
if (!strcmp(params[7].name, "ip6.addr")) {
|
||||
count = iov[7].value.iov_len / sizeof(struct in6_addr);
|
||||
for (ai = 0; ai < count; ai++)
|
||||
if (inet_ntop(AF_INET6, &((struct in_addr *)
|
||||
iov[7].value.iov_base)[ai],
|
||||
ipbuf, sizeof(ipbuf)) == NULL)
|
||||
err(1, "inet_ntop");
|
||||
else
|
||||
printf("%6s %-15.15s\n", "", ipbuf);
|
||||
}
|
||||
} else if (pflags & PRINT_DEFAULT)
|
||||
printf("%6d %-15.15s %-29.29s %.74s\n",
|
||||
xp->pr_id, (in.s_addr) ? inet_ntoa(in) : "",
|
||||
xp->pr_host, xp->pr_path);
|
||||
|
||||
return (q);
|
||||
*(int *)iov[0].value.iov_base,
|
||||
iov[1].value.iov_len == 0 ? "-"
|
||||
: inet_ntoa(*(struct in_addr *)iov[1].value.iov_base),
|
||||
(char *)iov[2].value.iov_base,
|
||||
(char *)iov[3].value.iov_base);
|
||||
else {
|
||||
for (i = spc = 0; i < nparams; i++) {
|
||||
if (!(params[i].flags & PARAM_USER))
|
||||
continue;
|
||||
if ((pflags & PRINT_SKIP) &&
|
||||
((!(params[i].flags & PARAM_WR)) ||
|
||||
(params[i].noparent >= 0 &&
|
||||
*(int *)iov[params[i].noparent].value.iov_base)))
|
||||
continue;
|
||||
if (spc)
|
||||
putchar(' ');
|
||||
else
|
||||
spc = 1;
|
||||
if (pflags & PRINT_NAMEVAL) {
|
||||
/*
|
||||
* Generally "name=value", but for booleans
|
||||
* either "name" or "noname".
|
||||
*/
|
||||
switch (params[i].type) {
|
||||
case CTLTYPE_BOOL:
|
||||
if (*(int *)iov[i].value.iov_base)
|
||||
printf("%s", params[i].name);
|
||||
else {
|
||||
nname = noname(params[i].name);
|
||||
printf("%s", nname);
|
||||
free(nname);
|
||||
}
|
||||
break;
|
||||
case CTLTYPE_NOBOOL:
|
||||
if (*(int *)iov[i].value.iov_base)
|
||||
printf("%s", params[i].name);
|
||||
else {
|
||||
nname =
|
||||
nononame(params[i].name);
|
||||
printf("%s", nname);
|
||||
free(nname);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
printf("%s=", params[i].name);
|
||||
}
|
||||
}
|
||||
count = params[i].flags & PARAM_ARRAY
|
||||
? iov[i].value.iov_len / params[i].size : 1;
|
||||
if (count == 0) {
|
||||
if (pflags & PRINT_QUOTED)
|
||||
printf("\"\"");
|
||||
else if (!(pflags & PRINT_NAMEVAL))
|
||||
putchar('-');
|
||||
}
|
||||
for (ai = 0; ai < count; ai++) {
|
||||
if (ai > 0)
|
||||
putchar(',');
|
||||
switch (params[i].type) {
|
||||
case CTLTYPE_INT:
|
||||
printf("%d", ((int *)
|
||||
iov[i].value.iov_base)[ai]);
|
||||
break;
|
||||
case CTLTYPE_UINT:
|
||||
printf("%u", ((int *)
|
||||
iov[i].value.iov_base)[ai]);
|
||||
break;
|
||||
case CTLTYPE_IPADDR:
|
||||
if (inet_ntop(AF_INET,
|
||||
&((struct in_addr *)
|
||||
iov[i].value.iov_base)[ai],
|
||||
ipbuf, sizeof(ipbuf)) == NULL)
|
||||
err(1, "inet_ntop");
|
||||
else
|
||||
printf("%s", ipbuf);
|
||||
break;
|
||||
case CTLTYPE_IP6ADDR:
|
||||
if (inet_ntop(AF_INET6,
|
||||
&((struct in6_addr *)
|
||||
iov[i].value.iov_base)[ai],
|
||||
ipbuf, sizeof(ipbuf)) == NULL)
|
||||
err(1, "inet_ntop");
|
||||
else
|
||||
printf("%s", ipbuf);
|
||||
break;
|
||||
case CTLTYPE_LONG:
|
||||
printf("%ld", ((long *)
|
||||
iov[i].value.iov_base)[ai]);
|
||||
case CTLTYPE_ULONG:
|
||||
printf("%lu", ((long *)
|
||||
iov[i].value.iov_base)[ai]);
|
||||
break;
|
||||
case CTLTYPE_STRING:
|
||||
if (pflags & PRINT_QUOTED)
|
||||
quoted_print((char *)
|
||||
iov[i].value.iov_base,
|
||||
params[i].size);
|
||||
else
|
||||
printf("%.*s",
|
||||
(int)params[i].size,
|
||||
(char *)
|
||||
iov[i].value.iov_base);
|
||||
break;
|
||||
case CTLTYPE_BOOL:
|
||||
case CTLTYPE_NOBOOL:
|
||||
if (!(pflags & PRINT_NAMEVAL))
|
||||
printf(((int *)
|
||||
iov[i].value.iov_base)[ai]
|
||||
? "true" : "false");
|
||||
}
|
||||
}
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
for (i = 0; i < nparams; i++)
|
||||
if (params[i].value == NULL)
|
||||
free(iov[i].value.iov_base);
|
||||
return (jid);
|
||||
}
|
||||
|
||||
static void
|
||||
usage(void)
|
||||
quoted_print(char *str, int len)
|
||||
{
|
||||
int c, qc;
|
||||
char *p = str;
|
||||
char *ep = str + len;
|
||||
|
||||
(void)fprintf(stderr, "usage: jls [-av]\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
int ch, version;
|
||||
unsigned flags;
|
||||
size_t i, j, len;
|
||||
void *p, *q;
|
||||
|
||||
flags = 0;
|
||||
while ((ch = getopt(argc, argv, "av")) != -1) {
|
||||
switch (ch) {
|
||||
case 'a':
|
||||
flags |= FLAG_A;
|
||||
break;
|
||||
case 'v':
|
||||
flags |= FLAG_V;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (sysctlbyname("security.jail.list", NULL, &len, NULL, 0) == -1)
|
||||
err(1, "sysctlbyname(): security.jail.list");
|
||||
|
||||
j = len;
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (len <= 0)
|
||||
exit(0);
|
||||
p = q = malloc(len);
|
||||
if (p == NULL)
|
||||
err(1, "malloc()");
|
||||
|
||||
if (sysctlbyname("security.jail.list", q, &len, NULL, 0) == -1) {
|
||||
if (errno == ENOMEM) {
|
||||
free(p);
|
||||
p = NULL;
|
||||
len += j;
|
||||
continue;
|
||||
}
|
||||
err(1, "sysctlbyname(): security.jail.list");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (p == NULL)
|
||||
err(1, "sysctlbyname(): security.jail.list");
|
||||
if (len < sizeof(int))
|
||||
errx(1, "This is no prison. Kernel and userland out of sync?");
|
||||
version = *(int *)p;
|
||||
if (version > XPRISON_VERSION)
|
||||
errx(1, "Sci-Fi prison. Kernel/userland out of sync?");
|
||||
|
||||
if (flags & FLAG_V) {
|
||||
printf(" JID Hostname Path\n");
|
||||
printf(" Name State\n");
|
||||
printf(" CPUSetID\n");
|
||||
printf(" IP Address(es)\n");
|
||||
} else {
|
||||
printf(" JID IP Address Hostname"
|
||||
" Path\n");
|
||||
}
|
||||
for (; q != NULL && (char *)q + sizeof(int) < (char *)p + len;) {
|
||||
version = *(int *)q;
|
||||
if (version > XPRISON_VERSION)
|
||||
errx(1, "Sci-Fi prison. Kernel/userland out of sync?");
|
||||
switch (version) {
|
||||
#ifdef SUPPORT_OLD_XPRISON
|
||||
case 1:
|
||||
q = print_xprison_v1(q, (char *)p + len, flags);
|
||||
break;
|
||||
case 2:
|
||||
errx(1, "Version 2 was used by multi-IPv4 jail "
|
||||
"implementations that never made it into the "
|
||||
"official kernel.");
|
||||
/* NOTREACHED */
|
||||
break;
|
||||
#endif
|
||||
case 3:
|
||||
q = print_xprison_v3(q, (char *)p + len, flags);
|
||||
break;
|
||||
default:
|
||||
errx(1, "Prison unknown. Kernel/userland out of sync?");
|
||||
/* NOTREACHED */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
free(p);
|
||||
exit(0);
|
||||
/* An empty string needs quoting. */
|
||||
if (!*p) {
|
||||
fputs("\"\"", stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The value will be surrounded by quotes if it contains spaces
|
||||
* or quotes.
|
||||
*/
|
||||
qc = strchr(p, '\'') ? '"'
|
||||
: strchr(p, '"') ? '\''
|
||||
: strchr(p, ' ') || strchr(p, '\t') ? '"'
|
||||
: 0;
|
||||
if (qc)
|
||||
putchar(qc);
|
||||
while (p < ep && (c = *p++)) {
|
||||
if (c == '\\' || c == qc)
|
||||
putchar('\\');
|
||||
putchar(c);
|
||||
}
|
||||
if (qc)
|
||||
putchar(qc);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user