【问题标题】:Identify Linux passwd file识别 Linux passwd 文件
【发布时间】:2020-04-22 12:01:45
【问题描述】:

我需要帮助来编写一个函数(最好是 python)来识别文件是 /etc/passwd 还是 etc/shadow。到目前为止,我已经尝试使用 print(pw.getpwall()) 但这会从 os env 读取文件。我需要一个接受输入并能判断文件是否为 passwd/shadow 文件的库

【问题讨论】:

  • /etc/shadow 普通用户无法读取。也许您可以检查权限。同样在 /etc/passwd 中,第三列(由: 分隔的列)将始终为0,因为它表示root 用户的标识。您可以使用readlinesplit 函数来提取字符。
  • 谢谢慈斌。除了盐值之外,我已经能够提取字符。但是我的问题是“是否有一个库可以识别给定文件是否是 passwd/shadow 文件?”如果没有,是否有解决方法来实现这一目标?就像def is_passw(path)应该返回true是路径中提供的文件是passwd/shadow文件
  • 如果您只是想确定文件是passwd 还是shadow,为什么不对文件名使用模式匹配——比如正则表达式?或者,通过在文件内容中搜索预期的模式?或者,更简单:result = file_path == '/etc/passwd'
  • @secjedi 不,没有专门针对这个 afaik 的库。您必须提取并检查其中一个答案中详细说明的条件。
  • 是的,我明白了。我已经能够使用正则表达式实施检查,但我不希望那样。我想修改 pwd.py unix python 库,以便它接受我的文件,而不是读取 /etc/passwd 文件的 os 环境。 github.com/enthought/Python-2.7.3/blob/master/Lib/plat-os2emx/…你知道我该如何完成这项工作吗?

标签: python linux shadow passwd


【解决方案1】:

passwd 和 shadow 文件格式不同。

