1#!/usr/bin/python 2# 3#Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2014 Olivier Sessink 4#All rights reserved. 5# 6#Redistribution and use in source and binary forms, with or without 7#modification, are permitted provided that the following conditions 8#are met: 9# * Redistributions of source code must retain the above copyright 10# notice, this list of conditions and the following disclaimer. 11# * Redistributions in binary form must reproduce the above 12# copyright notice, this list of conditions and the following 13# disclaimer in the documentation and/or other materials provided 14# with the distribution. 15# * The names of its contributors may not be used to endorse or 16# promote products derived from this software without specific 17# prior written permission. 18# 19#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22#FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23#COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 24#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28#LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 29#ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30#POSSIBILITY OF SUCH DAMAGE. 31# 32 33from __future__ import print_function 34 35import sys 36if sys.version_info > (3, 0): #Python 3 37 from configparser import ConfigParser 38else: #Python 2 39 from ConfigParser import ConfigParser 40import os 41import os.path 42import string 43import getopt 44import shutil 45import stat 46 47dir_mode = 493 #octal 755, "rwxr-xr-x" 48 49INIPREFIX='/etc/jailkit' 50LIBDIR='/usr/share/jailkit' 51sys.path.append(LIBDIR) 52import jk_lib 53 54class jk_init: 55 56 def __init__(self): 57 self.didfiles = [] 58 self.didsections = [] 59 self.diddevices = [] 60 self.didusers = [] 61 self.didgroups = [] 62 63 def logsocket(self,config,chroot): 64 if (not os.path.exists(chroot+'/dev')): 65 os.mkdir(chroot+'/dev') 66 fd = open(INIPREFIX+'/jk_socketd.ini', 'r') 67 line = fd.readline() 68 if sys.version_info > (3, 0) and isinstance(line, bytes): #Python 3 69 line = line.decode('utf-8', 'replace') 70 while (line): 71 if (line[:-1] == '['+chroot+'/dev/log]'): 72 if (config['verbose']): 73 print('we already have '+chroot+'/dev/log in '+INIPREFIX+'/jk_socketd.ini') 74 return 75 line = fd.readline() 76 if sys.version_info > (3, 0) and isinstance(line, bytes): #Python 3 77 line = line.decode('utf-8', 'replace') 78 fd = open(INIPREFIX+'/jk_socketd.ini', 'a') 79 fd.write('\n['+chroot+'/dev/log]\nbase=512\npeak=2048\ninterval=10\n') 80 fd.close() 81 82 def proc_mount(self,config, chroot): 83 if (sys.platform[:5] == 'linux'): 84 if (config['verbose']): 85 print('appending proc mount '+chroot+'proc to /etc/fstab') 86 fd = open('/etc/fstab', 'a') 87 fd.write("proc "+chroot+"proc proc defaults 0 0\n") 88 fd.close() 89 if (config['verbose']): 90 print('executing mount '+chroot+'proc') 91 os.spawnlp(os.P_WAIT, 'mount','mount', chroot+'proc') 92 else: 93 print('Not processing proc mount; proc mounts are Linux specific') 94 95 def add_jk_socketd_entry(self,config, chroot): 96 print('not yet implemented') 97 98 def handle_cfg_section(self,config,chroot,cfg,section): 99 if(chroot[-1] == '/'): 100 chroot = chroot[:-1] 101 # first create the chroot jail itself if it does not yet exist 102 if (not os.path.exists(chroot)): 103 print('Creating jail '+chroot) 104 os.makedirs(chroot, mode=dir_mode) 105 # if the parent is setuid or setgid that is not covered by the umask set above, so we remove that 106 os.chmod(chroot, dir_mode) 107 sections = jk_lib.config_get_option_as_list(cfg,section,'includesections') 108 for tmp in sections: 109 if (tmp not in self.didsections): 110 self.handle_cfg_section(config,chroot,cfg,tmp) 111 self.didsections.append(tmp) 112 #libraries, executables, regularfiles and directories are now all handled as 'paths' 113 paths = jk_lib.config_get_option_as_list(cfg,section,'paths') 114 paths = paths + jk_lib.config_get_option_as_list(cfg,section,'libraries') 115 paths = paths + jk_lib.config_get_option_as_list(cfg,section,'executables') 116 paths = paths + jk_lib.config_get_option_as_list(cfg,section,'regularfiles') 117 paths = paths + jk_lib.config_get_option_as_list(cfg,section,'directories') 118 paths2 = jk_lib.find_files_in_path(paths) 119 self.didfiles = jk_lib.copy_binaries_and_libs(chroot, paths2, config['force'], config['verbose'], 1, try_hardlink=config['hardlink'],try_glob_matching=1,handledfiles=self.didfiles) 120 121 paths_w_owner = jk_lib.config_get_option_as_list(cfg,section,'paths_w_owner') 122 if (len(paths_w_owner)>0): 123 self.didfiles = jk_lib.copy_binaries_and_libs(chroot, paths_w_owner, config['force'], config['verbose'], check_libs=1, try_hardlink=config['hardlink'], retain_owner=1,try_glob_matching=1, handledfiles=self.didfiles) 124 125 paths_w_setuid = jk_lib.config_get_option_as_list(cfg,section,'paths_w_setuid') 126 if (len(paths_w_setuid)>0): 127 self.didfiles = jk_lib.copy_binaries_and_libs(chroot, paths_w_setuid, config['force'], config['verbose'], check_libs=1, try_hardlink=config['hardlink'], allow_suid=1, retain_owner=1, try_glob_matching=3, handledfiles=self.didfiles) 128 129 emptydirs = jk_lib.config_get_option_as_list(cfg,section,'emptydirs') 130 for edir in emptydirs: 131 # print('DEBUG emptydir='+edir) 132 jk_lib.create_parent_path(chroot,edir, config['verbose'], copy_permissions=0, allow_suid=0, copy_ownership=0) 133 users = [] 134 groups = [] 135 tmplist = jk_lib.config_get_option_as_list(cfg,section,'users') 136 for tmp in tmplist: 137 if (tmp not in self.didusers): 138 users.append(tmp) 139 tmplist = jk_lib.config_get_option_as_list(cfg,section,'groups') 140 for tmp in tmplist: 141 if (tmp not in self.didusers): 142 groups.append(tmp) 143 jk_lib.init_passwd_and_group(chroot,users,groups,config['verbose']) 144 self.didusers = self.didusers + users 145 self.didgroups = self.didusers + groups 146 if (cfg.has_option(section,'need_proc')): 147 do_proc = cfg.get(section,'need_proc') 148 if (do_proc): 149 self.proc_mount(config,chroot) 150 if (cfg.has_option(section,'need_logsocket')): 151 do_logsocket = cfg.get(section,'need_logsocket') 152 if (do_logsocket): 153 self.logsocket(config,chroot) 154 devices = jk_lib.config_get_option_as_list(cfg,section,'devices') 155 for tmp in devices: 156 if (tmp not in self.diddevices): 157 jk_lib.create_parent_path(chroot,os.path.dirname(tmp), config['verbose'], copy_permissions=0, allow_suid=0, copy_ownership=0) 158 jk_lib.copy_device(chroot,tmp,config['verbose']) 159 self.diddevices.append(tmp) 160 161 162def activateConfig(config, jail, args): 163 cfg = ConfigParser() 164 cfg.read([config['file']]) 165 start = 0 166 if (jail == None): 167 jail = args[0] 168 start = 1 169 170 ji = jk_init() 171 for section in args[start:]: 172 if (cfg.has_section(section)): 173 ji.handle_cfg_section(config,jail,cfg,section) 174 else: 175 print('WARNING: section '+section+' does not exist in '+config['file']) 176 jk_lib.gen_library_cache(jail) 177 178def usage(): 179 print('') 180 print("Usage: "+sys.argv[0]+" [OPTIONS]") 181 print("Usage: "+sys.argv[0]+" [OPTIONS] -j jaildir sections...") 182 print('') 183 print("-h --help : this help screen") 184 print("-c, --configfile=FILE : specify configfile location") 185 print('-l, --list : list all available sections in the configfile') 186 print('-j, --jail= : specify the jail to use.') 187 print(' For backwards compatibility, if no jail is specified, the first') 188 print(' argument after the options will be used as jail') 189 print("-v, --verbose : show what is being done") 190 print("-f, --force : force overwriting of existing files") 191 print("-k, --hardlink : use hardlinks if possible") 192 print('') 193 194def listsections(file): 195 cfg = ConfigParser() 196 cfg.read(file) 197 sections = cfg.sections() 198 sections.sort() 199 print('\n** Available sections in '+file+' **\n') 200 for sec in sections: 201 if cfg.has_option(sec, 'comment'): 202 print(sec+' - '+cfg.get(sec, 'comment')) 203 else: 204 print(sec) 205 print('') 206 207def testargs(config,jail,args): 208 if ((len(args)<2 and jail == None) or (jail != None and len(args)<1)): 209 jk_lib.clean_exit(2,'need at least a jail directory and a configfile-section',usage) 210 if (jail == None): 211 jail = args[0] 212 if (jail[0] != '/'): 213 jail = os.path.abspath(jail) 214 else: 215 jail = os.path.normpath(jail) 216 if (jk_lib.chroot_is_safe(jail) != 1): 217 jk_lib.clean_exit(3,'jail directory '+args[0]+' is not safe',usage) 218 if (not os.path.isfile(config['file'])): 219 jk_lib.clean_exit(3,'configfile '+config['file']+' does not exist',usage) 220 return jail 221 222def main(): 223 if (os.getuid()!=0): 224 print('Cannot create chroot jail without root privileges. Abort.') 225 sys.exit(5) 226 try: 227 opts, args = getopt.getopt(sys.argv[1:], "vhflc:kj:", ["help", "configfile=", "verbose", "force", 'list', 'hardlink', 'jail']) 228 except getopt.GetoptError: 229 usage() 230 sys.exit(1) 231 config = {} 232 config['file'] = INIPREFIX+'/jk_init.ini' 233 config['verbose'] = 0 234 config['force'] = 0 235 config['hardlink'] = 0 236 jail = None 237 list = 0 238 for o, a in opts: 239 if o in ("-h", "--help"): 240 usage() 241 sys.exit() 242 if o in ("-c", "--configfile"): 243 config['file'] = a 244 if o in ("-l", "--list"): 245 list = 1 246 if o in ("-v", "--verbose"): 247 config['verbose'] = 1 248 if o in ("-f", "--force"): 249 config['force'] = 1 250 if o in ("-k", "--hardlink"): 251 config['hardlink'] = 1 252 if o in ("-j", "--jail="): 253 jail = a 254 if (list ==1): 255 listsections(config['file']) 256 sys.exit() 257 jail = testargs(config,jail,args) 258 activateConfig(config, jail, args) 259 260if __name__ == "__main__": 261 main() 262