ext/bsdjail.c
changeset 7 4460fc10c6a3
parent 6 2d52adc4adcc
equal deleted inserted replaced
6:2d52adc4adcc 7:4460fc10c6a3
     1 /*
       
     2  *  bsdjail.c - JParallel extension for FreeBSD jail(2) functions
       
     3  *  $Id$
       
     4  *  
       
     5  *  Authors:
       
     6  *    * Michael Granger <ged@FaerieMUD.org>
       
     7  *    * Mahlon E. Smith <mahlon@martini.nu>
       
     8  *  
       
     9  *  Copyright (c) 2008, Michael Granger and Mahlon Smith
       
    10  *  All rights reserved.
       
    11  *  
       
    12  *  Redistribution and use in source and binary forms, with or without
       
    13  *  modification, are permitted provided that the following conditions are met:
       
    14  *  
       
    15  *    * Redistributions of source code must retain the above copyright notice,
       
    16  *      this list of conditions and the following disclaimer.
       
    17  *  
       
    18  *    * Redistributions in binary form must reproduce the above copyright notice,
       
    19  *      this list of conditions and the following disclaimer in the documentation
       
    20  *      and/or other materials provided with the distribution.
       
    21  *  
       
    22  *    * Neither the name of the author/s, nor the names of the project's
       
    23  *      contributors may be used to endorse or promote products derived from this
       
    24  *      software without specific prior written permission.
       
    25  *  
       
    26  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
       
    27  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
       
    28  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
       
    29  *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
       
    30  *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
       
    31  *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
       
    32  *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
       
    33  *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
       
    34  *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
       
    35  *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
       
    36  *  
       
    37  */
       
    38 
       
    39 #include "bsdjail.h"
       
    40 
       
    41 VALUE rbjail_mBSD;
       
    42 VALUE rbjail_cBSDJail;
       
    43 
       
    44 
       
    45 /*
       
    46  *  Debug logging function
       
    47  */
       
    48 void
       
    49 #ifdef HAVE_STDARG_PROTOTYPES
       
    50 rbjail_debug(const char *fmt, ...)
       
    51 #else
       
    52 rbjail_debug( const char *fmt, va_dcl )
       
    53 #endif
       
    54 {
       
    55 	char buf[BUFSIZ], buf2[BUFSIZ];
       
    56 	va_list	args;
       
    57 
       
    58 	if ( !RTEST(ruby_debug) ) return;
       
    59 
       
    60 	snprintf( buf, BUFSIZ, "Debug>>> %s", fmt );
       
    61 
       
    62 	va_init_list( args, fmt );
       
    63 	vsnprintf( buf2, BUFSIZ, buf, args );
       
    64 	fputs( buf2, stderr );
       
    65 	fputs( "\n", stderr );
       
    66 	fflush( stderr );
       
    67 	va_end( args );
       
    68 }
       
    69 
       
    70 
       
    71 /*
       
    72 struct jail {
       
    73 	u_int32_t       version;
       
    74 	char            *path;
       
    75 	char            *hostname;
       
    76 	u_int32_t       ip_number;
       
    77 };
       
    78 */
       
    79 
       
    80 /*
       
    81  * Allocation function
       
    82  */
       
    83 static jail *
       
    84 rbjail_jail_alloc() {
       
    85 	jail *ptr = ALLOC( jail );
       
    86 	
       
    87 	ptr->version	= 0;
       
    88 	ptr->path		= NULL;
       
    89 	ptr->hostname	= NULL;
       
    90 	ptr->ip_number	= 0;
       
    91 	
       
    92 	debugMsg(( "Initialized a jail pointer <%p>", ptr ));
       
    93 	return ptr;
       
    94 }
       
    95 
       
    96 
       
    97 /*
       
    98  * GC Free function
       
    99  */
       
   100 static void
       
   101 rbjail_gc_free( jail *ptr ) {
       
   102 	if ( ptr ) {
       
   103 		if ( ptr->path ) xfree( ptr->path );
       
   104 		if ( ptr->hostname ) xfree( ptr->hostname );
       
   105 		
       
   106 		ptr->path		= NULL;
       
   107 		ptr->hostname	= NULL;
       
   108 
       
   109 		xfree( ptr );
       
   110 	}
       
   111 	
       
   112 	else {
       
   113 		debugMsg(( "Not freeing an uninitialized jail" ));
       
   114 	}
       
   115 }
       
   116 
       
   117 
       
   118 /*
       
   119  * Object validity checker. Returns the data pointer.
       
   120  */
       
   121 static jail *
       
   122 rbjail_check_jail( VALUE self ) {
       
   123 	debugMsg(( "Checking a BSD::Jail object (%d).", self ));
       
   124 	Check_Type( self, T_DATA );
       
   125 
       
   126     if ( !rb_obj_is_kind_of(self, rbjail_cBSDJail) ) {
       
   127 		rb_raise( rb_eTypeError, "wrong argument type %s (expected BSD::Jail)",
       
   128 				  rb_class2name(CLASS_OF( self )) );
       
   129     }
       
   130 	
       
   131 	return DATA_PTR( self );
       
   132 }
       
   133 
       
   134 
       
   135 /*
       
   136  * Fetch the data pointer and check it for sanity.
       
   137  */
       
   138 static jail *
       
   139 rbjail_get_jail( VALUE self ) {
       
   140 	jail *ptr = check_sentence( self );
       
   141 
       
   142 	debugMsg(( "Fetching a Jail (%p).", ptr ));
       
   143 	if ( !ptr )
       
   144 		rb_raise( rb_eRuntimeError, "uninitialized Sentence" );
       
   145 
       
   146 	return ptr;
       
   147 }
       
   148 
       
   149 
       
   150 /* --------------------------------------------------------------
       
   151  * Jail utility functions
       
   152  * -------------------------------------------------------------- */
       
   153 
       
   154 /*
       
   155  * Try to jail_attach() to the specified +jid+, raising an exception if it fails.
       
   156  */
       
   157 static void
       
   158 rbjail_do_jail_attach( int jid )
       
   159 {
       
   160 	if ( jail_attach(jid) == -1 )
       
   161 		rb_sys_fail( "jail_attach" );
       
   162 }
       
   163 
       
   164 
       
   165 /* 
       
   166  * Fork + Block function for rbjail_attach(). Mostly stolen from Ruby's process.c.
       
   167  */
       
   168 static VALUE
       
   169 rbjail_attach_block( int jid )
       
   170 {
       
   171     int pid;
       
   172 
       
   173     rb_secure(2);
       
   174 
       
   175     fflush( stdout );
       
   176     fflush( stderr );
       
   177 
       
   178 	switch ( pid = fork() ) {
       
   179 		case 0:
       
   180 			rb_thread_atfork();
       
   181 			if ( rb_block_given_p() ) {
       
   182 				int status;
       
   183 
       
   184 				rbjail_do_jail_attach( jid );
       
   185 				rb_protect( rb_yield, Qundef, &status );
       
   186 				ruby_stop( status );
       
   187 			}
       
   188 			return Qnil;
       
   189 
       
   190 		case -1:
       
   191 			rb_sys_fail( "fork(2)" );
       
   192 			return Qnil;
       
   193 
       
   194 		default:
       
   195 			return INT2FIX( pid );
       
   196 	}
       
   197 }
       
   198 
       
   199 
       
   200 /* --------------------------------------------------------------
       
   201  * Class methods
       
   202  * -------------------------------------------------------------- */
       
   203 
       
   204 /*
       
   205  *  call-seq:
       
   206  *     BSD::Jail.allocate   -> bsdjail
       
   207  *
       
   208  *  Allocate a new BSD::Jail object.
       
   209  *
       
   210  */
       
   211 static VALUE
       
   212 rbjail_jail_s_allocate( VALUE klass ) {
       
   213 	return Data_Wrap_Struct( klass, 0, rbjail_gc_free, 0 );
       
   214 }
       
   215 
       
   216 
       
   217 /*
       
   218  *  call-seq:
       
   219  *     BSD::Jail.list   -> array
       
   220  *
       
   221  *  Return an Array of all the running jails on the host.
       
   222  *
       
   223  */
       
   224 static VALUE
       
   225 rbjail_jail_s_list( VALUE klass ) {
       
   226 	VALUE rval = rb_ary_new();
       
   227 	struct xprison *xp;
       
   228 	struct in_addr in;
       
   229 	size_t i, len;
       
   230 
       
   231 	if ( sysctlbyname("jail.list", NULL, &len, NULL, 0) == -1 )
       
   232 		rb_sys_fail( "sysctlbyname(): jail.list" );
       
   233 
       
   234 	xp = ALLOCA_N( xprison, 1 );
       
   235 
       
   236 	if ( sysctlbyname("jail.list", xp, &len, NULL, 0) == -1 ) {
       
   237 		rb_sys_fail( "sysctlbyname(): jail.list" );
       
   238 	}
       
   239 
       
   240 	if ( len < sizeof(*xp) || len % sizeof(*xp) ||
       
   241 		xp->pr_version != KINFO_PRISON_VERSION )
       
   242 		rb_fatal( "Kernel and userland out of sync" );
       
   243 
       
   244 	len /= sizeof( *xp );
       
   245 	for ( i = 0; i < len; i++ ) {
       
   246 		VALUE jail, args[3];
       
   247 		
       
   248 		/* Hostname */
       
   249 		args[0] = rb_str_new2( xp->pr_host );
       
   250 
       
   251 		/* IP */
       
   252 		in.s_addr = ntohl( xp->pr_ip );
       
   253 		args[1] = rb_str_new2( inet_ntoa(in) );
       
   254 		
       
   255 		/* Path */
       
   256 		args[2] = rb_str_new2( xp->pr_path );
       
   257 		
       
   258 		jail = rb_class_new_instance( 3, args, klass );
       
   259 		rb_ary_push( rval, jail );
       
   260 
       
   261 		xp++;
       
   262 	}
       
   263 
       
   264 	return rval;
       
   265 }
       
   266 
       
   267 
       
   268 
       
   269 
       
   270 /* --------------------------------------------------------------
       
   271  * Instance methods
       
   272  * -------------------------------------------------------------- */
       
   273 
       
   274 /*
       
   275  *  call-seq:
       
   276  *     BSD::Jail.new( hostname, ip, path )           -> new_jail
       
   277  *
       
   278  *  Create a new jail for the given +hostname+, +ip+, and +path+.
       
   279  */
       
   280 static VALUE
       
   281 rbjail_jail_initialize( VALUE self, VALUE hostname, VALUE ip, VALUE path ) {
       
   282 	if ( !rbjail_check_jail(self) ) {
       
   283 		struct jail *ptr = rbjail_jail_alloc();
       
   284 		char *pathstr = NULL, *hostnamestr = NULL;
       
   285 
       
   286 		Check_Type( hostname, T_STRING );
       
   287 		Check_Type( ip, T_STRING );
       
   288 
       
   289 		pathstr = ALLOC_N( char, RSTRING(path)->len );
       
   290 		strncpy( pathstr, RSTRING(path)->ptr, RSTRING(path)->len );
       
   291 
       
   292 		hostnamestr = ALLOC_N( char, RSTRING(hostname)->len );
       
   293 		strncpy( hostnamestr, RSTRING(hostname)->ptr, RSTRING(hostname)->len );
       
   294 		
       
   295 		/*
       
   296 		struct jail {
       
   297 			u_int32_t       version;
       
   298 			char            *path;
       
   299 			char            *hostname;
       
   300 			u_int32_t       ip_number;
       
   301 		};
       
   302 		*/
       
   303 		ptr->version = 0;   /* Per the manpage's recommendation */
       
   304 		ptr->ip_number = inet_addr( StringValuePtr(ip) );
       
   305 		ptr->path = path;
       
   306 		ptr->hostname = hostname;
       
   307 	}
       
   308 }
       
   309 
       
   310 
       
   311 /*
       
   312  *  call-seq:
       
   313  *     jail.attach             -> true or false
       
   314  *     jail.attach { block }   -> pid
       
   315  *
       
   316  *  Attach to the given jail. In the non-block form, attach the current process to the
       
   317  *  jail and return +true+ if it succeeds. This is a one-way operation, and requires root
       
   318  *  privileges.
       
   319  *  
       
   320  *  In the block form, the process will be forked, and the block will be attached to the jail and 
       
   321  *  run by the child. The parent process will receive the process ID of the child.
       
   322  */
       
   323 static VALUE
       
   324 rbjail_attach( int argc, VALUE *argv, VALUE self ) {
       
   325 	VALUE jidnum, rval;
       
   326 	int jid;
       
   327 	
       
   328 	rb_scan_args( argc, argv, "1", &jidnum );
       
   329 	jid = NUM2INT( jidnum );
       
   330 
       
   331 	if ( rb_block_given_p() ) {
       
   332 		rval = rbjail_attach_block( jid );
       
   333 	}
       
   334 	
       
   335 	else {
       
   336 		rbjail_do_jail_attach( jid );
       
   337 		rval = Qtrue;
       
   338 	}
       
   339 	
       
   340 	return rval;
       
   341 }
       
   342 
       
   343 
       
   344 /*
       
   345  * I can't remember how the hell you document a class, but that will go here.
       
   346  */
       
   347 void
       
   348 Init_bsdjail( void ) {
       
   349 	rbjail_mBSD = rb_define_module( "BSD" );
       
   350 	rbjail_cBSDJail = rb_define_class_under( rbjail_mBSD, "Jail" );
       
   351 
       
   352 	/* Class methods */
       
   353 	rb_define_alloc_function( rbjail_cBSDJail, rbjail_jail_s_allocate );
       
   354 	rb_define_singleton_method( rbjail_cBSDJail, "jail", rbjail_jail_s_jail, 0 );
       
   355 	rb_define_singleton_method( rbjail_cBSDJail, "list", rbjail_jail_s_list, 0 );
       
   356 	
       
   357 	/* Instance methods */
       
   358 	rb_define_method( rbjail_cBSDJail, "initialize", rbjail_jail_initialize, 3 );
       
   359 	rb_define_method( rbjail_cBSDJail, "attach", rbjail_jail_attach, -1 );
       
   360 	rb_define_method( rbjail_cBSDJail, "hostname", rbjail_jail_hostname, 0 );
       
   361 	rb_define_method( rbjail_cBSDJail, "path", rbjail_jail_path, 0 );
       
   362 	rb_define_method( rbjail_cBSDJail, "ip", rbjail_jail_ip, 0 );
       
   363 	rb_define_method( rbjail_cBSDJail, "jid", rbjail_jail_jid, 0 );
       
   364 	
       
   365 }
       
   366