# HG changeset patch # User Mahlon E. Smith # Date 1236119025 0 # Node ID 4460fc10c6a3692879e9f69ea3fa69a7af9a67a2 # Parent 2d52adc4adcc6c1bc11baaa7da37103ff1a8b75c * Now with 87% more hot jail action! * Predeclared all C methods in jail.h, so they could be arranged in logical order in jail.c * Fixed the extconf namespace. * Added rdoc. * Added usage examples, demonstrating jls, jexec, and jail ruby equivalents. * Re-added the "attach and execute within a block" code. * Added Enumerable and Comparable support. * Return 'path' as a Pathname object. TODO: * Create the actual 'jParallel' shell binary, now that we have a good backend framework. * Tests? How? * Add support for recently committed (will be part of 7.2-RELEASE) multiple IPs per jail, and jail labels. diff -r 2d52adc4adcc -r 4460fc10c6a3 examples/jail.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/jail.rb Tue Mar 03 22:23:45 2009 +0000 @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby +# +# An example 'jail' utility in ruby. +# + +BEGIN { + require 'pathname' + basedir = Pathname.new( __FILE__ ).dirname.parent + + $LOAD_PATH.unshift basedir + "ext" unless + $LOAD_PATH.include? basedir + "ext" +} + +require 'jail' + +jid = BSD::Jail.create( '127.0.0.1', '/tmp', 'testjail' ) +puts "New jail created with id: %d" % [ jid ] + +# We're in the jail, now. +Dir.foreach('.') { |i| puts i } + diff -r 2d52adc4adcc -r 4460fc10c6a3 examples/jexec.rb --- a/examples/jexec.rb Sat Feb 28 06:52:48 2009 +0000 +++ b/examples/jexec.rb Tue Mar 03 22:23:45 2009 +0000 @@ -1,29 +1,18 @@ -#!/usr/bin/ruby +#!/usr/bin/env ruby +# +# An example 'jexec' in ruby. +# BEGIN { require 'pathname' basedir = Pathname.new( __FILE__ ).dirname.parent - + $LOAD_PATH.unshift basedir + "ext" unless $LOAD_PATH.include? basedir + "ext" } - -require 'bsdjail' - -jid = Integer( ARGV.shift ) -args = ARGV - -$deferr.puts "In process #{Process.pid}, about to jail_attach() with a block" +require 'jail' -childpid = BSD::Jail.attach( jid ) do - Dir.chdir( "/" ) - $deferr.puts "Child #{Process.pid} exec()ing:", " " + args.join(" ") - exec( *args ) -end +BSD::Jail.attach( ARGV.shift.to_i ) +exec( *ARGV ) -$deferr.puts "Parent: waiting on imprisoned child #{childpid}" -Process.waitpid( childpid ) - -$deferr.puts "Child exited with exit code: %d" % [ $?.exitstatus ] - diff -r 2d52adc4adcc -r 4460fc10c6a3 examples/jexec2.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/jexec2.rb Tue Mar 03 22:23:45 2009 +0000 @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +# +# A 'smarter' jexec in ruby. +# +# Run a command in multiple jails in parallel. +# Jails can be selected via host, ip, or jid. +# +# "Run 'ls' in all jails that match the hostname 'trac': +# ./jexec2.rb trac ls +# + +BEGIN { + require 'pathname' + basedir = Pathname.new( __FILE__ ).dirname.parent + + $LOAD_PATH.unshift basedir + "ext" unless + $LOAD_PATH.include? basedir + "ext" +} + +require 'jail' + +jarg = ARGV.shift +args = ARGV + +jails = BSD::Jail.find_all do |j| + j.jid == jarg.to_i || + j.ip.to_s == jarg || + j.host =~ /#{jarg}/ +end or raise "Unable to find jails that match '#{jarg}'." + + +jails.each do |j| + $deferr.puts "Parent #{Process.pid} about to attach to #{j.host} in a block!" + childpid = j.attach do + $deferr.puts "Child #{Process.pid} exec()ing:", " " + args.join(" ") + exec( *args ) + end + + $deferr.puts "Parent: waiting on imprisoned child #{childpid}" + Process.waitpid( childpid ) + $deferr.puts "Child exited with exit code: %d" % [ $?.exitstatus ] +end + diff -r 2d52adc4adcc -r 4460fc10c6a3 examples/jls.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/jls.rb Tue Mar 03 22:23:45 2009 +0000 @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby +# +# An example of the 'jls' utility in ruby. +# Output is sorted by hostname. +# + +BEGIN { + require 'pathname' + basedir = Pathname.new( __FILE__ ).dirname.parent + + $LOAD_PATH.unshift basedir + "ext" unless + $LOAD_PATH.include? basedir + "ext" +} + +require 'jail' + +puts " JID IP Address Hostname Path" +BSD::Jail.sort_by {|j| j.host }.each do |j| + puts "%6d %-15.15s %-29.29s %.74s" % [ j.jid, j.ip, j.host, j.path ] +end + diff -r 2d52adc4adcc -r 4460fc10c6a3 examples/startjail.rb diff -r 2d52adc4adcc -r 4460fc10c6a3 ext/bsdjail.c --- a/ext/bsdjail.c Sat Feb 28 06:52:48 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,366 +0,0 @@ -/* - * bsdjail.c - JParallel extension for FreeBSD jail(2) functions - * $Id$ - * - * Authors: - * * Michael Granger - * * Mahlon E. Smith - * - * Copyright (c) 2008, Michael Granger and Mahlon 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 "bsdjail.h" - -VALUE rbjail_mBSD; -VALUE rbjail_cBSDJail; - - -/* - * 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 ); -} - - -/* -struct jail { - u_int32_t version; - char *path; - char *hostname; - u_int32_t ip_number; -}; -*/ - -/* - * Allocation function - */ -static jail * -rbjail_jail_alloc() { - jail *ptr = ALLOC( jail ); - - ptr->version = 0; - ptr->path = NULL; - ptr->hostname = NULL; - ptr->ip_number = 0; - - debugMsg(( "Initialized a jail pointer <%p>", ptr )); - return ptr; -} - - -/* - * GC Free function - */ -static void -rbjail_gc_free( jail *ptr ) { - if ( ptr ) { - if ( ptr->path ) xfree( ptr->path ); - if ( ptr->hostname ) xfree( ptr->hostname ); - - ptr->path = NULL; - ptr->hostname = NULL; - - xfree( ptr ); - } - - else { - debugMsg(( "Not freeing an uninitialized jail" )); - } -} - - -/* - * Object validity checker. Returns the data pointer. - */ -static jail * -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 jail * -rbjail_get_jail( VALUE self ) { - jail *ptr = check_sentence( self ); - - debugMsg(( "Fetching a Jail (%p).", ptr )); - if ( !ptr ) - rb_raise( rb_eRuntimeError, "uninitialized Sentence" ); - - return ptr; -} - - -/* -------------------------------------------------------------- - * Jail utility functions - * -------------------------------------------------------------- */ - -/* - * Try to jail_attach() to the specified +jid+, raising an exception if it fails. - */ -static void -rbjail_do_jail_attach( int jid ) -{ - if ( jail_attach(jid) == -1 ) - rb_sys_fail( "jail_attach" ); -} - - -/* - * Fork + Block function for rbjail_attach(). Mostly stolen 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 ); - } -} - - -/* -------------------------------------------------------------- - * Class methods - * -------------------------------------------------------------- */ - -/* - * call-seq: - * BSD::Jail.allocate -> bsdjail - * - * Allocate a new BSD::Jail object. - * - */ -static VALUE -rbjail_jail_s_allocate( VALUE klass ) { - return Data_Wrap_Struct( klass, 0, rbjail_gc_free, 0 ); -} - - -/* - * call-seq: - * BSD::Jail.list -> array - * - * Return an Array of all the running jails on the host. - * - */ -static VALUE -rbjail_jail_s_list( VALUE klass ) { - VALUE rval = rb_ary_new(); - struct xprison *xp; - struct in_addr in; - size_t i, len; - - if ( sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1 ) - rb_sys_fail( "sysctlbyname(): jail.list" ); - - xp = ALLOCA_N( xprison, 1 ); - - if ( sysctlbyname("jail.list", xp, &len, NULL, 0) == -1 ) { - rb_sys_fail( "sysctlbyname(): jail.list" ); - } - - if ( len < sizeof(*xp) || len % sizeof(*xp) || - xp->pr_version != KINFO_PRISON_VERSION ) - rb_fatal( "Kernel and userland out of sync" ); - - len /= sizeof( *xp ); - for ( i = 0; i < len; i++ ) { - VALUE jail, args[3]; - - /* Hostname */ - args[0] = rb_str_new2( xp->pr_host ); - - /* IP */ - in.s_addr = ntohl( xp->pr_ip ); - args[1] = rb_str_new2( inet_ntoa(in) ); - - /* Path */ - args[2] = rb_str_new2( xp->pr_path ); - - jail = rb_class_new_instance( 3, args, klass ); - rb_ary_push( rval, jail ); - - xp++; - } - - return rval; -} - - - - -/* -------------------------------------------------------------- - * Instance methods - * -------------------------------------------------------------- */ - -/* - * call-seq: - * BSD::Jail.new( hostname, ip, path ) -> new_jail - * - * Create a new jail for the given +hostname+, +ip+, and +path+. - */ -static VALUE -rbjail_jail_initialize( VALUE self, VALUE hostname, VALUE ip, VALUE path ) { - if ( !rbjail_check_jail(self) ) { - struct jail *ptr = rbjail_jail_alloc(); - char *pathstr = NULL, *hostnamestr = NULL; - - Check_Type( hostname, T_STRING ); - Check_Type( ip, T_STRING ); - - pathstr = ALLOC_N( char, RSTRING(path)->len ); - strncpy( pathstr, RSTRING(path)->ptr, RSTRING(path)->len ); - - hostnamestr = ALLOC_N( char, RSTRING(hostname)->len ); - strncpy( hostnamestr, RSTRING(hostname)->ptr, RSTRING(hostname)->len ); - - /* - struct jail { - u_int32_t version; - char *path; - char *hostname; - u_int32_t ip_number; - }; - */ - ptr->version = 0; /* Per the manpage's recommendation */ - ptr->ip_number = inet_addr( StringValuePtr(ip) ); - ptr->path = path; - ptr->hostname = hostname; - } -} - - -/* - * call-seq: - * jail.attach -> true or false - * jail.attach { block } -> pid - * - * Attach to the given jail. In the non-block form, attach the current process to the - * jail and return +true+ if it succeeds. This is a one-way operation, and requires root - * privileges. - * - * In the block form, the process will be forked, and the block will be attached to the jail and - * run by the child. The parent process will receive the process ID of the child. - */ -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; -} - - -/* - * I can't remember how the hell you document a class, but that will go here. - */ -void -Init_bsdjail( void ) { - rbjail_mBSD = rb_define_module( "BSD" ); - rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail" ); - - /* Class methods */ - rb_define_alloc_function( rbjail_cBSDJail, rbjail_jail_s_allocate ); - rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail_s_jail, 0 ); - rb_define_singleton_method( rbjail_cBSDJail, "list", rbjail_jail_s_list, 0 ); - - /* Instance methods */ - rb_define_method( rbjail_cBSDJail, "initialize", rbjail_jail_initialize, 3 ); - rb_define_method( rbjail_cBSDJail, "attach", rbjail_jail_attach, -1 ); - rb_define_method( rbjail_cBSDJail, "hostname", rbjail_jail_hostname, 0 ); - rb_define_method( rbjail_cBSDJail, "path", rbjail_jail_path, 0 ); - rb_define_method( rbjail_cBSDJail, "ip", rbjail_jail_ip, 0 ); - rb_define_method( rbjail_cBSDJail, "jid", rbjail_jail_jid, 0 ); - -} - diff -r 2d52adc4adcc -r 4460fc10c6a3 ext/bsdjail.h --- a/ext/bsdjail.h Sat Feb 28 06:52:48 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -/* - * bsdjail.h - Header for the bsdjail extension - * $Id$ - * - * Authors: - * * Michael Granger - * * Mahlon E. Smith - * - * Copyright (c) 2008, Michael Granger and Mahlon 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. - * - */ - -#ifndef _BSDJAIL_H_ -#define _BSDJAIL_H_ - - -#include -#include -#include -#include -#include - -#include -#include /* For rb_dbl2big() */ - - -/* Debugging functions/macros */ -#ifdef HAVE_STDARG_PROTOTYPES -#include -#define va_init_list(a,b) va_start(a,b) -extern void rbjail_debug(const char *fmt, ...); -#else -#include -#define va_init_list(a,b) va_start(a) -extern void rbjail_debug(fmt, va_alist); -#endif - - -/* Debugging macro */ -#if DEBUG -# define debugMsg(f) rbjail_debug f -#else /* ! DEBUG */ -# define debugMsg(f) -#endif /* DEBUG */ - - -#endif /* _BSDJAIL_H_ */ - diff -r 2d52adc4adcc -r 4460fc10c6a3 ext/extconf.rb --- a/ext/extconf.rb Sat Feb 28 06:52:48 2009 +0000 +++ b/ext/extconf.rb Tue Mar 03 22:23:45 2009 +0000 @@ -22,15 +22,18 @@ end -dir_config( 'bsdjail' ) +dir_config( 'bsd/jail' ) -have_header( 'stdio.h' ) or fail "Can't find the stdio.h header." -have_header( 'sys/param.h' ) or fail "Can't find the sys/param.h header." -have_header( 'sys/jail.h' ) or fail "Can't find the sys/jail.h header." -have_header( 'sys/types.h' ) or fail "Can't find the sys/types.h header." -have_header( 'unistd.h' ) or fail "Can't find the unistd.h header." +have_header( 'stdio.h' ) or fail "Can't find the stdio.h header." +have_header( 'sys/param.h' ) or fail "Can't find the sys/param.h header." +have_header( 'sys/jail.h' ) or fail "Can't find the sys/jail.h header." +have_header( 'sys/types.h' ) or fail "Can't find the sys/types.h header." +have_header( 'sys/sysctl.h' ) or fail "Can't find the sys/sysctl.h header." +have_header( 'unistd.h' ) or fail "Can't find the unistd.h header." +have_header( 'arpa/inet.h' ) or fail "Can't find the arpa/inet.h header." +have_header( 'errno.h' ) or fail "Can't find the errno.h header." -have_func( "jail_attach" ) or fail "Can't find jail_attach in the stdlib." +have_func( "jail_attach" ) or fail "Can't find jail_attach in the stdlib." -create_makefile( 'bsdjail' ) +create_makefile( 'bsd/jail' ) diff -r 2d52adc4adcc -r 4460fc10c6a3 ext/jail.c --- a/ext/jail.c Sat Feb 28 06:52:48 2009 +0000 +++ b/ext/jail.c Tue Mar 03 22:23:45 2009 +0000 @@ -1,5 +1,5 @@ /* - * jail.c - Ruby jparallel + * jail.c - Ruby jParallel * * vim: set nosta noet ts=4 sw=4: * @@ -39,13 +39,12 @@ * */ - #include "jail.h" -VALUE rbjail_mBSD; -VALUE rbjail_cBSDJail; -VALUE rbjail_cIPAddr; +/* -------------------------------------------------------------- + * Utility functions + * -------------------------------------------------------------- */ /* * Debug logging function @@ -83,7 +82,7 @@ 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 )) ); + rb_obj_classname( self )); } return DATA_PTR( self ); @@ -106,6 +105,62 @@ /* + * Attach to a running jail and chdir to the root. + */ +static int +rbjail_do_attach( int jid ) +{ + int attach_status = jail_attach(jid); + + if ( attach_status == -1 ) + rb_sys_fail( "jail_attach" ); + if ( chdir("/") == -1 ) rb_sys_fail( "chdir" ); + + return attach_status; +} + + +/* + * Attach to a running jail from within a block, forking first + * and returning the child pid. + */ +static VALUE +rbjail_attach_block( int jid ) +{ + int pid; + int status; + + if ( ! rb_block_given_p() ) return Qnil; + + rb_secure(2); + + fflush(stdout); + fflush(stderr); + + switch ( pid = fork() ) { + case 0: + rb_thread_atfork(); + rbjail_do_attach( jid ); + rb_protect( rb_yield, Qundef, &status ); + rb_exit( status ); + return Qnil; + + case -1: + rb_sys_fail( "fork(2)" ); + return Qnil; + + default: + return INT2FIX( pid ); + } +} + + + +/* -------------------------------------------------------------- + * Memory-management functions + * -------------------------------------------------------------- */ + +/* * Copy memory from the given 'xp' to a ruby managed object. */ static VALUE @@ -124,56 +179,6 @@ /* - * - */ -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 @@ -198,18 +203,30 @@ } +/* -------------------------------------------------------------- + * Class methods + * -------------------------------------------------------------- */ + /* - * Create a new jail object. - * Returns the +id+ of the newly created jail. + * call-seq: + * BSD::Jail.new( ip, path, host ) => jail_id + * + * Create a new BSD::Jail object from required +ip+, +path+, and +host+ + * arguments. You can optionally pass a +securelevel+ fourth argument. + * + * Returns the +id+ of the newly created jail. */ static VALUE -rbjail_jail( int argc, VALUE *argv, VALUE self ) { +rbjail_jail( int argc, VALUE *argv, VALUE self ) +{ struct jail j; struct in_addr in; - VALUE ip, path, host, securelevel; + VALUE ip, path, host, sec_level; int id; + int securelevel = -1; - rb_scan_args( argc, argv, "31", &ip, &path, &host, &securelevel ); + rb_scan_args( argc, argv, "31", &ip, &path, &host, &sec_level ); + if ( sec_level != Qnil ) securelevel = FIX2INT( sec_level ); 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 ); @@ -226,16 +243,62 @@ if ( id == -1 ) rb_sys_fail( "jail" ); if ( chdir("/") == -1 ) rb_sys_fail( "chdir" ); + if ( securelevel > -1 && securelevel < 4 ) { + debugMsg(( "Setting securelevel to: %d", securelevel )); + if ( sysctlbyname("kern.securelevel", NULL, 0, &securelevel, sizeof(securelevel)) ) + rb_sys_fail( "securelevel" ); + } + 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. - */ + * call-seq: + * BSD::Jail.find_by_jid( jid ) => BSD::Jail + * + * Iterate over the currently instantiated jails, returning a jail + * object that matches the given +jid+, or nil if no jail is found. + * + */ +static VALUE +rbjail_find_by_jid( VALUE self, VALUE jid ) +{ + VALUE args[0]; + + if ( TYPE(jid) != T_FIXNUM ) + rb_raise( rb_eTypeError, "invalid argument to find_by_jid(): %s", + RSTRING_PTR( rb_inspect(jid)) ); + + args[0] = jid; + return rbjail_find( 1, args, self ); +} + + +/* + * call-seq: + * BSD::Jail.search( id ) => BSD::Jail + * BSD::Jail.search( hostname ) => [ BSD::Jail, ... ] + * BSD::Jail.search( IPAddr.new('127.0.0.1') ) => [ BSD::Jail, ... ] + * BSD::Jail.search( Pathname.new('/tmp') ) => [ BSD::Jail, ... ] + * BSD::Jail.search => [ BSD::Jail, BSD::Jail, ... ] + * BSD::Jail.search { |obj| block } => obj + * + * Iterate over the currently instantiated jails, returning a jail + * object that matches the given argument. + * + * If the argument is an integer, it is assumed to be a JID. + * Otherwise, it is converted to a string, and compared against + * IP addresses, hostnames, and paths. JIDs are unique, so there is + * only one object that can match. Only the matching object (or nil) + * is returned in that event. IPs, hostnames, and paths can be + * shared between jails, so searching on those return an array populated + * with matched jail objects -- or if there are no valid matches, an empty array. + * + * Without an argument, return an array of all jails as objects. + * + */ static VALUE rbjail_find( int argc, VALUE *argv, VALUE self ) { @@ -262,8 +325,8 @@ jid = FIX2INT( arg ); break; - // find by IP/hostname - // + // find by IP/hostname/path + // case T_OBJECT: case T_DATA: case T_STRING: @@ -272,8 +335,8 @@ break; default: - rb_raise( rb_eTypeError, "invalid argument to find(): %s", - RSTRING_PTR(rb_inspect(arg)) ); + rb_raise( rb_eTypeError, "invalid argument: %s", + RSTRING_PTR( rb_inspect(arg)) ); } } @@ -302,10 +365,13 @@ 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 + // No arguments to find() -- yield each successive jail, + // and return an array of all jail objects. // if ( argc == 0 ) { for ( i = 0; i < len / sizeof(*xp); i++ ) { + rbjail = rbjail_alloc( self, xp ); + if ( rb_block_given_p() ) rb_yield( rbjail ); rb_ary_push( jails, rbjail_alloc( self, xp ) ); xp++; } @@ -315,21 +381,32 @@ } // Argument passed to find(): walk the jail list, comparing the arg - // with each current jail. Return first match. + // with each current jail. Return all matches. // 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 )) + ( strcmp( str, xp->pr_host ) == 0 ) || + ( strcmp( str, xp->pr_path ) == 0 )) )) { debugMsg(( "Located jail: %d", xp->pr_id )); - rbjail = rbjail_alloc( self, xp ); - free( sxp ); - return rbjail; + // If the user already knows the JID, there can only be + // one match. Return it immediately. + // + if ( compare == 0 ) { + free( sxp ); + return rbjail_alloc( self, xp ); + } + + // If searching for anything other than JID, the argument + // isn't unique. Put matching jails into an array. + // + rb_ary_push( jails, rbjail_alloc( self, xp ) ); + xp++; } else { @@ -338,102 +415,295 @@ } free( sxp ); - return Qnil; -} - -static void -rbjail_do_jail_attach( int jid ) -{ - if ( jail_attach(jid) == -1 ) - rb_sys_fail( "jail_attach" ); + if ( compare == 0 && rb_ary_shift( jails ) == Qnil ) + return Qnil; + + return jails; } -/* Mostly ripped off from Ruby's process.c */ +/* + * call-seq: + * BSD::Jail.attach( id ) => true + * BSD::Jail.attach( hostname ) => true + * + * Attach to a currently running jail instance directly. + * Operates under the same rules as BSD::Jail#search -- you may + * specify a jail by IP Address, hostname, or jid. + * + * Please note that attaching your process into a jail is a one way + * operation that requires root privileges. You must fork() if + * your process needs to continue in the host environment. + * + */ static VALUE -rbjail_attach_block( int jid ) +rbjail_class_attach( VALUE self, VALUE arg ) { - int pid; - - rb_secure(2); - - fflush(stdout); - fflush(stderr); + int jid = 0; + VALUE jails; + VALUE find_args [0]; - switch ( pid = fork() ) { - case 0: - rb_thread_atfork(); - if ( rb_block_given_p() ) { - int status; + switch ( TYPE(arg) ) { + + // The user knows the JID already, attach directly. + // + case T_FIXNUM: + + jid = FIX2INT( arg ); + rbjail_do_attach( jid ); + break; - rbjail_do_jail_attach( jid ); - rb_protect( rb_yield, Qundef, &status ); - ruby_stop( status ); - } - return Qnil; + // Find the JID to attach to. First match wins. + // + case T_OBJECT: + case T_DATA: + case T_STRING: - case -1: - rb_sys_fail( "fork(2)" ); - return Qnil; + find_args[0] = arg; + jails = rbjail_find( 1, find_args, self ); + jid = FIX2INT( rbjail_get_jid( rb_ary_shift(jails) )); + rbjail_do_attach( jid ); + break; default: - return INT2FIX( pid ); + rb_raise( rb_eTypeError, "invalid argument to attach(): %s", + RSTRING_PTR( rb_inspect(arg)) ); } + + return Qtrue; } + +/* -------------------------------------------------------------- + * Instance methods + * -------------------------------------------------------------- */ + +/* + * call-seq: + * BSD::Jail <=> BSD::Jail + * + * Interface for Comparable. + * + */ static VALUE -rbjail_attach( int argc, VALUE *argv, VALUE self ) +rbjail_compare( VALUE self, VALUE other ) { - VALUE jidnum, rval; - int jid; + debugMsg(("self id: %s, other id: %s", + RSTRING_PTR(rb_inspect( self )), + RSTRING_PTR(rb_inspect( other )))); - rb_scan_args( argc, argv, "1", &jidnum ); - jid = NUM2INT( jidnum ); + return rb_funcall( + rbjail_get_jid( self ), + rb_intern("<=>"), + 1, + rbjail_get_jid( other ) + ); +} + - if ( rb_block_given_p() ) { - rval = rbjail_attach_block( jid ); - } +/* + * Fetch the configured IP address for the jail. + * Returns an IPAddr object. + * + */ +static VALUE +rbjail_get_ip( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + struct in_addr in; + VALUE args [0]; - else { - rbjail_do_jail_attach( jid ); - rval = Qtrue; - } + in.s_addr = ntohl( xp->pr_ip ); + args[0] = rb_str_new2( inet_ntoa(in) ); + + return rb_class_new_instance( 1, args, rbjail_cIPAddr ); +} + - return rval; +/* + * Fetch the assigned JID for the jail. + * + */ +static VALUE +rbjail_get_jid( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + return INT2FIX( xp->pr_id ); } /* - * Ruby initializer. + * Fetch the configured hostname for the jail as a string. + * + */ +static VALUE +rbjail_get_host( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + return rb_str_new2( xp->pr_host ); +} + + +/* + * Fetch the configured path for the jail. + * Returns a Pathname object. + * + */ +static VALUE +rbjail_get_path( VALUE self ) +{ + struct xprison *xp = rbjail_get_jailptr( self ); + VALUE args[0]; + + args[0] = rb_str_new2( xp->pr_path ); + + return rb_class_new_instance( 1, args, rbjail_cPathname ); +} + + +/* + * Return a human readable version of the object. + */ +static VALUE +rbjail_inspect( VALUE self ) +{ + char inspect_str[BUFSIZ]; + + sprintf( inspect_str, "#<%s:0x%07x jid:%d (%s)>", + rb_obj_classname( self ), + NUM2UINT(rb_obj_id( self )) * 2, + FIX2INT(rbjail_get_jid( self )), + RSTRING_PTR(rbjail_get_host( self )) + ); + + return rb_str_new2( inspect_str ); +} + + +/* + * call-seq: + * jail.attach + * jail.attach do ... end => child pid + * + * Attach to the jail. + * + * Please note that attaching your process into a jail is a one way + * operation that requires root privileges. You must fork() if + * your process needs to continue in the host environment. + * + * In the block form, a fork() is performed automatically. + * + */ +static VALUE +rbjail_instance_attach( VALUE self ) +{ + int jid = FIX2INT( rbjail_get_jid( self ) ); + + if ( rb_block_given_p() ) { + return rbjail_attach_block( jid ); + } + + else { + rbjail_do_attach( jid ); + } + + return Qtrue; +} + + +/* -------------------------------------------------------------- + * Initalizer + * -------------------------------------------------------------- */ + +/* + * BSD::Jail + * + * Ruby bindings for the FreeBSD jail(2) subsystem. + * + * Example usage: + * + * require 'bsd/jail' + * + * # create a new jail + * jid = BSD::Jail.create( '127.0.0.1', '/tmp', 'testjail' ) + * + * # find existing jail(s) + * jail = BSD::Jail.find_by_jid( jid ) + * jails = BSD::Jail.search( hostname ) + * jails = BSD::Jail.search( IPAddr.new('127.0.0.1') ) + * jails = BSD::Jail.search( Pathname.new('/tmp') ) + * + * # attach to jails + * BSD::Jail.attach( jid ) + * jail.attach do + * ... do something fancy! + * end + * + * BSD::Jail includes behaviors from Enumerable and Comparable, + * so you can do things such as the following: + * + * # Are these two instantiated jails the same jid? + * BSD::Jail.search( '/tmp' ) == BSD::Jail.search( 'testjail' ) + * + * # Alternative interface for finding a jail by jid + * BSD::Jail.find { |j| j.jid == 3 } + * + * # Return all jails as an array + * jails = BSD::Jail.find_all + * + * # What jails have IPs within the 192.168.16.0/24 class C netblock? + * nb = IPAddr.new( '192.168.16.0/24' ); + * jails = BSD::Jail.find? { |j| nb.include?(j.ip) } + * */ void Init_jail( void ) { + // namespacing + // 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") ); - + // struct wrapping function 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 + // class methods + // + rb_define_singleton_method( rbjail_cBSDJail, "attach", rbjail_class_attach, 1 ); + rb_define_singleton_method( rbjail_cBSDJail, "create", rbjail_jail, -1 ); + rb_define_singleton_method( rbjail_cBSDJail, "search", rbjail_find, -1 ); + rb_define_singleton_method( rbjail_cBSDJail, "find_by_jid", rbjail_find_by_jid, 1 ); + + // instance methods // - 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, "attach", rbjail_instance_attach, 0 ); + rb_define_method( rbjail_cBSDJail, "host", rbjail_get_host, 0 ); + rb_define_alias( rbjail_cBSDJail, "hostname", "host" ); + rb_define_method( rbjail_cBSDJail, "inspect", rbjail_inspect, 0 ); + rb_define_method( rbjail_cBSDJail, "ip", rbjail_get_ip, 0 ); + rb_define_method( rbjail_cBSDJail, "jid", rbjail_get_jid, 0 ); + rb_define_method( rbjail_cBSDJail, "path", rbjail_get_path, 0 ); + + // Additional (base) modules for accessor wrapping // - 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 ); + rb_require( "ipaddr" ); + rb_require( "pathname" ); + rbjail_cIPAddr = rb_const_get( rb_cObject, rb_intern("IPAddr") ); + rbjail_cPathname = rb_const_get( rb_cObject, rb_intern("Pathname") ); + + // Enumerable! + // + rb_define_singleton_method( rbjail_cBSDJail, "each", rbjail_find, -1 ); + rb_extend_object( rbjail_cBSDJail, rb_const_get( rb_cObject, rb_intern("Enumerable") )); + + // Comparable! + // + rb_define_method( rbjail_cBSDJail, "<=>", rbjail_compare, 1 ); + rb_include_module( rbjail_cBSDJail, rb_const_get( rb_cObject, rb_intern("Comparable") )); } diff -r 2d52adc4adcc -r 4460fc10c6a3 ext/jail.h --- a/ext/jail.h Sat Feb 28 06:52:48 2009 +0000 +++ b/ext/jail.h Tue Mar 03 22:23:45 2009 +0000 @@ -76,3 +76,47 @@ #endif /* _BSDJAIL_H_ */ + +/* -------------------------------------------------------------- + * Predeclarations + * -------------------------------------------------------------- */ + +// globals +// +VALUE rbjail_mBSD; +VALUE rbjail_cBSDJail; +VALUE rbjail_cIPAddr; +VALUE rbjail_cPathname; + +// utility +// +static struct xprison * rbjail_check_jail( VALUE self ); +static struct xprison * rbjail_get_jailptr( VALUE self ); +static int rbjail_do_attach( int jid ); + +// memory management +// +static VALUE rbjail_alloc( VALUE class, struct xprison *xp ); +static void rbjail_gc_free( struct xprison *ptr ); +static VALUE rbjail_s_alloc( VALUE class ); + +// class +// +static VALUE rbjail_jail( int argc, VALUE *argv, VALUE self ); +static VALUE rbjail_find( int argc, VALUE *argv, VALUE self ); +static VALUE rbjail_find_by_jid( VALUE self, VALUE jid ); +static VALUE rbjail_class_attach( VALUE self, VALUE arg ); +static VALUE rbjail_compare( VALUE self, VALUE other ); + +// instance +// +static VALUE rbjail_inspect( VALUE self ); +static VALUE rbjail_get_ip( VALUE self ); +static VALUE rbjail_get_jid( VALUE self ); +static VALUE rbjail_get_host( VALUE self ); +static VALUE rbjail_get_path( VALUE self ); +static VALUE rbjail_instance_attach( VALUE self ); +static VALUE rbjail_attach_block( int jid ); + +void Init_jail( void ); +