2017-03-14 23:32:11 +00:00
|
|
|
#!/usr/bin/env python3
|
2006-05-15 15:21:43 +00:00
|
|
|
# Copyright (C) 2004 Tresys Technology, LLC
|
|
|
|
# see file 'COPYING' for use and warranty information
|
|
|
|
#
|
|
|
|
# genhomedircon - this script is used to generate file context
|
|
|
|
# configuration entries for user home directories based on their
|
|
|
|
# default roles and is run when building the policy. Specifically, we
|
|
|
|
# replace HOME_ROOT, HOME_DIR, and ROLE macros in .fc files with
|
|
|
|
# generic and user-specific values.
|
|
|
|
#
|
|
|
|
# Based off original script by Dan Walsh, <dwalsh@redhat.com>
|
|
|
|
#
|
|
|
|
# ASSUMPTIONS:
|
|
|
|
#
|
|
|
|
# The file CONTEXTDIR/files/homedir_template exists. This file is used to
|
|
|
|
# set up the home directory context for each real user.
|
2016-12-06 12:28:10 +00:00
|
|
|
#
|
2006-05-15 15:21:43 +00:00
|
|
|
# If a user has more than one role in CONTEXTDIR/local.users, genhomedircon uses
|
|
|
|
# the first role in the list.
|
|
|
|
#
|
|
|
|
# If a user is not listed in CONTEXTDIR/local.users, he will default to user_u, role user
|
|
|
|
#
|
|
|
|
# "Real" users (as opposed to system users) are those whose UID is greater than
|
|
|
|
# or equal STARTING_UID (usually 500) and whose login is not a member of
|
|
|
|
# EXCLUDE_LOGINS. Users who are explicitly defined in CONTEXTDIR/local.users
|
|
|
|
# are always "real" (including root, in the default configuration).
|
|
|
|
#
|
2016-12-06 12:28:10 +00:00
|
|
|
#
|
2006-05-15 15:21:43 +00:00
|
|
|
# Old ASSUMPTIONS:
|
|
|
|
#
|
|
|
|
# If a user has more than one role in FILECONTEXTDIR/users, genhomedircon uses
|
|
|
|
# the first role in the list.
|
|
|
|
#
|
|
|
|
# If a user is not listed in FILECONTEXTDIR/users, genhomedircon assumes that
|
|
|
|
# the user's home dir will be found in one of the HOME_ROOTs.
|
|
|
|
#
|
|
|
|
# "Real" users (as opposed to system users) are those whose UID is greater than
|
|
|
|
# or equal STARTING_UID (usually 500) and whose login is not a member of
|
|
|
|
# EXCLUDE_LOGINS. Users who are explicitly defined in FILECONTEXTDIR/users
|
|
|
|
# are always "real" (including root, in the default configuration).
|
|
|
|
#
|
|
|
|
|
2021-01-31 20:50:27 +00:00
|
|
|
import sys, pwd, getopt, re, os
|
2020-05-13 20:12:34 +00:00
|
|
|
from subprocess import getstatusoutput
|
2006-05-15 15:21:43 +00:00
|
|
|
|
|
|
|
EXCLUDE_LOGINS=["/sbin/nologin", "/bin/false"]
|
|
|
|
|
2017-03-18 17:38:20 +00:00
|
|
|
|
2006-05-15 15:21:43 +00:00
|
|
|
def getStartingUID():
|
2017-03-14 23:32:11 +00:00
|
|
|
starting_uid = 99999
|
2020-05-13 20:12:34 +00:00
|
|
|
rc=getstatusoutput("grep -h '^UID_MIN' /etc/login.defs")
|
2006-05-15 15:21:43 +00:00
|
|
|
if rc[0] == 0:
|
|
|
|
uid_min = re.sub("^UID_MIN[^0-9]*", "", rc[1])
|
|
|
|
#stip any comment from the end of the line
|
|
|
|
uid_min = uid_min.split("#")[0]
|
|
|
|
uid_min = uid_min.strip()
|
|
|
|
if int(uid_min) < starting_uid:
|
|
|
|
starting_uid = int(uid_min)
|
2020-05-13 20:12:34 +00:00
|
|
|
rc=getstatusoutput("grep -h '^LU_UIDNUMBER' /etc/libuser.conf")
|
2006-05-15 15:21:43 +00:00
|
|
|
if rc[0] == 0:
|
|
|
|
lu_uidnumber = re.sub("^LU_UIDNUMBER[^0-9]*", "", rc[1])
|
|
|
|
#stip any comment from the end of the line
|
|
|
|
lu_uidnumber = re.sub("[ \t].*", "", lu_uidnumber)
|
|
|
|
lu_uidnumber = lu_uidnumber.split("#")[0]
|
|
|
|
lu_uidnumber = lu_uidnumber.strip()
|
|
|
|
if int(lu_uidnumber) < starting_uid:
|
|
|
|
starting_uid = int(lu_uidnumber)
|
2017-03-14 23:32:11 +00:00
|
|
|
if starting_uid == 99999:
|
2006-05-15 15:21:43 +00:00
|
|
|
starting_uid = 500
|
|
|
|
return starting_uid
|
|
|
|
|
|
|
|
def getDefaultHomeDir():
|
|
|
|
ret = []
|
2021-01-31 20:50:27 +00:00
|
|
|
if os.path.isfile('/etc/default/useradd'):
|
|
|
|
rc=getstatusoutput("grep -h '^HOME' /etc/default/useradd")
|
|
|
|
if rc[0] == 0:
|
|
|
|
homedir = rc[1].split("=")[1]
|
|
|
|
homedir = homedir.split("#")[0]
|
|
|
|
homedir = homedir.strip()
|
|
|
|
if not homedir in ret:
|
|
|
|
ret.append(homedir)
|
|
|
|
else:
|
|
|
|
#rc[0] == 1 means the file was there, we read it, but the grep didn't match
|
|
|
|
if rc[0] != 1:
|
|
|
|
sys.stderr.write("(%d): %s\n" % (rc[0], rc[1]))
|
|
|
|
sys.stderr.write("You do not have access to /etc/default/useradd HOME=\n")
|
|
|
|
sys.stderr.flush()
|
|
|
|
if os.path.isfile('/etc/libuser.conf'):
|
|
|
|
rc=getstatusoutput("grep -h '^LU_HOMEDIRECTORY' /etc/libuser.conf")
|
|
|
|
if rc[0] == 0:
|
|
|
|
homedir = rc[1].split("=")[1]
|
|
|
|
homedir = homedir.split("#")[0]
|
|
|
|
homedir = homedir.strip()
|
|
|
|
if not homedir in ret:
|
|
|
|
ret.append(homedir)
|
|
|
|
else:
|
|
|
|
#rc[0] == 1 means the file was there, we read it, but the grep didn't match
|
|
|
|
if rc[0] != 1:
|
|
|
|
sys.stderr.write("(%d): %s\n" % (rc[0], rc[1]))
|
|
|
|
sys.stderr.write("You do not have access to /etc/libuser.conf LU_HOMEDIRECTORY=\n")
|
|
|
|
sys.stderr.flush()
|
2006-05-15 15:21:43 +00:00
|
|
|
if ret == []:
|
|
|
|
ret.append("/home")
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def getSELinuxType(directory):
|
2020-05-13 20:12:34 +00:00
|
|
|
rc=getstatusoutput("grep ^SELINUXTYPE= %s/config" % directory)
|
2006-05-15 15:21:43 +00:00
|
|
|
if rc[0]==0:
|
|
|
|
return rc[1].split("=")[-1].strip()
|
|
|
|
return "targeted"
|
|
|
|
|
|
|
|
def usage(error = ""):
|
|
|
|
if error != "":
|
|
|
|
sys.stderr.write("%s\n" % error)
|
|
|
|
sys.stderr.write("Usage: %s [ -d selinuxdir ] [-n | --nopasswd] [-t selinuxtype ]\n" % sys.argv[0])
|
|
|
|
sys.stderr.flush()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
def warning(warning = ""):
|
|
|
|
sys.stderr.write("%s\n" % warning)
|
|
|
|
sys.stderr.flush()
|
2016-12-06 12:28:10 +00:00
|
|
|
|
2006-05-15 15:21:43 +00:00
|
|
|
def errorExit(error):
|
|
|
|
sys.stderr.write("%s exiting for: " % sys.argv[0])
|
|
|
|
sys.stderr.write("%s\n" % error)
|
|
|
|
sys.stderr.flush()
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
class selinuxConfig:
|
2017-03-14 23:32:11 +00:00
|
|
|
def __init__(self, selinuxdir="/etc/selinux", setype="targeted", usepwd=1):
|
|
|
|
self.setype=setype
|
2006-05-15 15:21:43 +00:00
|
|
|
self.selinuxdir=selinuxdir +"/"
|
|
|
|
self.contextdir="/contexts"
|
|
|
|
self.filecontextdir=self.contextdir+"/files"
|
|
|
|
self.usepwd=usepwd
|
|
|
|
|
|
|
|
def getFileContextDir(self):
|
2017-03-14 23:32:11 +00:00
|
|
|
return self.selinuxdir+self.setype+self.filecontextdir
|
2006-05-15 15:21:43 +00:00
|
|
|
|
|
|
|
def getFileContextFile(self):
|
|
|
|
return self.getFileContextDir()+"/file_contexts"
|
2016-12-06 12:28:10 +00:00
|
|
|
|
2006-05-15 15:21:43 +00:00
|
|
|
def getHomeDirTemplate(self):
|
|
|
|
return self.getFileContextDir()+"/homedir_template"
|
|
|
|
|
|
|
|
def getHomeRootContext(self, homedir):
|
2020-05-13 20:12:34 +00:00
|
|
|
rc=getstatusoutput("grep HOME_ROOT %s | sed -e \"s|^HOME_ROOT|%s|\"" % ( self.getHomeDirTemplate(), homedir))
|
2021-01-31 20:50:25 +00:00
|
|
|
if rc[0] != 0:
|
|
|
|
errorExit("sed error (" + str(rc[0]) + "): " + rc[1])
|
|
|
|
return rc[1]+"\n"
|
2006-05-15 15:21:43 +00:00
|
|
|
|
|
|
|
def getUsersFile(self):
|
2017-03-14 23:32:11 +00:00
|
|
|
return self.selinuxdir+self.setype+"/users/local.users"
|
2006-05-15 15:21:43 +00:00
|
|
|
|
|
|
|
def getSystemUsersFile(self):
|
2017-03-14 23:32:11 +00:00
|
|
|
return self.selinuxdir+self.setype+"/users/system.users"
|
2016-12-06 12:28:10 +00:00
|
|
|
|
2006-05-15 15:21:43 +00:00
|
|
|
def heading(self):
|
|
|
|
ret = "\n#\n#\n# User-specific file contexts, generated via %s\n" % sys.argv[0]
|
|
|
|
ret += "# edit %s to change file_context\n#\n#\n" % self.getUsersFile()
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def getUsers(self):
|
|
|
|
users=""
|
2020-05-13 20:12:34 +00:00
|
|
|
rc = getstatusoutput('grep "^user" %s' % self.getSystemUsersFile())
|
2006-05-15 15:21:43 +00:00
|
|
|
if rc[0] == 0:
|
|
|
|
users+=rc[1]+"\n"
|
2020-05-13 20:12:34 +00:00
|
|
|
rc = getstatusoutput("grep ^user %s" % self.getUsersFile())
|
2006-05-15 15:21:43 +00:00
|
|
|
if rc[0] == 0:
|
|
|
|
users+=rc[1]
|
|
|
|
udict = {}
|
|
|
|
if users != "":
|
|
|
|
ulist = users.split("\n")
|
|
|
|
for u in ulist:
|
|
|
|
user = u.split()
|
|
|
|
try:
|
|
|
|
if len(user)==0 or user[1] == "user_u" or user[1] == "system_u":
|
|
|
|
continue
|
|
|
|
# !!! chooses first role in the list to use in the file context !!!
|
|
|
|
role = user[3]
|
|
|
|
if role == "{":
|
|
|
|
role = user[4]
|
|
|
|
role = role.split("_r")[0]
|
2021-01-31 20:50:23 +00:00
|
|
|
pwdentry = pwd.getpwnam(user[1])
|
|
|
|
home = pwdentry[5]
|
2006-05-15 15:21:43 +00:00
|
|
|
if home == "/":
|
|
|
|
continue
|
|
|
|
prefs = {}
|
|
|
|
prefs["role"] = role
|
|
|
|
prefs["home"] = home
|
2021-01-31 20:50:23 +00:00
|
|
|
prefs["name"] = pwdentry[0]
|
|
|
|
prefs["uid"] = pwdentry[2]
|
2006-05-15 15:21:43 +00:00
|
|
|
udict[user[1]] = prefs
|
|
|
|
except KeyError:
|
|
|
|
sys.stderr.write("The user \"%s\" is not present in the passwd file, skipping...\n" % user[1])
|
|
|
|
return udict
|
|
|
|
|
2021-01-31 20:50:23 +00:00
|
|
|
def getHomeDirContext(self, seuser, home, role, username, userid):
|
|
|
|
ret = "\n\n#\n# Context for user %s\n#\n\n" % seuser
|
|
|
|
rc = getstatusoutput("grep -E '^HOME_DIR|%%{USERID}|%%{USERNAME}' %s | sed"
|
|
|
|
" -e 's|HOME_DIR|%s|'"
|
|
|
|
" -e 's|ROLE|%s|'"
|
|
|
|
" -e 's|system_u|%s|'"
|
|
|
|
" -e 's|%%{USERID}|%s|'"
|
|
|
|
" -e 's|%%{USERNAME}|%s|'"
|
|
|
|
% (self.getHomeDirTemplate(), home, role, seuser, userid, username))
|
|
|
|
if rc[0] != 0:
|
|
|
|
errorExit("sed error (" + str(rc[0]) + "): " + rc[1])
|
2006-05-15 15:21:43 +00:00
|
|
|
return ret + rc[1] + "\n"
|
|
|
|
|
|
|
|
def genHomeDirContext(self):
|
|
|
|
users = self.getUsers()
|
|
|
|
ret=""
|
|
|
|
# Fill in HOME and ROLE for users that are defined
|
2021-01-31 20:50:25 +00:00
|
|
|
for u in users:
|
2021-01-31 20:50:23 +00:00
|
|
|
ret += self.getHomeDirContext (u, users[u]["home"], users[u]["role"], users[u]["name"], users[u]["uid"])
|
2006-05-15 15:21:43 +00:00
|
|
|
return ret+"\n"
|
|
|
|
|
|
|
|
def checkExists(self, home):
|
2020-05-13 20:12:34 +00:00
|
|
|
if getstatusoutput("grep -E '^%s[^[:alnum:]_-]' %s" % (home, self.getFileContextFile()))[0] == 0:
|
2006-05-15 15:21:43 +00:00
|
|
|
return 0
|
|
|
|
#this works by grepping the file_contexts for
|
|
|
|
# 1. ^/ makes sure this is not a comment
|
|
|
|
# 2. prints only the regex in the first column first cut on \t then on space
|
2020-05-13 20:12:34 +00:00
|
|
|
rc=getstatusoutput("grep \"^/\" %s | cut -f 1 | cut -f 1 -d \" \" " % self.getFileContextFile() )
|
2006-05-15 15:21:43 +00:00
|
|
|
if rc[0] == 0:
|
|
|
|
prefix_regex = rc[1].split("\n")
|
|
|
|
else:
|
|
|
|
sys.stderr.write("%s\n" % rc[1])
|
|
|
|
sys.stderr.write("You do not have access to grep/cut/the file contexts\n")
|
|
|
|
sys.stderr.flush()
|
|
|
|
exists=1
|
|
|
|
for regex in prefix_regex:
|
|
|
|
#match a trailing (/*)? which is actually a bug in rpc_pipefs
|
2017-10-10 22:00:30 +00:00
|
|
|
regex = re.sub(r"\(/\*\)\?$", "", regex)
|
2006-05-15 15:21:43 +00:00
|
|
|
#match a trailing .+
|
2017-10-10 22:00:30 +00:00
|
|
|
regex = re.sub(r"\.+$", "", regex)
|
2006-05-15 15:21:43 +00:00
|
|
|
#match a trailing .*
|
2017-10-10 22:00:30 +00:00
|
|
|
regex = re.sub(r"\.\*$", "", regex)
|
2006-05-15 15:21:43 +00:00
|
|
|
#strip a (/.*)? which matches anything trailing to a /*$ which matches trailing /'s
|
2017-10-10 22:00:30 +00:00
|
|
|
regex = re.sub(r"\(\/\.\*\)\?", "", regex)
|
2006-05-15 15:21:43 +00:00
|
|
|
regex = regex + "/*$"
|
|
|
|
if re.search(regex, home, 0):
|
|
|
|
exists = 0
|
|
|
|
break
|
|
|
|
if exists == 1:
|
|
|
|
return 1
|
2021-01-31 20:50:25 +00:00
|
|
|
return 0
|
2006-05-15 15:21:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
def getHomeDirs(self):
|
|
|
|
homedirs = []
|
|
|
|
homedirs = homedirs + getDefaultHomeDir()
|
|
|
|
starting_uid=getStartingUID()
|
|
|
|
if self.usepwd==0:
|
|
|
|
return homedirs
|
|
|
|
ulist = pwd.getpwall()
|
|
|
|
for u in ulist:
|
|
|
|
if u[2] >= starting_uid and \
|
|
|
|
not u[6] in EXCLUDE_LOGINS and \
|
|
|
|
u[5] != "/" and \
|
2017-03-14 23:32:11 +00:00
|
|
|
u[5].count("/") > 1:
|
|
|
|
homedir = u[5][:u[5].rfind("/")]
|
2006-05-15 15:21:43 +00:00
|
|
|
if not homedir in homedirs:
|
|
|
|
if self.checkExists(homedir)==0:
|
|
|
|
warning("%s is already defined in %s,\n%s will not create a new context." % (homedir, self.getFileContextFile(), sys.argv[0]))
|
|
|
|
else:
|
|
|
|
homedirs.append(homedir)
|
|
|
|
|
|
|
|
homedirs.sort()
|
|
|
|
return homedirs
|
2016-12-06 12:28:10 +00:00
|
|
|
|
2006-05-15 15:21:43 +00:00
|
|
|
def genoutput(self):
|
|
|
|
ret= self.heading()
|
|
|
|
for h in self.getHomeDirs():
|
2021-01-31 20:50:23 +00:00
|
|
|
ret += self.getHomeDirContext ("user_u" , h+'/[^/]+', "user", "[^/]+", "[0-9]+")
|
2006-05-15 15:21:43 +00:00
|
|
|
ret += self.getHomeRootContext(h)
|
|
|
|
ret += self.genHomeDirContext()
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def write(self):
|
|
|
|
try:
|
|
|
|
fd = open(self.getFileContextDir()+"/file_contexts.homedirs", "w")
|
|
|
|
fd.write(self.genoutput())
|
|
|
|
fd.close()
|
2017-03-14 23:32:11 +00:00
|
|
|
except IOError as error:
|
2006-05-15 15:21:43 +00:00
|
|
|
sys.stderr.write("%s: %s\n" % ( sys.argv[0], error ))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# This script will generate home dir file context
|
|
|
|
# based off the homedir_template file, entries in the password file, and
|
|
|
|
#
|
|
|
|
try:
|
|
|
|
usepwd=1
|
|
|
|
directory="/etc/selinux"
|
2017-03-14 23:32:11 +00:00
|
|
|
setype=None
|
2006-05-15 15:21:43 +00:00
|
|
|
gopts, cmds = getopt.getopt(sys.argv[1:], 'nd:t:', ['help',
|
|
|
|
'type=',
|
|
|
|
'nopasswd',
|
|
|
|
'dir='])
|
|
|
|
for o,a in gopts:
|
2021-01-31 20:50:25 +00:00
|
|
|
if o in ('--type', '-t'):
|
2017-03-14 23:32:11 +00:00
|
|
|
setype=a
|
2021-01-31 20:50:25 +00:00
|
|
|
if o in ('--nopasswd', '-n'):
|
2006-05-15 15:21:43 +00:00
|
|
|
usepwd=0
|
2021-01-31 20:50:25 +00:00
|
|
|
if o in ('--dir', '-d'):
|
2006-05-15 15:21:43 +00:00
|
|
|
directory=a
|
|
|
|
if o == '--help':
|
|
|
|
usage()
|
|
|
|
|
|
|
|
|
2017-03-14 23:32:11 +00:00
|
|
|
if setype is None:
|
|
|
|
setype=getSELinuxType(directory)
|
2006-05-15 15:21:43 +00:00
|
|
|
|
|
|
|
if len(cmds) != 0:
|
|
|
|
usage()
|
2017-03-14 23:32:11 +00:00
|
|
|
selconf=selinuxConfig(directory, setype, usepwd)
|
2006-05-15 15:21:43 +00:00
|
|
|
selconf.write()
|
|
|
|
|
2019-03-15 13:25:00 +00:00
|
|
|
except Exception as error:
|
|
|
|
errorExit(error)
|