* Added jail header dependencies for mkmf Makefile.
* Renamed header file (bsdjail -> jail)
* Fixed copyright
* Initial commit of primary functionality
* Attach to a running jail
* Create a new jail
* List and instantiate existing jails
* Fetch information about existing jails
* Still needs...
* Attach "in a block" fleshed out
* Documentation and better comments
* Tests (of some kind, this will be tough)
* Update for not-yet-MFC'ed multiple IP jail patch in FBSD 7.2-STABLE
/*
* jail.c - Ruby jparallel
*
* vim: set nosta noet ts=4 sw=4:
*
* $Id$
*
* Authors:
* * Michael Granger <ged@FaerieMUD.org>
* * Mahlon E. Smith <mahlon@martini.nu>
*
* Copyright (c) 2008, Michael Granger and Mahlon E. Smith
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the author/s, nor the names of the project's
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 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.
*
*/
#include "jail.h"
VALUE rbjail_mBSD;
VALUE rbjail_cBSDJail;
VALUE rbjail_cIPAddr;
/*
* Debug logging function
*/
void
#ifdef HAVE_STDARG_PROTOTYPES
rbjail_debug(const char *fmt, ...)
#else
rbjail_debug( const char *fmt, va_dcl )
#endif
{
char buf[BUFSIZ], buf2[BUFSIZ];
va_list args;
if ( !RTEST(ruby_debug) ) return;
snprintf( buf, BUFSIZ, "Debug>>> %s", fmt );
va_init_list( args, fmt );
vsnprintf( buf2, BUFSIZ, buf, args );
fputs( buf2, stderr );
fputs( "\n", stderr );
fflush( stderr );
va_end( args );
}
/*
* Object validity checker. Returns the data pointer.
*/
static struct xprison *
rbjail_check_jail( VALUE self ) {
debugMsg(( "Checking a BSD::Jail object (%d).", self ));
Check_Type( self, T_DATA );
if ( !rb_obj_is_kind_of(self, rbjail_cBSDJail) ) {
rb_raise( rb_eTypeError, "wrong argument type %s (expected BSD::Jail)",
rb_class2name(CLASS_OF( self )) );
}
return DATA_PTR( self );
}
/*
* Fetch the data pointer and check it for sanity.
*/
static struct xprison *
rbjail_get_jailptr( VALUE self ) {
struct xprison *ptr = rbjail_check_jail( self );
debugMsg(( "Fetching a Jail (%p).", ptr ));
if ( !ptr )
rb_raise( rb_eRuntimeError, "uninitialized Jail" );
return ptr;
}
/*
* Copy memory from the given 'xp' to a ruby managed object.
*/
static VALUE
rbjail_alloc( VALUE class, struct xprison *xp )
{
struct xprison *rbjail_xp = ALLOC( struct xprison );
VALUE rbjail = rb_funcall( class, rb_intern("allocate"), 0 );
// replace the null pointer obj with an xprison ptr.
//
memcpy( rbjail_xp, xp, sizeof( struct xprison ) );
DATA_PTR( rbjail ) = rbjail_xp;
return rbjail;
}
/*
*
*/
static VALUE
rbjail_get_ip( VALUE self )
{
struct xprison *xp = rbjail_get_jailptr( self );
struct in_addr in;
char *ip;
in.s_addr = ntohl( xp->pr_ip );
ip = inet_ntoa(in);
return rb_funcall( rbjail_cIPAddr, rb_intern("new"), 1, rb_str_new2(ip) );
}
/*
*
*/
static VALUE
rbjail_get_jid( VALUE self )
{
struct xprison *xp = rbjail_get_jailptr( self );
return INT2FIX( xp->pr_id );
}
/*
*
*/
static VALUE
rbjail_get_host( VALUE self )
{
struct xprison *xp = rbjail_get_jailptr( self );
return rb_str_new2( xp->pr_host );
}
/*
*
*/
static VALUE
rbjail_get_path( VALUE self )
{
struct xprison *xp = rbjail_get_jailptr( self );
return rb_str_new2( xp->pr_path );
}
/*
* GC Free function
*/
static void
rbjail_gc_free( struct xprison *ptr ) {
if ( ptr ) {
xfree( ptr );
}
else {
debugMsg(( "Not freeing an uninitialized jail" ));
}
}
/*
* Allocate a new ruby object with a NULL pointer.
*/
static VALUE
rbjail_s_alloc( VALUE class )
{
return Data_Wrap_Struct( class, NULL, rbjail_gc_free, NULL );
}
/*
* Create a new jail object.
* Returns the +id+ of the newly created jail.
*/
static VALUE
rbjail_jail( int argc, VALUE *argv, VALUE self ) {
struct jail j;
struct in_addr in;
VALUE ip, path, host, securelevel;
int id;
rb_scan_args( argc, argv, "31", &ip, &path, &host, &securelevel );
if ( inet_aton( RSTRING_PTR( rb_obj_as_string(ip) ), &in ) == 0 )
rb_raise( rb_eArgError, "Could not make sense of ip number: %s", ip );
SafeStringValue(path);
SafeStringValue(host);
j.version = 0;
j.path = RSTRING_PTR( path );
j.hostname = RSTRING_PTR( host );
j.ip_number = ntohl( in.s_addr );
id = jail(&j);
if ( id == -1 ) rb_sys_fail( "jail" );
if ( chdir("/") == -1 ) rb_sys_fail( "chdir" );
debugMsg(( "New jail created with id: %d\n", id ));
return INT2FIX( id );
}
/*
* Iterate over the currently instantiated jails, returning a jail
* object that matches the given string/ip/JID -- or nil if none do.
* Without an argument, return an array of all JIDs.
*/
static VALUE
rbjail_find( int argc, VALUE *argv, VALUE self )
{
struct xprison *sxp, *xp;
struct in_addr in;
size_t i, len;
VALUE arg, rbjail;
VALUE jails = rb_ary_new();
int jid = 0, compare = 0;
char *str = "";
rb_scan_args( argc, argv, "01", &arg );
// An argument was passed, so let's figure out what it was
// and try to compare it to the current jails.
//
if ( argc == 1 ) {
switch ( TYPE(arg) ) {
// find by JID
//
case T_FIXNUM:
jid = FIX2INT( arg );
break;
// find by IP/hostname
//
case T_OBJECT:
case T_DATA:
case T_STRING:
str = RSTRING_PTR( rb_obj_as_string(arg) );
compare = 1;
break;
default:
rb_raise( rb_eTypeError, "invalid argument to find(): %s",
RSTRING_PTR(rb_inspect(arg)) );
}
}
// Get the size of the xprison and allocate memory to it.
//
if ( sysctlbyname("security.jail.list", NULL, &len, NULL, 0) == -1 )
rb_sys_fail("sysctlbyname(): security.jail.list");
if ( len <= 0 ) {
rb_sys_fail("sysctlbyname(): unable to determine xprison size");
return Qnil;
}
sxp = xp = malloc( len );
if ( sxp == NULL ) {
rb_sys_fail("sysctlbyname(): unable to allocate memory");
return Qnil;
}
// Get and sanity check the current prison list
//
if ( sysctlbyname("security.jail.list", xp, &len, NULL, 0) == -1 ) {
if ( errno == ENOMEM ) free( sxp );
rb_sys_fail("sysctlbyname(): out of memory");
return Qnil;
}
if ( len < sizeof(*xp) || len % sizeof(*xp) || xp->pr_version != XPRISON_VERSION )
rb_fatal("Kernel and userland out of sync");
// No arguments to find() -- return an array of all JIDs
//
if ( argc == 0 ) {
for ( i = 0; i < len / sizeof(*xp); i++ ) {
rb_ary_push( jails, rbjail_alloc( self, xp ) );
xp++;
}
free( sxp );
return jails;
}
// Argument passed to find(): walk the jail list, comparing the arg
// with each current jail. Return first match.
//
for ( i = 0; i < len / sizeof(*xp); i++ ) {
in.s_addr = ntohl(xp->pr_ip);
if (( compare == 0 && xp->pr_id == jid ) ||
( compare == 1 &&
(( strcmp( str, inet_ntoa(in) ) == 0 ) ||
( strcmp( str, xp->pr_host ) == 0 ))
)) {
debugMsg(( "Located jail: %d", xp->pr_id ));
rbjail = rbjail_alloc( self, xp );
free( sxp );
return rbjail;
}
else {
xp++;
}
}
free( sxp );
return Qnil;
}
static void
rbjail_do_jail_attach( int jid )
{
if ( jail_attach(jid) == -1 )
rb_sys_fail( "jail_attach" );
}
/* Mostly ripped off from Ruby's process.c */
static VALUE
rbjail_attach_block( int jid )
{
int pid;
rb_secure(2);
fflush(stdout);
fflush(stderr);
switch ( pid = fork() ) {
case 0:
rb_thread_atfork();
if ( rb_block_given_p() ) {
int status;
rbjail_do_jail_attach( jid );
rb_protect( rb_yield, Qundef, &status );
ruby_stop( status );
}
return Qnil;
case -1:
rb_sys_fail( "fork(2)" );
return Qnil;
default:
return INT2FIX( pid );
}
}
static VALUE
rbjail_attach( int argc, VALUE *argv, VALUE self )
{
VALUE jidnum, rval;
int jid;
rb_scan_args( argc, argv, "1", &jidnum );
jid = NUM2INT( jidnum );
if ( rb_block_given_p() ) {
rval = rbjail_attach_block( jid );
}
else {
rbjail_do_jail_attach( jid );
rval = Qtrue;
}
return rval;
}
/*
* Ruby initializer.
*/
void
Init_jail( void )
{
rbjail_mBSD = rb_define_module( "BSD" );
rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail", rb_cObject );
rb_require("ipaddr");
rbjail_cIPAddr = rb_const_get( rb_cObject, rb_intern("IPAddr") );
rb_define_alloc_func( rbjail_cBSDJail, rbjail_s_alloc );
// Make the 'new' method private.
rb_funcall( rbjail_cBSDJail, rb_intern("private_class_method"), 1, ID2SYM(rb_intern("new")) );
// main utility functions
//
rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail, -1 );
rb_define_singleton_method( rbjail_cBSDJail, "find", rbjail_find, -1 );
rb_define_method( rbjail_cBSDJail, "attach", rbjail_attach, -1 );
// rb_define_alias( rbjail_cBSDJail, "list", "find" );
// accessor functions
//
rb_define_method( rbjail_cBSDJail, "jid", rbjail_get_jid, 0 );
rb_define_method( rbjail_cBSDJail, "ip", rbjail_get_ip, 0 );
rb_define_method( rbjail_cBSDJail, "host", rbjail_get_host, 0 );
rb_define_method( rbjail_cBSDJail, "path", rbjail_get_path, 0 );
}