This file is indexed.

/usr/share/pyshared/acct_mgr/htfile.py is in trac-accountmanager 0.4.3-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# -*- coding: utf-8 -*-
#
# Copyright (C) 2005-2007 Matthew Good <trac@matt-good.net>
# Copyright (C) 2011 Steffen Hoffmann <hoff.st@web.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution.
#
# Author: Matthew Good <trac@matt-good.net>

import errno
import os # to get not only os.path method but os.linesep too
# DEVEL: Use `with` statement for better file access code,
#   taking care of Python 2.5, but not needed for Python >= 2.6
#from __future__ import with_statement

from trac.core import Component, TracError, implements
from trac.config import Option

from acct_mgr.api import IPasswordStore, _, N_
from acct_mgr.pwhash import htpasswd, mkhtpasswd, htdigest
from acct_mgr.util import EnvRelativePathOption


class AbstractPasswordFileStore(Component):
    """Base class for managing password files.

    Derived classes support different formats such as
    Apache's htpasswd and htdigest format.
    See these concrete sub-classes for usage information.
    """
    abstract = True

    # DEVEL: This option is subject to removal after next major release.
    filename = EnvRelativePathOption('account-manager', 'password_file', '',
        doc = """Path to password file - depreciated in favor of other, more
              store-specific options""")

    def has_user(self, user):
        return user in self.get_users()

    def get_users(self):
        filename = str(self.filename)
        if not os.path.exists(filename):
            self.log.error('acct_mgr: get_users() -- '
                           'Can\'t locate password file "%s"' % filename)
            return []
        return self._get_users(filename)

    def set_password(self, user, password, old_password = None):
        user = user.encode('utf-8')
        password = password.encode('utf-8')
        return not self._update_file(self.prefix(user),
                                     self.userline(user, password))

    def delete_user(self, user):
        user = user.encode('utf-8')
        return self._update_file(self.prefix(user), None)

    def check_password(self, user, password):
        filename = str(self.filename)
        if not os.path.exists(filename):
            self.log.error('acct_mgr: check_password() -- '
                           'Can\'t locate password file "%s"' % filename)
            return False
        user = user.encode('utf-8')
        password = password.encode('utf-8')
        prefix = self.prefix(user)
        f = None
        try:
            f = open(filename, 'rU')
            for line in f:
                if line.startswith(prefix):
                    return self._check_userline(user, password,
                            line[len(prefix):].rstrip('\n'))
        # DEVEL: Better use new 'finally' statement here, but
        #   still need to care for Python 2.4 (RHEL5.x) for now
        except:
            self.log.error('acct_mgr: check_password() -- '
                           'Can\'t read password file "%s"' % filename)
        if isinstance(f, file):
            f.close()
        return None

    def _update_file(self, prefix, userline):
        """Add or remove user and change password.

        If `userline` is empty, the line starting with `prefix` is removed
        from the user file. Otherwise the line starting with `prefix`
        is updated to `userline`.  If no line starts with `prefix`,
        the `userline` is appended to the file.

        Returns `True` if a line matching `prefix` was updated,
        `False` otherwise.
        """
        filename = str(self.filename)
        matched = False
        new_lines = []
        try:
            # Open existing file read-only to read old content.
            # DEVEL: Use `with` statement available in Python >= 2.5
            #   as soon as we don't need to support 2.4 anymore.
            eol = '\n'
            f = open(filename, 'r')
            lines = f.readlines()

            # DEVEL: Beware, in shared use there is a race-condition,
            #   since file changes by other programs that occure from now on
            #   are currently not detected and will get overwritten.
            #   This could be fixed by file locking, but a cross-platform
            #   implementation is certainly non-trivial.
            # DEVEL: I've seen the AtomicFile object in trac.util lately,
            #   that may be worth a try.
            if len(lines) > 0:
                # predict eol style for lines without eol characters
                if not os.linesep == '\n':
                    if lines[-1].endswith('\r') and os.linesep == '\r':
                        # antique MacOS newline style safeguard
                        # DEVEL: is this really still needed?
                        eol = '\r'
                    elif lines[-1].endswith('\r\n') and os.linesep == '\r\n':
                        # Windows newline style safeguard
                        eol = '\r\n'

                for line in lines:
                    if line.startswith(prefix):
                        if not matched and userline:
                            new_lines.append(userline + eol)
                        matched = True
                    # preserve existing lines with proper eol
                    elif line.endswith(eol) and not \
                            (eol == '\n' and line.endswith('\r\n')):
                        new_lines.append(line)
                    # unify eol style using confirmed default and
                    # make sure the (last) line has a newline anyway
                    else:
                        new_lines.append(line.rstrip('\r\n') + eol)
        except EnvironmentError, e:
            if e.errno == errno.ENOENT:
                # Ignore, when file doesn't exist and create it below.
                pass
            elif e.errno == errno.EACCES:
                raise TracError(_(
                    """The password file could not be read. Trac requires
                    read and write access to both the password file
                    and its parent directory."""))
            else:
                raise

        # Finally add the new line here, if it wasn't used before
        # to update or delete a line, creating content for a new file as well.
        if not matched and userline:
            new_lines.append(userline + eol)

        # Try to (re-)open file write-only now and save new content.
        try:
            f = open(filename, 'w')
            f.writelines(new_lines)
        except EnvironmentError, e:
            if e.errno == errno.EACCES or e.errno == errno.EROFS:
                raise TracError(_(
                    """The password file could not be updated. Trac requires
                    read and write access to both the password file
                    and its parent directory."""))
            else:
                raise
        # DEVEL: Better use new 'finally' statement here, but
        #   still need to care for Python 2.4 (RHEL5.x) for now
        if isinstance(f, file):
            # Close open file now, even after exception raised.
            f.close()
            if not f.closed:
                self.log.debug('acct_mgr: _update_file() -- '
                               'Closing password file "%s" failed' % filename)
        return matched


