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