Edit File by line
/home/barbar84/public_h.../wp-conte.../plugins/sujqvwi/AnonR/anonr.TX.../proc/self/root/lib/fixperms
File: fixperms_base.py
"""Common fixperms classes"""
[0] Fix | Delete
import os
[1] Fix | Delete
from shlex import quote
[2] Fix | Delete
from pathlib import Path
[3] Fix | Delete
from typing import Union
[4] Fix | Delete
from stat import S_IMODE, S_ISREG, S_ISDIR, S_ISLNK
[5] Fix | Delete
import re
[6] Fix | Delete
from fixperms_cli import Args
[7] Fix | Delete
from fixperms_ids import IDCache
[8] Fix | Delete
[9] Fix | Delete
[10] Fix | Delete
class PermMap:
[11] Fix | Delete
"""Base class for fixperms"""
[12] Fix | Delete
[13] Fix | Delete
def __init__(
[14] Fix | Delete
self,
[15] Fix | Delete
ids: IDCache,
[16] Fix | Delete
args: Args,
[17] Fix | Delete
user: str,
[18] Fix | Delete
all_docroots: list[str],
[19] Fix | Delete
docroot_chmod: int,
[20] Fix | Delete
docroot_chown: tuple[str, str],
[21] Fix | Delete
):
[22] Fix | Delete
self.args = args
[23] Fix | Delete
self.skip = self.args.skip.copy()
[24] Fix | Delete
self.all_docroots = all_docroots
[25] Fix | Delete
self.log = args.logger
[26] Fix | Delete
self.hard_links = HardLinkTracker(self)
[27] Fix | Delete
self.ids = ids
[28] Fix | Delete
self.user = user
[29] Fix | Delete
pwuser = self.ids.getpwnam(user)
[30] Fix | Delete
self.uid = pwuser.pw_uid
[31] Fix | Delete
self.gid = pwuser.pw_gid
[32] Fix | Delete
doc_uid = ids.getpwnam(docroot_chown[0]).pw_uid
[33] Fix | Delete
doc_gid = ids.getgrnam(docroot_chown[1]).gr_gid
[34] Fix | Delete
self.docroot_perms = Rule('', (None, docroot_chmod), (doc_uid, doc_gid))
[35] Fix | Delete
self.homedir = os.path.realpath(pwuser.pw_dir)
[36] Fix | Delete
if not re.match(r'\/home\d*\/', self.homedir):
[37] Fix | Delete
raise ValueError(f"{user}: unexpected homedir: {self.homedir!r}")
[38] Fix | Delete
self.home_re = re.escape(pwuser.pw_dir)
[39] Fix | Delete
self.perm_map: list['Rule'] = []
[40] Fix | Delete
[41] Fix | Delete
def add_rule(
[42] Fix | Delete
self,
[43] Fix | Delete
regex: str,
[44] Fix | Delete
modes: tuple[Union[int, None], Union[int, None]],
[45] Fix | Delete
chown: tuple[int, int],
[46] Fix | Delete
) -> None:
[47] Fix | Delete
"""Add a fixperms path rule. ^HOMEDIR is automatically added"""
[48] Fix | Delete
# no actual ^ becasue we use .match, not .search
[49] Fix | Delete
self.perm_map.append(Rule(f"{self.home_re}{regex}", modes, chown))
[50] Fix | Delete
[51] Fix | Delete
def lchown(self, path: str, stat: os.stat_result, uid: int, gid: int):
[52] Fix | Delete
"""Runs os.lchown"""
[53] Fix | Delete
if uid == gid == -1:
[54] Fix | Delete
return
[55] Fix | Delete
tgt_uid = stat.st_uid if uid == -1 else uid
[56] Fix | Delete
tgt_gid = stat.st_gid if gid == -1 else gid
[57] Fix | Delete
if (stat.st_uid, stat.st_gid) == (tgt_uid, tgt_gid):
[58] Fix | Delete
return
[59] Fix | Delete
if not self.args.noop:
[60] Fix | Delete
try:
[61] Fix | Delete
os.lchown(path, uid, gid)
[62] Fix | Delete
except OSError as exc:
[63] Fix | Delete
self.log.error(exc)
[64] Fix | Delete
return
[65] Fix | Delete
old_user = self.ids.uid_label(stat.st_uid)
[66] Fix | Delete
old_group = self.ids.gid_label(stat.st_gid)
[67] Fix | Delete
new_user = self.ids.uid_label(tgt_uid)
[68] Fix | Delete
new_group = self.ids.gid_label(tgt_gid)
[69] Fix | Delete
self.log.debug(
[70] Fix | Delete
'Changed ownership of %s from %s:%s to %s:%s',
[71] Fix | Delete
quote(path),
[72] Fix | Delete
old_user,
[73] Fix | Delete
old_group,
[74] Fix | Delete
new_user,
[75] Fix | Delete
new_group,
[76] Fix | Delete
)
[77] Fix | Delete
[78] Fix | Delete
def lchmod(
[79] Fix | Delete
self,
[80] Fix | Delete
path: str,
[81] Fix | Delete
stat: os.stat_result,
[82] Fix | Delete
mode: Union[int, None],
[83] Fix | Delete
):
[84] Fix | Delete
"""Runs os.chmod if the path is not a symlink"""
[85] Fix | Delete
if mode is None:
[86] Fix | Delete
return
[87] Fix | Delete
orig = S_IMODE(stat.st_mode)
[88] Fix | Delete
if orig == mode:
[89] Fix | Delete
return
[90] Fix | Delete
if S_ISLNK(stat.st_mode):
[91] Fix | Delete
return # Linux does not support follow_symlinks=False
[92] Fix | Delete
if not self.args.noop:
[93] Fix | Delete
try:
[94] Fix | Delete
os.chmod(path, mode)
[95] Fix | Delete
except OSError as exc:
[96] Fix | Delete
self.log.error(exc)
[97] Fix | Delete
return
[98] Fix | Delete
self.log.debug(
[99] Fix | Delete
'Changed mode of %s from %s to %s',
[100] Fix | Delete
quote(path),
[101] Fix | Delete
oct(orig)[2:],
[102] Fix | Delete
oct(mode)[2:],
[103] Fix | Delete
)
[104] Fix | Delete
[105] Fix | Delete
def walk(self, path: str, ignore_skips: bool = False):
[106] Fix | Delete
"""os.walk/os.lstat to yield a path and all of its contents"""
[107] Fix | Delete
for entry in self._walk(path, ignore_skips):
[108] Fix | Delete
try:
[109] Fix | Delete
stat = os.lstat(entry)
[110] Fix | Delete
except OSError as exc:
[111] Fix | Delete
self.log.error(exc)
[112] Fix | Delete
continue
[113] Fix | Delete
yield stat, entry
[114] Fix | Delete
[115] Fix | Delete
def _walk(self, top_dir: str, ignore_skips: bool = False):
[116] Fix | Delete
if not ignore_skips and self.should_skip(top_dir):
[117] Fix | Delete
return
[118] Fix | Delete
yield top_dir
[119] Fix | Delete
if not os.path.isdir(top_dir):
[120] Fix | Delete
return
[121] Fix | Delete
for dirpath, dirnames, filenames in os.walk(top_dir):
[122] Fix | Delete
for filename in filenames:
[123] Fix | Delete
path = os.path.join(dirpath, filename)
[124] Fix | Delete
if ignore_skips or not self.should_skip(path):
[125] Fix | Delete
yield path
[126] Fix | Delete
skip_dirs = []
[127] Fix | Delete
for dirname in dirnames:
[128] Fix | Delete
path = os.path.join(dirpath, dirname)
[129] Fix | Delete
if not ignore_skips and self.should_skip(path):
[130] Fix | Delete
skip_dirs.append(path)
[131] Fix | Delete
else:
[132] Fix | Delete
yield path
[133] Fix | Delete
if skip_dirs:
[134] Fix | Delete
# editing dirnames[:] in-place causes os.walk to not traverse it
[135] Fix | Delete
dirnames[:] = [x for x in dirnames if x not in skip_dirs]
[136] Fix | Delete
[137] Fix | Delete
def run(self) -> None:
[138] Fix | Delete
"""To be called from fixperms_main.py - processes this user"""
[139] Fix | Delete
self.fixperms()
[140] Fix | Delete
self.hard_links.handle()
[141] Fix | Delete
[142] Fix | Delete
def fixperms(self) -> None:
[143] Fix | Delete
"""Iterate over a user's files and chown/chmod as needed"""
[144] Fix | Delete
for stat, path in self.walk(self.homedir):
[145] Fix | Delete
try:
[146] Fix | Delete
self.check_path(stat, path)
[147] Fix | Delete
except OSError as exc:
[148] Fix | Delete
self.log.error(exc)
[149] Fix | Delete
[150] Fix | Delete
def with_exec_bits(self, stat: os.stat_result, new_mode: Union[None, int]):
[151] Fix | Delete
"""Get a new file mode including old mode's exec bits"""
[152] Fix | Delete
if new_mode is None:
[153] Fix | Delete
return None
[154] Fix | Delete
if self.args.preserve_exec:
[155] Fix | Delete
exec_bits = stat.st_mode & 0o111
[156] Fix | Delete
return new_mode | exec_bits
[157] Fix | Delete
return new_mode
[158] Fix | Delete
[159] Fix | Delete
def check_path(self, stat: os.stat_result, path: str):
[160] Fix | Delete
"""Chown and chmod files as necessary"""
[161] Fix | Delete
rule = self.find_rule(str(path))
[162] Fix | Delete
file_mode, dir_mode = rule.modes
[163] Fix | Delete
if S_ISREG(stat.st_mode): # path is a regular file
[164] Fix | Delete
new_mode = self.with_exec_bits(stat, file_mode)
[165] Fix | Delete
if stat.st_nlink > 1:
[166] Fix | Delete
self.hard_links.add(path, stat, rule.chown, new_mode)
[167] Fix | Delete
return
[168] Fix | Delete
elif S_ISDIR(stat.st_mode): # path is a directory
[169] Fix | Delete
new_mode = dir_mode
[170] Fix | Delete
elif S_ISLNK(stat.st_mode): # path is a symlink
[171] Fix | Delete
new_mode = None
[172] Fix | Delete
else: # path is socket/device/fifo/etc
[173] Fix | Delete
self.log.warning("Skipping unexpected path type at %s", path)
[174] Fix | Delete
return
[175] Fix | Delete
if new_mode is not None:
[176] Fix | Delete
self.lchmod(path, stat, new_mode)
[177] Fix | Delete
self.lchown(path, stat, *rule.chown)
[178] Fix | Delete
[179] Fix | Delete
def find_rule(self, path: str) -> 'Rule':
[180] Fix | Delete
"""Find the matching ``Rule`` for a given path"""
[181] Fix | Delete
assert isinstance(path, str)
[182] Fix | Delete
if path in self.all_docroots:
[183] Fix | Delete
return self.docroot_perms
[184] Fix | Delete
for rule in self.perm_map:
[185] Fix | Delete
if rule.regex.match(path):
[186] Fix | Delete
return rule
[187] Fix | Delete
raise ValueError(f"No matching rule for {path}")
[188] Fix | Delete
[189] Fix | Delete
def should_skip(self, path: str):
[190] Fix | Delete
"""Determine if a path should be skipped based on --skip args"""
[191] Fix | Delete
for skip in self.skip:
[192] Fix | Delete
if path == skip:
[193] Fix | Delete
return True
[194] Fix | Delete
if Path(path).is_relative_to(skip):
[195] Fix | Delete
return True
[196] Fix | Delete
return False
[197] Fix | Delete
[198] Fix | Delete
[199] Fix | Delete
class HardLinkTracker:
[200] Fix | Delete
"""Tracks and handles hard links discovered while walking through a
[201] Fix | Delete
user's files"""
[202] Fix | Delete
[203] Fix | Delete
def __init__(self, perm_map: PermMap):
[204] Fix | Delete
self.perm_map = perm_map
[205] Fix | Delete
self.chowns: dict[int, tuple[int, int]] = {}
[206] Fix | Delete
self.stats: dict[int, os.stat_result] = {}
[207] Fix | Delete
self.modes: dict[int, int] = {}
[208] Fix | Delete
self.paths: dict[int, list[str]] = {}
[209] Fix | Delete
[210] Fix | Delete
def add(
[211] Fix | Delete
self,
[212] Fix | Delete
path: str,
[213] Fix | Delete
stat: os.stat_result,
[214] Fix | Delete
chown: tuple[int, int],
[215] Fix | Delete
mode: Union[int, None],
[216] Fix | Delete
):
[217] Fix | Delete
"""Used to add a hard link found during the fixperms run which might
[218] Fix | Delete
be unsafe to operate on"""
[219] Fix | Delete
inum = stat.st_ino
[220] Fix | Delete
self.stats[inum] = stat # will be the same for all ends of the link
[221] Fix | Delete
if inum in self.paths:
[222] Fix | Delete
self.paths[inum].append(path)
[223] Fix | Delete
else:
[224] Fix | Delete
self.paths[inum] = [path]
[225] Fix | Delete
if inum in self.chowns:
[226] Fix | Delete
uid, gid = chown
[227] Fix | Delete
prev_uid, prev_gid = self.chowns[inum]
[228] Fix | Delete
if uid == -1:
[229] Fix | Delete
uid = prev_uid
[230] Fix | Delete
if gid == -1:
[231] Fix | Delete
gid = prev_gid
[232] Fix | Delete
self.chowns[inum] = [uid, gid]
[233] Fix | Delete
else:
[234] Fix | Delete
self.chowns[inum] = chown
[235] Fix | Delete
if mode is not None:
[236] Fix | Delete
self.modes[inum] = mode
[237] Fix | Delete
[238] Fix | Delete
def handle(self):
[239] Fix | Delete
"""If self.hard_links was populated with any items, handle any that are
[240] Fix | Delete
safe, or log any that are not"""
[241] Fix | Delete
for inum, stat in self.stats.items():
[242] Fix | Delete
# for each distinct inode found with hard links...
[243] Fix | Delete
if stat.st_nlink == len(self.paths[inum]):
[244] Fix | Delete
# If we came across every end of the link in this run, then it's
[245] Fix | Delete
# safe to operate on. Chmod the first instance of it; the rest
[246] Fix | Delete
# will change with it.
[247] Fix | Delete
path = self.paths[inum][0]
[248] Fix | Delete
self.perm_map.lchown(path, stat, *self.chowns[inum])
[249] Fix | Delete
self.perm_map.lchmod(path, stat, self.modes.get(inum, None))
[250] Fix | Delete
continue
[251] Fix | Delete
# Otherwise these hard links can't be trusted.
[252] Fix | Delete
for path in self.paths[inum]:
[253] Fix | Delete
self.perm_map.log.error(
[254] Fix | Delete
'%s is hardlinked and not owned by the user',
[255] Fix | Delete
quote(path),
[256] Fix | Delete
)
[257] Fix | Delete
[258] Fix | Delete
[259] Fix | Delete
class Rule:
[260] Fix | Delete
"""Fixperms path rule"""
[261] Fix | Delete
[262] Fix | Delete
def __init__(
[263] Fix | Delete
self,
[264] Fix | Delete
regex: str,
[265] Fix | Delete
modes: tuple[Union[int, None], Union[int, None]],
[266] Fix | Delete
chown: tuple[int, int],
[267] Fix | Delete
):
[268] Fix | Delete
"""Fixperms path rule
[269] Fix | Delete
[270] Fix | Delete
Args:
[271] Fix | Delete
regex (str): regular expression
[272] Fix | Delete
file tuple[(int | None), (int | None)]: (file, dir) modes if matched
[273] Fix | Delete
chown tuple[int, int]: if a matching file/dir is found, chown to
[274] Fix | Delete
this UID/GID. Use -1 to make no change.
[275] Fix | Delete
"""
[276] Fix | Delete
self.regex = re.compile(regex)
[277] Fix | Delete
assert isinstance(modes, tuple)
[278] Fix | Delete
assert isinstance(chown, tuple)
[279] Fix | Delete
self.modes = modes
[280] Fix | Delete
self.chown = chown
[281] Fix | Delete
[282] Fix | Delete
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function