pythonlinuxshadowpasswd

Identify Linux passwd file


I need help in writing a function (python preferably) to identify if a file is /etc/passwd or etc/shadow. So far I have tried using print(pw.getpwall()) but this reads the file from the os env. I need a library that takes input and can tell if a file is passwd/shadow file or not


Solution

  • passwd and shadow file format differs.

    You can write a short function or class. First iteration would be:

    1. Find root user, almost 100% true that root is the first entry
    2. Check 2nd, 6th and 7th column (separator is : sign)
    3. If 2nd is x and 6th is /root and 7th is /bin/*sh then it is a password file almost in 100%
    4. If 2nd is a salt and hash (format: $salt$hash) and 6th is a number and 7th is empy then it is a shadow file almost in 100%

    Naturally there could be problems:

    Please check manuals: man 5 passwd and man 5 shadow

    EDIT, 2020-04-24: Here is the my corrected 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())