您可以编写一个简短的函数或类。第一次迭代是:

  1. 找到 root 用户,几乎 100% 正确,root 是第一个条目
  2. 检查第 2、6、7 列(分隔符为 : 符号)
  3. 如果第 2 个是 x,第 6 个是 /root,第 7 个是 /bin/*sh,那么它几乎是 100 个密码文件%
  4. 如果第二个是盐和哈希(格式:$salt$hash),第六个是数字,第七个是空,那么它几乎是 100% 的影子文件

自然会有问题:

  • Linux 配置为不使用影子文件。在这种情况下,密码文件第 2 列包含密码
  • Linux 被配置为不使用 salt(我猜有没有可能)

请查看手册:man 5 passwdman 5 shadow

编辑,2020 年 4 月 24 日: 这是我更正后的 pwd.py:

#!/usr/bin/env python3

import os
import sys

passwd_file=('./passwd')

# path conversion handlers
def __nullpathconv(path):
    return path

def __unixpathconv(path):
    return path

# decide what field separator we can try to use - Unix standard, with
# the platform's path separator as an option.  No special field conversion
# handler is required when using the platform's path separator as field
# separator, but are required for the home directory and shell fields when
# using the standard Unix (":") field separator.
__field_sep = {':': __unixpathconv}
if os.pathsep:
    if os.pathsep != ':':
        __field_sep[os.pathsep] = __nullpathconv

# helper routine to identify which separator character is in use
def __get_field_sep(record):
    fs = None
    for c in list(__field_sep.keys()):
        # there should be 6 delimiter characters (for 7 fields)
        if record.count(c) == 6:
            fs = c
            break
    if fs:
        return fs
    else:
        raise KeyError

# class to match the new record field name accessors.
# the resulting object is intended to behave like a read-only tuple,
# with each member also accessible by a field name.
class Passwd:
    def __init__(self, name, passwd, uid, gid, gecos, dir, shell):
        self.__dict__['pw_name'] = name
        self.__dict__['pw_passwd'] = passwd
        self.__dict__['pw_uid'] = uid
        self.__dict__['pw_gid'] = gid
        self.__dict__['pw_gecos'] = gecos
        self.__dict__['pw_dir'] = dir
        self.__dict__['pw_shell'] = shell
        self.__dict__['_record'] = (self.pw_name, self.pw_passwd,
                                    self.pw_uid, self.pw_gid,
                                    self.pw_gecos, self.pw_dir,
                                    self.pw_shell)

    def __len__(self):
        return 7

    def __getitem__(self, key):
        return self._record[key]

    def __setattr__(self, name, value):
        raise AttributeError('attribute read-only: %s' % name)

    def __repr__(self):
        return str(self._record)

    def __cmp__(self, other):
        this = str(self._record)
        if this == other:
            return 0
        elif this < other:
            return -1
        else:
            return 1

# read the whole file, parsing each entry into tuple form
# with dictionaries to speed recall by UID or passwd name
def __read_passwd_file():
    if passwd_file:
        passwd = open(passwd_file, 'r')
    else:
        raise KeyError
    uidx = {}
    namx = {}
    sep = None
    while 1:
        entry = passwd.readline().strip()
        if len(entry) > 6:
            if sep is None:
                sep = __get_field_sep(entry)
            fields = entry.split(sep)
            for i in (2, 3):
                fields[i] = int(fields[i])
            for i in (5, 6):
                fields[i] = __field_sep[sep](fields[i])
            record = Passwd(*fields)
            if fields[2] not in uidx:
                uidx[fields[2]] = record
            if fields[0] not in namx:
                namx[fields[0]] = record
        elif len(entry) > 0:
            pass                         # skip empty or malformed records
        else:
            break
    passwd.close()
    if len(uidx) == 0:
        raise KeyError
    return (uidx, namx)

# return the passwd database entry by UID
def getpwuid(uid):
    u, n = __read_passwd_file()
    return u[uid]

# return the passwd database entry by passwd name
def getpwnam(name):
    u, n = __read_passwd_file()
    return n[name]

# return all the passwd database entries
def getpwall():
    u, n = __read_passwd_file()
    return list(n.values())

# test harness
if __name__ == '__main__':
    print(getpwall())

【讨论】:

  • 识别 root 用户的更一致的方法是他们的 UID/GID 都为零 (0)
  • 是的,我明白了。我已经能够使用正则表达式实施检查,但我不希望那样。我想修改 pwd.py unix python 库,以便它接受我的文件,而不是像库在这里那样读取操作系统环境的 /etc/passwd 文件:github.com/enthought/Python-2.7.3/blob/master/Lib/plat-os2emx/… 你知道如何完成这项工作吗? ?
  • 我猜你想在 python3 环境中使用它。首先,您必须转换为 python3 代码。请参阅:https://docs.python.org/2/library/2to3.html。在我删除 try to find passwd file 块(第 62-80 行)并将 passwd 文件名放入命令行参数选项之后。在代码中,passwd_file 变量包含最终的 passwd 文件。我更喜欢argparse 来处理命令行参数。
  • 我已转换为 python3,删除了其他行并实例化了 passwd_file = './passwd.txt' 即我的文件,但我在行出现“TypeError: replace() argument 1 must be str, not None”错误在第 93 行。
  • 这个库用于 OS/2 系统。据我所知,在 Unix/Linux/Mac 的情况下,没有替代分隔符,所以 os.altsepNone 并且 lib 不处理这种情况。这就是你有 TypeError 的原因。如果您只在 unix/linux 环境中使用此脚本,那么最快的解决方案是将 return path* 放在 ** _nullpathconv_unixpathconv的开头> 功能。是的,我知道这是一个肮脏的 hack,但您可以测试该库,如果满足您的要求,您可以完成它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-21
  • 2018-02-20
  • 2017-08-18
  • 2011-02-10
  • 2014-06-26
相关资源
最近更新 更多