class HtPasswdStore(AbstractPasswordFileStore):
    """Manages user accounts stored in Apache's htpasswd format.

    To use this implementation add the following configuration section to
    trac.ini:
    {{{
    [account-manager]
    password_store = HtPasswdStore
    htpasswd_file = /path/to/trac.htpasswd
    htpasswd_hash_type = crypt|md5|sha|sha256|sha512 <- None or one of these
    }}}

    Default behaviour is to detect presence of 'crypt' and use it or
    fallback to generation of passwords with md5 hash otherwise.
    """

    implements(IPasswordStore)

    filename = EnvRelativePathOption('account-manager', 'htpasswd_file', '',
        doc = N_("""Path relative to Trac environment or full host machine
                path to password file"""))
    hash_type = Option('account-manager', 'htpasswd_hash_type', 'crypt',
        doc = N_("Default hash type of new/updated passwords"))

    def config_key(self):
        return 'htpasswd'

    def prefix(self, user):
        return user + ':'

    def userline(self, user, password):
        return self.prefix(user) + mkhtpasswd(password, self.hash_type)

    def _check_userline(self, user, password, suffix):
        return suffix == htpasswd(password, suffix)

    def _get_users(self, filename):
        f = open(filename, 'rU')
        for line in f:
            user = line.split(':', 1)[0]
            if user:
                yield user.decode('utf-8')


class HtDigestStore(AbstractPasswordFileStore):
    """Manages user accounts stored in Apache's htdigest format.

    To use this implementation add the following configuration section to
    trac.ini:
    {{{
    [account-manager]
    password_store = HtDigestStore
    htdigest_file = /path/to/trac.htdigest
    htdigest_realm = TracDigestRealm
    }}}
    """

    implements(IPasswordStore)

    filename = EnvRelativePathOption('account-manager', 'htdigest_file', '',
        doc = N_("""Path relative to Trac environment or full host machine
                path to password file"""))
    realm = Option('account-manager', 'htdigest_realm', '',
        doc = N_("Realm to select relevant htdigest file entries"))

    def config_key(self):
        return 'htdigest'

    def prefix(self, user):
        return '%s:%s:' % (user, self.realm.encode('utf-8'))

    def userline(self, user, password):
        return self.prefix(user) + htdigest(user, self.realm.encode('utf-8'), password)

    def _check_userline(self, user, password, suffix):
        return suffix == htdigest(user, self.realm.encode('utf-8'), password)

    def _get_users(self, filename):
        _realm = self.realm.encode('utf-8')
        f = open(filename)
        for line in f:
            args = line.split(':')[:2]
            if len(args) == 2:
                user, realm = args
                if realm == _realm and user:
                    yield user.decode('utf-8')