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
|
# Copyright (C) 1998-2009 by the Free Software Foundation, 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.
"""Track pending actions which require confirmation."""
import os
import time
import errno
import random
import cPickle
from Mailman import mm_cfg
from Mailman import UserDesc
from Mailman.Utils import sha_new
# Types of pending records
SUBSCRIPTION = 'S'
UNSUBSCRIPTION = 'U'
CHANGE_OF_ADDRESS = 'C'
HELD_MESSAGE = 'H'
RE_ENABLE = 'E'
PROBE_BOUNCE = 'P'
_ALLKEYS = (SUBSCRIPTION, UNSUBSCRIPTION,
CHANGE_OF_ADDRESS, HELD_MESSAGE,
RE_ENABLE, PROBE_BOUNCE,
)
try:
True, False
except NameError:
True = 1
False = 0
_missing = []
class Pending:
def InitTempVars(self):
self.__pendfile = os.path.join(self.fullpath(), 'pending.pck')
def pend_new(self, op, *content, **kws):
"""Create a new entry in the pending database, returning cookie for it.
"""
assert op in _ALLKEYS, 'op: %s' % op
lifetime = kws.get('lifetime', mm_cfg.PENDING_REQUEST_LIFE)
# We try the main loop several times. If we get a lock error somewhere
# (for instance because someone broke the lock) we simply try again.
assert self.Locked()
# Load the database
db = self.__load()
# Calculate a unique cookie. Algorithm vetted by the Timbot. time()
# has high resolution on Linux, clock() on Windows. random gives us
# about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and
# clock values basically help obscure the random number generator, as
# does the hash calculation. The integral parts of the time values
# are discarded because they're the most predictable bits.
while True:
now = time.time()
x = random.random() + now % 1.0 + time.clock() % 1.0
cookie = sha_new(repr(x)).hexdigest()
# We'll never get a duplicate, but we'll be anal about checking
# anyway.
if not db.has_key(cookie):
break
# Store the content, plus the time in the future when this entry will
# be evicted from the database, due to staleness.
db[cookie] = (op,) + content
evictions = db.setdefault('evictions', {})
evictions[cookie] = now + lifetime
self.__save(db)
return cookie
def __load(self):
try:
fp = open(self.__pendfile)
except IOError, e:
if e.errno <> errno.ENOENT: raise
return {'evictions': {}}
try:
return cPickle.load(fp)
finally:
fp.close()
def __save(self, db):
evictions = db['evictions']
now = time.time()
for cookie, data in db.items():
if cookie in ('evictions', 'version'):
continue
timestamp = evictions[cookie]
if now > timestamp:
# The entry is stale, so remove it.
del db[cookie]
del evictions[cookie]
# Clean out any bogus eviction entries.
for cookie in evictions.keys():
if not db.has_key(cookie):
del evictions[cookie]
db['version'] = mm_cfg.PENDING_FILE_SCHEMA_VERSION
tmpfile = '%s.tmp.%d.%d' % (self.__pendfile, os.getpid(), now)
omask = os.umask(007)
try:
fp = open(tmpfile, 'w')
try:
cPickle.dump(db, fp)
fp.flush()
os.fsync(fp.fileno())
finally:
fp.close()
os.rename(tmpfile, self.__pendfile)
finally:
os.umask(omask)
def pend_confirm(self, cookie, expunge=True):
"""Return data for cookie, or None if not found.
If optional expunge is True (the default), the record is also removed
from the database.
"""
db = self.__load()
# If we're not expunging, the database is read-only.
if not expunge:
return db.get(cookie)
# Since we're going to modify the database, we must make sure the list
# is locked, since it's the list lock that protects pending.pck.
assert self.Locked()
content = db.get(cookie, _missing)
if content is _missing:
return None
# Do the expunge
del db[cookie]
del db['evictions'][cookie]
self.__save(db)
return content
def pend_repend(self, cookie, data, lifetime=mm_cfg.PENDING_REQUEST_LIFE):
assert self.Locked()
db = self.__load()
db[cookie] = data
db['evictions'][cookie] = time.time() + lifetime
self.__save(db)
def _update(olddb):
db = {}
# We don't need this entry anymore
if olddb.has_key('lastculltime'):
del olddb['lastculltime']
evictions = db.setdefault('evictions', {})
for cookie, data in olddb.items():
# The cookies used to be kept as a 6 digit integer. We now keep the
# cookies as a string (sha in our case, but it doesn't matter for
# cookie matching).
cookie = str(cookie)
# The old format kept the content as a tuple and tacked the timestamp
# on as the last element of the tuple. We keep the timestamps
# separate, but require the prepending of a record type indicator. We
# know that the only things that were kept in the old format were
# subscription requests. Also, the old request format didn't have the
# subscription language. Best we can do here is use the server
# default. We also need a fullname because confirmation processing
# references all those UserDesc attributes.
ud = UserDesc.UserDesc(address=data[0],
fullname='',
password=data[1],
digest=data[2],
lang=mm_cfg.DEFAULT_SERVER_LANGUAGE,
)
db[cookie] = (SUBSCRIPTION, ud)
# The old database format kept the timestamp as the time the request
# was made. The new format keeps it as the time the request should be
# evicted.
evictions[cookie] = data[-1] + mm_cfg.PENDING_REQUEST_LIFE
return db
|