/usr/lib/python2.7/dist-packages/nss_cache/caches/caches.py is in nsscache 0.33-3.
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 | # Copyright 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
"""Base class of cache for nsscache."""
__author__ = 'jaq@google.com (Jamie Wilkinson)'
import errno
import logging
import os
import shutil
import stat
import tempfile
from nss_cache import config
from nss_cache import error
from nss_cache.maps import automount
from nss_cache.maps import group
from nss_cache.maps import netgroup
from nss_cache.maps import passwd
from nss_cache.maps import shadow
from nss_cache.maps import sshkey
class Cache(object):
"""Abstract base class for Caches.
The Cache object represents the cache used by NSS, that we plan on
writing the NSS data to -- it is the cache that we up date so that
the NSS module has a place to retrieve data from. Typically a cache
is some form of on-disk local storage.
You can manipulate a cache directly, like asking for a Map object from
it, or giving it a Map to write out to disk. There is an Updater class
which holds the logic for taking data from Source objects and merging them
with Cache objects.
It is important to note that a new Cache is instantiated for each
'map' defined in the configuration -- allowing different Cache
storages for different NSS maps, instead of one Cache to hold them all
(and in the darkness bind them).
"""
def __init__(self, conf, map_name, automount_mountpoint=None):
"""Initialise the Cache object.
Args:
conf: A dictionary of key/value pairs
map_name: A string representation of the map type
automount_mountpoint: A string containing the automount mountpoint,
used only by automount maps.
Raises:
UnsupportedMap: for map types we don't know about
"""
super(Cache, self).__init__()
# Set up a logger for our children
self.log = logging.getLogger(self.__class__.__name__)
# Store config info
self.conf = conf
self.output_dir = conf.get('dir', '.')
self.automount_mountpoint = automount_mountpoint
self.map_name = map_name
# Setup the map we may be asked to load our cache into.
if map_name == config.MAP_PASSWORD:
self.data = passwd.PasswdMap()
elif map_name == config.MAP_SSHKEY:
self.data = sshkey.SshkeyMap()
elif map_name == config.MAP_GROUP:
self.data = group.GroupMap()
elif map_name == config.MAP_SHADOW:
self.data = shadow.ShadowMap()
elif map_name == config.MAP_NETGROUP:
self.data = netgroup.NetgroupMap()
elif map_name == config.MAP_AUTOMOUNT:
self.data = automount.AutomountMap()
else:
raise error.UnsupportedMap('Cache does not support %s' % map_name)
def _Begin(self):
"""Start a write transaction."""
self.log.debug('Output dir: %s', self.output_dir)
self.log.debug('CWD: %s', os.getcwd())
try:
(fd, self.temp_cache_filename) = tempfile.mkstemp(
prefix='nsscache-cache-file-',
dir=os.path.join(os.getcwd(), self.output_dir))
self.temp_cache_file = os.fdopen(fd, 'w+b')
self.log.debug('opened temporary cache filename %r',
self.temp_cache_filename)
except OSError, e:
if e.errno == errno.EACCES:
self.log.info('Got OSError (%s) when trying to create temporary file',
e)
raise error.PermissionDenied('OSError: ' + str(e))
raise
def _Rollback(self):
"""Rollback a write transaction."""
self.log.debug('rolling back, deleting temp cache file %r',
self.temp_cache_filename)
self.temp_cache_file.close()
# Safe file remove (ignore "no such file or directory" errors):
try:
os.remove(self.temp_cache_filename)
except OSError, e:
if e.errno != errno.ENOENT: # errno.ENOENT = no such file or directory
raise # re-raise exception if a different error occured
def _Commit(self):
"""Ensure the cache is now the active data source for NSS.
Perform an atomic rename on the cache file to the location
expected by the NSS module. No verification of database validity
or consistency is performed here.
Returns:
Always returns True
"""
# TODO(jaq): if self WriteModifyTimestamp() fails below, we still have a
# new cache, but we might instead want to reserve the space on
# disk for a timestamp first -- thus needing a write/commit pair
# of functions for a timestamp. Edge case, so not bothering for now.
if not self.temp_cache_file.closed:
self.temp_cache_file.flush()
os.fsync(self.temp_cache_file.fileno())
self.temp_cache_file.close()
else:
self.log.debug('temp cache file was already closed before Commit')
# We emulate the permissions of our source map to avoid bugs where
# permissions may differ (usually w/shadow map)
# Catch the case where the source file may not exist for some reason and
# chose a sensible default.
try:
shutil.copymode(self.GetCompatFilename(), self.temp_cache_filename)
stat_info = os.stat(self.GetCompatFilename())
uid = stat_info.st_uid
gid = stat_info.st_gid
os.chown(self.temp_cache_filename, uid, gid)
except OSError, e:
if e.errno == errno.ENOENT:
if self.map_name == "sshkey":
os.chmod(self.temp_cache_filename,
stat.S_IRUSR|stat.S_IRGRP|stat.S_IROTH)
else:
os.chmod(self.temp_cache_filename,
stat.S_IRUSR|stat.S_IWUSR|stat.S_IRGRP|stat.S_IROTH)
self.log.debug('committing temporary cache file %r to %r',
self.temp_cache_filename, self.GetCacheFilename())
os.rename(self.temp_cache_filename, self.GetCacheFilename())
return True
def GetCacheFilename(self):
"""Return the final destination pathname of the cache file."""
return os.path.join(self.output_dir, self.CACHE_FILENAME)
def GetCompatFilename(self):
"""Return the filename where the normal (not-cache) map would be."""
# TODO(jaq): Probably shouldn't hard code '/etc' here.
return os.path.join('/etc', self.map_name)
def GetMap(self, cache_filename=None):
"""Returns the map from the cache.
Must be implemented by the child class!
Args:
cache_filename: optional extra info used by the child class
Raises:
NotImplementedError: We should have been implemented by child.
"""
raise NotImplementedError('%s must implement this method!' %
self.__class__.__name__)
def GetMapLocation(self):
"""Return the location of the Map in this cache.
This is used by automount maps so far, and must be implemented in the
child class only if it is to support automount maps.
Raises:
NotImplementedError: We should have been implemented by child.
"""
raise NotImplementedError('%s must implement this method!' %
self.__class__.__name__)
def WriteMap(self, map_data=None, force_write=False):
"""Write a map to disk.
Args:
map_data: optional Map object to overwrite our current data with.
force_write: optional flag to indicate verification checks can be
ignored.
Returns:
0 if succesful, 1 if not
"""
if map_data is None:
writable_map = self.data
else:
writable_map = map_data
entries_written = self.Write(writable_map)
# N.B. Write is destructive, len(writable_map) == 0 now.
# Asserting this isn't good for the unit tests, though.
#assert 0 == len(writable_map), "self.Write should be destructive."
if entries_written is None:
self.log.warn('cache write failed, exiting')
return 1
if force_write or self.Verify(entries_written):
# TODO(jaq): in the future we should handle return codes from
# Commit()
self._Commit()
# Create an index for this map.
self.WriteIndex()
return 0
self.log.warn('verification failed, exiting')
return 1
def WriteIndex(self):
"""Build an index for this cache.
No-op, but child classes may override this.
"""
pass
def Write(self, writable_map):
raise NotImplementedError
def Verify(self, entries_written):
raise NotImplementedError
|