Python module¶
Synopsis¶
from sievemgr import SieveManager
Description¶
- class sievemgr.SieveManager(*args, backups: int = 0, checksize: int = -1, maxmemory: int = 524_288, **kwargs)[source]¶
Bases:
SieveConn,AbstractContextManagerConnection to a ManageSieve server.
args and kwargs are passed to
open()if given. Otherwise, no connection is established.- Parameters:
backups – How many backups to keep by default.
checksize – Check whether there is enough space before uploading scripts that are larger than this size in bytes. Set to a negative number to disable this check.
maxmemory – See max_size in
tempfile.SpooledTemporaryFile.args – Positional arguments for
open().kwargs – Keyword arguments for
open().
- Raises:
ClientConnectionError – Socket error.
SieveCapabilityError – “STARTTLS” not supported.
SieveProtocolError – Server violated the ManageSieve protocol.
TLSSecurityError – Server certificate has been revoked.
For example:
>>> with SieveManager('imap.foo.example') as mgr: >>> mgr.authenticate('user', 'password') >>> with open('sieve.script', newline='') as script: >>> mgr.putscript(script, script.name) >>> mgr.setactive('sieve.script')
Warning
SieveManageris not thread-safe.- authenticate(login: str, *auth, owner: str = '', sasl: Iterable[type[BaseAuth]] = (), logauth: bool = False, **kwauth)¶
Authenticate as login.
How the user is authenticated depends on the type of SASL mechanisms given in sasl (e.g., password-based or external).
If no mechanisms are given, authentication is attempted with every non-obsolete password-based mechanism that is supported, starting with those with better security properties and progressing to those with worse security properties.
Unrecognized arguments are passed on to SASL mechanism constructors. Password-based mechanisms require a password:
>>> mgr.authenticate('user', 'password')
By contrast, the “EXTERNAL” mechanism takes no arguments:
>>> mgr.authenticate('user', sasl=(ExternalAuth,))
If an owner is given, the scripts of that owner are managed instead of those owned by login. This requires elevated privileges.
- Parameters:
login – User to login as (authentication ID).
owner – User whose scripts to manage (authorization ID).
sasl – SASL mechanisms (default:
BasePwdAuth.getmechs()).logauth – Log authentication exchange?
auth – Positional arguments for SASL mechanism constructors.
kwauth – Keyword arguments for SASL mechanism constructors.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SASLCapabilityError – Authentication mechanisms exhausted.
SASLProtocolError – Server violated the SASL protocol.
SASLSecurityError – Server could not be verified.
SieveConnectionError – Server has closed the connection.
SieveOperationError – Authentication failed.
SieveProtocolError – Server violated the ManageSieve protocol.
ValueError – Bad characters in credentials.
Note
If an owner is given, but the selected authentication mechanism does not support proxy authentication, an error is logged to the console and authentication is attempted with the next mechanism.
- backupscript(script: str, keep: int = 1) str[source]¶
Make an Emacs-style backup of script.
- keep = 0
Do nothing.
- keep = 1
scriptis backed up asscript~.- keep > 1
scriptis backed up asscript.~n~. n starts with 1 and increments with each backup. Old backups are deleted if there are more than keep backups.
For example:
>>> mgr.listscripts() [('script.sieve', True)] >>> mgr.backupscript('script.sieve', keep=0) >>> mgr.listscripts() [('script.sieve', True)]
>>> mgr.listscripts() [('script.sieve', True)] >>> mgr.backupscript('script.sieve', keep=1) >>> mgr.listscripts() [('script.sieve', True), ('script.sieve~', False)]
>>> mgr.listscripts() [('script.sieve', True)] >>> mgr.backupscript('script.sieve', keep=2) >>> mgr.listscripts() [('script.sieve', True), ('script.sieve.~1~', False)] >>> mgr.backupscript('script.sieve', keep=2) >>> mgr.listscripts() [('script.sieve', True), ('script.sieve.~1~', False), ('script.sieve.~2~', False)] >>> mgr.backupscript('script.sieve', keep=2) >>> mgr.listscripts() [('script.sieve', True), ('script.sieve.~2~', False), ('script.sieve.~3~', False)]
- Parameters:
script – Script name.
keep – How many backups to keep.
- Returns:
Backup filename or the empty string if no backup was made.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- checkscript(script: str | IO)[source]¶
Check whether script is valid.
Syntax errors trigger a
SieveOperationError. Semantic errors are reported inwarning.For example:
>>> checkscript('foo') Traceback (most recent call last): [...] SieveOperationError: line 1: error: expected end of command ';' error: parse failed.
>>> checkscript('# foo') >>>
- Parameters:
script – Script (not script name).
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveCapabilityError – “CHECKSCRIPT” not supported.
SieveConnectionError – Server has closed the connection.
SieveOperationError – Script contains syntax errors.
SieveProtocolError – Server violated the ManageSieve protocol.
Important
Sieve scripts must be encoded in UTF-8.
- close()¶
Close the client side of the connection.
Warning
Call only when the server has closed the connection.
- copyscript(source: str, target: str, backups: int | None = None)[source]¶
Download source and re-upload it as target.
- Parameters:
source – Source name.
target – Target name.
backups – How many backups to keep (default:
backups).
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- deletescript(script: str)[source]¶
Delete script.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- getactive() str | None[source]¶
Get the name of the active script.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- getscript(script: str) str[source]¶
Download script.
For example:
>>> with open('foo.sieve', 'w', encoding='utf8') as file: >>> file.write(mgr.getscript('foo.sieve'))
- Parameters:
script – Script name.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- geturl() URL | None¶
URL of the current connection.
For example:
>>> with SieveManager('imap.foo.example') as mgr: >>> mgr.authenticate('user', 'password') >>> mgr.geturl() 'sieve://user@imap.foo.example'
- havespace(script: str, size: int)[source]¶
Check whether there is enough space for script.
- Parameters:
script – Script name.
size – Script size in bytes.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveOperationError – There is not enough space.
SieveProtocolError – Server violated the ManageSieve protocol.
- listscripts(cached: bool = False) list[ScriptRecord][source]¶
List scripts and whether they are the active script.
For example:
>>> mgr.listscripts() [('foo.sieve', False), ('bar.sieve', True)]
>>> scripts = [script for script, _ in mgr.listscripts()]
- Parameters:
cached – Return cached response? [1]
- Returns:
A list of script name/status tuples.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- localscripts(scripts: Iterable[str], *args, create: bool = False, **kwargs) LocalScripts[source]¶
Temporarily download scripts for editing.
For example:
>>> from subprocess import run >>> >>> with manager.localscripts(('foo', 'bar'), create=True) as local: >>> while local.scripts: >>> run(['vi'] + local.scripts, check=True) >>> try: >>> local.reupload() >>> except Exception as err: >>> print(err, file=sys.stderr) >>> if input('Retry? ').casefold() not in ('y', 'yes'): >>> break
- Parameters:
manager – Connection to server.
scripts – Scripts to edit.
create – Create scripts that do not exist?
args – Positional arguments for
tempfile.TemporaryDirectory.kwargs – Keywords arguments for
tempfile.TemporaryDirectory.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
ValueError – Script name contains path separator.
- logout()[source]¶
Log out.
Note
logout()should be called to close the connection unlessSieveManageris used as a context manager.Warning
Logging out is unsafe after a
ProtocolError. Useshutdown()instead.
- noop(tag: str | None = None) str | None[source]¶
Request a no-op.
For example:
>>> mgr.noop('foo') 'foo'
- Parameters:
tag – String for the server to echo back.
- Returns:
Server echo.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveCapabilityError – “NOOP” not supported.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- open(host: str, port: int = 4190, source: tuple[str, int] = ('', 0), timeout: float | None = socket.getdefaulttimeout(), tls: bool = True, ocsp: bool = True)¶
Connect to host at port.
- Parameters:
host – Server name or address.
port – Server port.
source – Source address and port.
timeout – Timeout in seconds.
tls – Secure the connection?
ocsp – Check whether the server’s TLS certificate was revoked?
- Raises:
ConnectionError – Connection failed.
SieveCapabilityError – “STARTTLS” not supported.
SieveProtocolError – Server violated the ManageSieve protocol.
TLSSecurityError – Server certificate has been revoked.
- putscript(source: bytes | str | IO, target: str, backups: int | None = None)[source]¶
Upload source to the server as target.
The server should reject syntactically invalid scripts and may issue a
warningfor semantically invalid ones. Updates should be atomic.For example:
>>> mgr.putscript('# empty', 'foo.sieve')
>>> with open('foo.sieve', newline='') as file: >>> mgr.putscript(file, 'foo.sieve')
- Parameters:
source – Script (not script name).
target – Script name.
backups – How many backups to keep (default:
backups).
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
ClientSoftwareError – source has been opened in text mode without
newline=''.SieveConnectionError – Server has closed the connection.
SieveOperationError – Script contains syntax errors.
SieveProtocolError – Server violated the ManageSieve protocol.
Important
Sieve scripts must be encoded in UTF-8.
Attention
File-like objects must be opened in binary mode or with
open()’s newline argument set to the empty string.
- renamescript(source: str, target: str, emulate: bool = True)[source]¶
Rename source to target.
Some servers do not the support the “RENAMESCRIPT” command. On such servers, renaming is emulated by downloading source, re-uploading it as target, marking target as the active script if source is the active script, and then deleting source.
For example:
>>> mgr.renamescript('foo.sieve', 'bar.sieve', emulate=False)
- Parameters:
source – Script name.
target – Script name.
emulate – Emulate “RENAMESCRIPT” if the server does not support it?
- Raises:
SieveCapabilityError – “RENAMESCRIPT” not supported. [2]
SieveOperationError – source does not exist or target exists.
[2] Only raised if emulate is False.
- scriptexists(script: str, cached: bool = False) bool[source]¶
Check if script exists.
- Parameters:
script – Script name.
cached – Return cached response? [1]
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- setactive(script: str)[source]¶
Mark script as the active script.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- shutdown()¶
Shut the connection down.
Note
Use only when logging out would be unsafe.
- unauthenticate()[source]¶
Unauthenticate.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveCapabilityError – “UNAUTHENTICATE” not supported.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- unsetactive()[source]¶
Deactivate the active script.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
- classmethod validname(script: str, check: bool = False) bool[source]¶
Check whether script is a valid script name.
- Parameters:
script – Script name
check – Raise an error if script is not a valid script name?
- Raises:
ValueError – script is not valid. [3]
[3] Only raised if check is True.
- capabilities: Capabilities | None = None¶
Server capabilities.
- logger: Logger = <Logger sievemgr (WARNING)>¶
Logger to use.
Messages are logged with the following priorities:
Priority
Used for
logging.ERRORNon-fatal errors
logging.INFOState changes
logging.DEBUGData sent to/received from the server
Suppress logging:
>>> from logging import getLogger >>> getLogger('sievemgr').setLevel(logging.CRITICAL)
Use a custom logger:
>>> from logging import getLogger >>> mgr.logger = getLogger('foo').addHandler(logging.NullHandler())
Print data send to/received from the server to standard error:
>>> from logging import getLogger >>> getLogger('sievemgr').setLevel(logging.DEBUG) >>> mgr.listscripts() C: LISTSCRIPTS S: "foo.sieve" ACTIVE S: "bar.sieve" S: "baz.sieve" S: OK "Listscripts completed" (Response(response=Atom('OK'), code=(), message=None), [('foo.sieve', True), ('bar.sieve', False), ('baz.sieve', False)])
- property timeout: float | None¶
Connection timeout in seconds.
Set timeout to 500 ms:
>>> mgr.timeout = 0.5
Note
The timeout can only be set while a connection is open.
- tlscontext: SSLContext = <ssl.SSLContext object>¶
Settings for negotiating Transport Layer Security (TLS).
Disable workarounds for broken X.509 certificates:
>>> with SieveManager() as mgr: >>> mgr.tlscontext.verify_flags |= ssl.VERIFY_X509_STRICT >>> mgr.open('imap.foo.example') >>> ...
Load client certificate/key pair:
>>> with SieveManager() as mgr: >>> mgr.tlscontext.load_cert_chain(cert='cert.pem') >>> mgr.open('imap.foo.example') >>> ...
Use a custom certificate authority:
>>> with SieveManager() as mgr: >>> mgr.tlscontext.load_verify_locations(cafile='ca.pem') >>> mgr.open('imap.foo.example') >>> ...
- warning: str | None = None¶
Warning issued in response to the last “CHECKSCRIPT” or “PUTSCRIPT”.
For example:
>>> with open('script.sieve', 'br') as file: >>> mgr.execute('putscript', file, 'script.sieve') (Response(response=Atom('OK'), code=('warnings,'), message='line 7: may need to be frobnicated'), []) >>> mgr.warning 'line 7: may need to be frobnicated'
See also
- RFC 5804 (sec. 1.3)
ManageSieve “WARNINGS” response code.
- class sievemgr.Capabilities(implementation: str | None = None, sieve: tuple[str, ...] = (), language: str | None = None, maxredirects: int | None = None, notify: tuple[str, ...] = (), owner: str = '', sasl: tuple[str, ...] = (), starttls: bool = False, unauthenticate: bool = False, version: str | None = None, notunderstood: dict = <factory>)[source]¶
Bases:
objectServer capabilities.
- classmethod fromlines(lines: Iterable[Line]) CapabilitiesT[source]¶
Create a
Capabilitiesobject from a server response.
- class sievemgr.LocalScripts(manager: SieveManager, scripts: Iterable[str], *args, create: bool = False, **kwargs)[source]¶
Bases:
TemporaryDirectoryTemporarily download scripts for local editing.
For example:
>>> from subprocess import run >>> >>> def compare(manager: SieveManager, script1: str, script2: str) -> bool: >>> with LocalScripts(manager, (script1, script2)) as local: >>> cp = run(['cmp', '-s'] + local.scripts, check=False) >>> return cp.returncode == 0
- Parameters:
manager – Connection to server.
scripts – Scripts to edit.
create – Create scripts that do not exist?
args – Positional arguments for
tempfile.TemporaryDirectory.kwargs – Keywords arguments for
tempfile.TemporaryDirectory.
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveProtocolError – Server violated the ManageSieve protocol.
ValueError – Script name contains path separator.
- reupload()[source]¶
Re-upload scripts that have been changed.
Files are removed from
scriptsafter having been re-uploaded.For example:
>>> from subprocess import run >>> >>> with LocalScripts(manager, ('foo', 'bar'), create=True) as local: >>> while local.scripts: >>> run(['vi'] + local.scripts, check=True) >>> try: >>> local.reupload() >>> except Exception as err: >>> print(err, file=sys.stderr) >>> if input('Retry? ').casefold() not in ('y', 'yes') >>> break
- Raises:
ClientConnectionError – Socket error.
ClientOperationError – Another operation is already in progress.
SieveConnectionError – Server has closed the connection.
SieveOperationError – Script contains syntax errors.
SieveProtocolError – Server violated the ManageSieve protocol.
- class sievemgr.Response(response: Atom, code: tuple[Word, ...] = (), message: str | None = None)[source]¶
Bases:
objectServer response to a command.
See also
- RFC 5804 (secs. 1.2, 1.3, 4, 6.4, and passim)
ManageSieve responses
- matches(*categories: str) bool[source]¶
Check if
codematches any of the given categories.Returns False if
codeis empty. Matching is case-insensitive.For example:
>>> with open('script.sieve', newline='') as script: >>> try: >>> mgr.putscript(script, script.name) >>> except SieveOperationError as err: >>> if err.matches('QUOTA'): >>> print('over quota')
Print more informative messages:
>>> with open('script.sieve', newline='') as script: >>> try: >>> mgr.putscript(script, script.name) >>> except SieveOperationError as err: >>> if err.matches('QUOTA/MAXSCRIPTS'): >>> print('too many scripts') >>> elif err.matches('QUOTA/MAXSIZE'): >>> print(f'{script.name} is too large') >>> elif err.matches('QUOTA'): >>> print('over quota')
- toerror() SieveError[source]¶
Convert a
Responseinto an error.
- code: tuple[Word, ...] = ()¶
Response code.
ManageSieve response codes are lists of categories, separated by slashes (“/”), where each category is the super-category of the next (e.g., “quota/maxsize”).
Some response codes carry data (e.g.,
TAG "SYNC-123").See RFC 5804 (sec. 1.3) for a list of response codes.
Warning
Servers need not return response codes.
- class sievemgr.URL(hostname: str, scheme: str = 'sieve', username: str | None = None, password: str | None = None, port: int | None = None, owner: str | None = None, scriptname: str | None = None)[source]¶
Bases:
objectSieve URL.
See also
- RFC 5804 (sec. 3)
Sieve URL Scheme
- classmethod fromstr(url: str) URLT[source]¶
Create a
URLobject from a URL string.For example:
>>> URL.fromstr('sieve://user@imap.foo.example') URL(hostname='imap.foo.example', scheme='sieve', username='user', password=None, port=None, owner=None, scriptname=None)
- Raises:
ValueError – Not a valid Sieve URL.
SASL¶
Note
You need not read this section unless you want to implement an authentication mechanism.
sequenceDiagram
participant auth as authenticate()
participant mech as :SASL mechanism
participant conn as :SASL adapter
auth ->> mech: .__init__(conn, authcid, authzid, ...)
activate auth
activate mech
mech ->> mech: Prepare credentials
break Encoding invalid
mech --) auth: ValueError
end
deactivate auth
deactivate mech
auth ->> mech: .__call__()
activate auth
activate mech
mech ->> conn: .begin(name, ...)
activate conn
deactivate conn
loop SASL exchange
mech ->> conn: .receive()
activate conn
conn --) mech: Message
deactivate conn
mech ->> conn: .send(message)
activate conn
deactivate conn
end
mech ->> conn: .end()
activate conn
break Authentication failed
conn --) auth: OperationError
end
conn --) mech: Capabilities
deactivate conn
mech --) auth: Capabilities
deactivate mech
auth ->> mech: .authcid
mech --) auth: Authentication ID
auth ->> mech: .authzid
mech --) auth: Authorisation ID
deactivate auth
Authentication architecture¶
- class sievemgr.BaseAuth(adapter: BaseSASLAdapter, authcid: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
ABCBase class for authentication mechanisms.
The ManageSieve “AUTHENTICATE” command performs a Simple Authentication and Security Layer (SASL) exchange. The SASL protocol defines multiple authentication mechanisms, and
SieveManager.authenticate()delegates the SASL exchange to classes that implement such mechanisms. Such classes must subclassBaseAuthand have anameattribute that declares which mechanism they implement.Credentials must be prepared in
__init__. Subclasses should pass their connection, authcid, authzid, and prepare arguments on toBaseAuth.__init__()and then useBaseAuth.prepare()to prepare the remaining credentials.For example:
def __init__(self, connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL): """Prepare authentication. `authcid`, `authzid`, and `password` are prepared according to :rfc:`3454` and :rfc:`4013` if requested in `prepare`. Arguments: conn: Connection over which to authenticate. authcid: Authentication ID (user to login as). password: Password. authzid: Authorization ID (user whose rights to acquire). prepare: Which credentials to prepare. Raises: ValueError: Bad characters in username or password. """ super().__init__(connection, authcid, authzid, prepare) prepare &= SASLPrep.PASSWORDS # type: ignore[assignment] self.password = self.prepare(password) if prepare else password
Starting and ending the SASL exchange should be left to
BaseAuth(). The SASL exchange itself should be implemented inexchange()by usingsend()andreceive(). For example:def exchange(self): credentials = '\0'.join((self.authzid, self.authcid, self.password)) self.send(credentials.encode('utf8'))
That said,
BaseAuth’s methods can be overriden. For example:class ExternalAuth(BaseAuth): """EXTERNAL authentication. .. seealso:: :rfc:`4422` (App. A) Definition of the EXTERNAL mechanism. """ def __call__(self): """Authenticate.""" args = (self.authzid.encode('utf8'),) if self.authzid else () self.begin(*args) self.receive() self.send(b'') self.end() def exchange(self): """No-op.""" name = 'EXTERNAL'
See also
BaseSASLAdapterAbstract base class for sending and receiving SASL messages.
- RFC 4422
Simple Authentication and Security Layer (SASL)
- RFC 5804 (sec. 2.1)
ManageSieve “AUTHENTICATE” command
- __call__() Any | None[source]¶
Authenticate as
authcid.authcidis authorized asauthzidifauthzidis set (proxy authentication).- Returns:
Data returned by the server, if any.
- Raises:
ConnectionError – Server has closed the connection.
OperationError – Authentication failed.
SASLCapabilityError – Some feature is not supported.
SASLProtocolError – Server violated the SASL protocol.
SASLSecurityError – Server verification failed.
TLSCapabilityError – Channel-binding is not supported.
Note
Calls
exchange()andend().
- __init__(adapter: BaseSASLAdapter, authcid: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Prepare authentication.
authcid and authzid are prepared according to RFC 3454 and RFC 4013 if requested in prepare.
- Parameters:
conn – Connection over which to authenticate.
authcid – Authentication ID (user to login as).
authzid – Authorization ID (user whose rights to acquire).
prepare – Which credentials to prepare.
- Raises:
ValueError – Bad characters in username.
- abort()[source]¶
Abort authentication.
- Raises:
ProtocolError – Protocol violation.
- begin(data: bytes | None = None)[source]¶
Begin authentication.
- Parameters:
data – Optional client-first message.
- Raises:
ConnectionError – Connection was closed.
ProtocolError – Protocol violation.
- end()[source]¶
Conclude authentication.
- Raises:
ConnectionError – Connection was closed.
OperationError – Authentication failed.
ProtocolError – Protocol violation.
- classmethod getmechs(sort: bool = True, obsolete: bool = False) list[type[BaseAuthT]][source]¶
Get authentication classes that subclass this class.
- Parameters:
sort – Sort mechanisms by
order?obsolete – Return obsolete mechanisms?
- static prepare(string: str) str[source]¶
Prepare string according to RFC 3454 and RFC 4013.
- Returns:
Prepared string.
- Raises:
ValueError – String is malformed.
- receive() bytes[source]¶
Receive and decode an SASL message.
- Raises:
ConnectionError – Connection was closed.
OperationError – Authentication failed.
ProtocolError – Protocol violation.
Note
Calls
begin()if needed.
- send(data: bytes)[source]¶
Encode and send an SASL message.
- Raises:
ConnectionError – Connection was closed.
ProtocolError – Protocol violation.
Note
Calls
begin()if needed.
- class sievemgr.BaseSASLAdapter[source]¶
Bases:
ABCAbstract base class for sending and receiving SASL messages.
SASL messages must be translated to the underlying protocol. This class defines the types of messages that may occur in an SASL protocol exchange. Classes that translate between SASL and the underlying protocol must subclass this class.
See also
- abstract abort()[source]¶
Abort authentication.
- Raises:
ProtocolError – Protocol violation.
- abstract begin(name: str, data: bytes | None = None)[source]¶
Begin authentication.
- Parameters:
name – SASL mechanism name.
data – Optional client-first message.
- Raises:
ConnectionError – Connection was closed.
ProtocolError – Protocol violation.
- abstract end()[source]¶
Conclude authentication.
- Raises:
ConnectionError – Connection was closed.
OperationError – Authentication failed.
ProtocolError – Protocol violation.
- abstract receive() bytes[source]¶
Receive and decode an SASL message.
- Raises:
ConnectionError – Connection was closed.
OperationError – Authentication failed.
ProtocolError – Protocol violation.
- abstract send(data: bytes)[source]¶
Encode and send an SASL message.
- Raises:
ConnectionError – Connection was closed.
ProtocolError – Protocol violation.
- class sievemgr.BasePwdAuth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
-
Base class for password-based authentication mechanisms.
Prepares credentials, so that subclasses need only implement
exchange().For example:
class PlainAuth(BasePwdAuth): """PLAIN authentication. .. seealso:: :rfc:`4616` PLAIN authentication mechanism. """ def exchange(self): credentials = '\0'.join((self.authzid, self.authcid, self.password)) self.send(credentials.encode('utf8')) name = 'PLAIN'
- __init__(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Prepare authentication.
authcid, authzid, and password are prepared according to RFC 3454 and RFC 4013 if requested in prepare.
- Parameters:
conn – Connection over which to authenticate.
authcid – Authentication ID (user to login as).
password – Password.
authzid – Authorization ID (user whose rights to acquire).
prepare – Which credentials to prepare.
- Raises:
ValueError – Bad characters in username or password.
- class sievemgr.BaseScramAuth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BasePwdAuth,ABCBase class for SCRAM authentication mechanisms.
Implements
exchange(), so that subclasses need only define adigest. For example:class ScramSHA1Auth(BaseScramAuth): """SCRAM-SHA-1 authentication.""" @property def digest(self) -> str: return 'sha1' name = 'SCRAM-SHA-1' order = -10
See also
- RFC 5802
Salted Challenge Response Authentication Mechanism (SCRAM).
- RFC 7677
SCRAM-SHA-256 and SCRAM-SHA-256-PLUS.
- https://datatracker.ietf.org/doc/html/draft-melnikov-scram-bis
Updated recommendations for implementing SCRAM.
- https://datatracker.ietf.org/doc/html/draft-melnikov-scram-sha-512
SCRAM-SHA-512 and SCRAM-SHA-512-PLUS.
- https://datatracker.ietf.org/doc/html/draft-melnikov-scram-sha3-512
SCRAM-SHA3-512 and SCRAM-SHA3-512-PLUS.
- https://csb.stevekerrison.com/post/2022-01-channel-binding
Discussion of TLS channel binding.
- https://csb.stevekerrison.com/post/2022-05-scram-detail
Discussion of SCRAM.
- class sievemgr.BaseScramPlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramAuth,ABCBase class for SCRAM mechanisms with channel binding.
For example:
class ScramSHA1PlusAuth(BaseScramPlusAuth, ScramSHA1Auth): """SCRAM-SHA-1-PLUS authentication.""" name = 'SCRAM-SHA-1-PLUS' order = -1000
- class sievemgr.AuthzUnsupportedMixin(*args, **kwargs)[source]¶
Bases:
objectMixin for SASL mechanisms that do not support authorization.
For example:
- Raises:
SASLCapabilityError –
authzidis set.
- class sievemgr.CramMD5Auth(*args, **kwargs)[source]¶
Bases:
AuthzUnsupportedMixin,BasePwdAuthCRAM-MD5 authentication.
See also
- RFC 2195 (sec. 2)
Definition of CRAM-MD5.
- class sievemgr.ExternalAuth(adapter: BaseSASLAdapter, authcid: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseAuthEXTERNAL authentication.
See also
- RFC 4422 (App. A)
Definition of the EXTERNAL mechanism.
- class sievemgr.LoginAuth(*args, **kwargs)[source]¶
Bases:
AuthzUnsupportedMixin,BasePwdAuthLOGIN authentication.
- Parameters:
conn – Connection over which to authenticate.
authcid – Authentication ID (user to login as).
password – Password.
authzid – Authorization ID (user whose rights to acquire).
prepare – Which credentials to prepare.
- Raises:
ValueError – Password contains CR, LF, or NUL.
See also
- https://datatracker.ietf.org/doc/draft-murchison-sasl-login
Definition of the LOGIN mechanism.
- class sievemgr.PlainAuth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BasePwdAuthPLAIN authentication.
See also
- RFC 4616
PLAIN authentication mechanism.
- class sievemgr.ScramSHA1Auth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseScramAuthSCRAM-SHA-1 authentication.
- class sievemgr.ScramSHA1PlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramPlusAuth,ScramSHA1AuthSCRAM-SHA-1-PLUS authentication.
- class sievemgr.ScramSHA224Auth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseScramAuthSCRAM-SHA-224 authentication.
- class sievemgr.ScramSHA224PlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramPlusAuth,ScramSHA224AuthSCRAM-SHA-224-PLUS authentication.
- class sievemgr.ScramSHA256Auth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseScramAuthSCRAM-SHA-256 authentication.
- class sievemgr.ScramSHA256PlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramPlusAuth,ScramSHA256AuthSCRAM-SHA-256-PLUS authentication.
- class sievemgr.ScramSHA384Auth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseScramAuthSCRAM-SHA-384 authentication.
- class sievemgr.ScramSHA384PlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramPlusAuth,ScramSHA384AuthSCRAM-SHA-384-PLUS authentication.
- class sievemgr.ScramSHA512Auth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseScramAuthSCRAM-SHA-512 authentication.
- class sievemgr.ScramSHA512PlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramPlusAuth,ScramSHA512AuthSCRAM-SHA-512-PLUS authentication.
- class sievemgr.ScramSHA3_512Auth(connection: BaseSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]¶
Bases:
BaseScramAuthSCRAM-SHA-512 authentication.
- class sievemgr.ScramSHA3_512PlusAuth(*args, **kwargs)[source]¶
Bases:
BaseScramPlusAuth,ScramSHA3_512AuthSCRAM-SHA-512-PLUS authentication.
ERRORS¶
- exception sievemgr.ProtocolError[source]¶
Bases:
ErrorBase class for protocol errors.
Danger
Continuing after a
ProtocolErrorleads to undefined behavior.
- exception sievemgr.SecurityError[source]¶
Bases:
ErrorBase class for security errors.
Danger
Continuing after a
SecurityErrorcompromises transmitted data.
- exception sievemgr.SoftwareError[source]¶
Bases:
ErrorBase class for software errors (i.e., bugs).
Danger
Continuing after a
SoftwareErrorleads to undefined behavior.
- exception sievemgr.ClientConfigError[source]¶
Bases:
ClientError,ConfigErrorClient configuration error.
- exception sievemgr.ClientConnectionError[source]¶
Bases:
ClientError,ConnectionErrorClient-side connection error.
- exception sievemgr.ClientOperationError[source]¶
Bases:
ClientError,OperationErrorClient-side operation error.
- exception sievemgr.ClientSecurityError[source]¶
Bases:
ClientError,SecurityErrorClient security error.
- exception sievemgr.ClientSoftwareError[source]¶
Bases:
ClientError,SecurityErrorClient software error (i.e., bug).
- exception sievemgr.OCSPOperationError[source]¶
Bases:
OCSPError,OperationErrorOCSP operation error.
- exception sievemgr.SASLProtocolError[source]¶
Bases:
SASLError,ProtocolErrorServer violated the SASL protocol.
- exception sievemgr.SASLSecurityError[source]¶
Bases:
SASLError,SecurityErrorSASL security error.
- exception sievemgr.SieveCapabilityError[source]¶
Bases:
SieveError,CapabilityErrorCapability not supported by the server.
- exception sievemgr.SieveConnectionError(response: Atom = Atom('BYE'), code: tuple[Word, ...] = (), message: str | None = None)[source]¶
Bases:
Response,SieveError,ConnectionErrorServer said “BYE”.
- exception sievemgr.SieveOperationError(response: Atom = Atom('NO'), code: tuple[Word, ...] = (), message: str | None = None)[source]¶
Bases:
Response,SieveError,OperationErrorServer said “NO”.
- exception sievemgr.SieveProtocolError[source]¶
Bases:
SieveError,ProtocolErrorServer violated the ManageSieve protocol error.
- exception sievemgr.TLSCapabilityError[source]¶
Bases:
TLSError,CapabilityErrorTLS capability error.
- exception sievemgr.TLSSecurityError[source]¶
Bases:
TLSError,SecurityErrorTLS security error.
Example¶
Patch the active Sieve script of every user:
from contextlib import suppress
from sievemgr import SieveManager, ExternalAuth
from subprocess import CalledProcessError, run
with SieveManager('imap.host.example') as mgr:
for user in users:
try:
mgr.authenticate('admin', owner=user, sasl=(ExternalAuth,))
with mgr.localscripts((mgr.getactive(),)) as local:
activescript, = local.scripts
run(['patch', activescript, patchfile], check=True)
except Exception as err:
print(err, file=sys.stderr)
continue
finally:
with suppress(SieveOperationError):
mgr.unauthenticate()
Security¶
Connections are secured with Transport Layer Security (TLS) by default. TLS should not be disabled.
Credentials are stored in memory so that they need not be entered again in case of a referral. However, because page-locking is unfeasible in Python, they may be swapped out to the disk.
Privacy¶
Checking whether a server’s certificate has been revoked using OCSP enables the certificate issuer to infer that the server is accessed from your internet address.