Python module

Synopsis

from sievemgr import SieveManager

Description

class sievemgr.SieveManager[source]

Connection to a ManageSieve server.

For example:

>>> with SieveManager('imap.foo.example') as mgr:
>>>     mgr.authenticate('user', 'password')
>>>     with open('sieve.script', 'br') as script:
>>>         mgr.putscript(script, 'sieve.script')
>>>     mgr.setactive('sieve.script')

Warning

SieveManager is not thread-safe.

__init__(*args, backup: int = 0, memory: int = 524_288, **kwargs)[source]

Create a SieveManager object.

If args or kwargs are given, they are passed to open(). Otherwise, no connection is established.

Parameters:
Raises:
authenticate(login: str, *auth, owner: str = '', sasl: type[AbstractAuth] | Iterable[type[AbstractAuth]] = (), 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 the “EXTERNAL” mechanism).

If no mechanisms are given, authentication is attempted with every supported non-obsolete password-based mechanism, starting with those with better security properties and progressing to those with worse security properties.

Unrecognised 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 (authorisation 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:

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)[source]

Make an Emacs-style backup of script.

keep = 0

Do nothing.

keep = 1

script is backed up as script~.

keep > 1

script is backed up as script.~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.

Raises:
checkscript(script: str | IO)[source]

Check whether script is valid.

Syntax errors trigger a SieveOperationError. Semantic errors are reported in warning.

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:

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.

collect(check: bool = False) tuple[Response, list[Line]]

Collect the server’s response to the last command.

For example:

>>> conn.sendline(Atom('listscripts'))
>>> conn.collect()
(Response(response=Atom('OK'), code=(), message=None),
 [['foo.sieve', 'ACTIVE'], ['bar.sieve'], ['baz.sieve']])
Parameters:

check – Raise an error if the response is not “OK”?

Raises:
copyscript(source: str, target: str, backup: int | None = None)[source]

Download source and re-upload it as target.

Parameters:
  • source – Source name.

  • target – Target name.

  • backup – How many backups to keep (default: backup).

Raises:
deletescript(script: str)[source]

Delete script.

Raises:
editscripts(command: list[str], scripts: list[str], *args, catch: Callable[[Exception, str], bool] | None = None, check: bool = True, create: bool = True, **kwargs) CompletedProcess[source]

Download scripts, edit them with command, and re-upload them.

The scripts are appended to the command, which is then passed to subprocess.run(). Scripts that have been changed are then re-uploaded to the server. If the server has closed the connection in the meantime, the connection is re-established automatically.

If putscript() raises an error and catch has been given, then the error and the name of the offending script are passed to catch, which should return True if the command should be re-invoked for that script and False otherwise. Either way, the error will be suppressed.

For example:

>>> mgr.editscripts(['vi'], ['foo.sieve'])
>>> cp = mgr.editscripts(['cmp'], ['a.sieve', 'b.sieve'], check=False)
>>> if cp.returncode != 0:
>>>     print('a.sieve and b.sieve differ')
Parameters:
  • command – Command to run.

  • scripts – Scripts to edit.

  • catch – Error handler.

  • check – See subprocess.run().

  • create – Create scripts that do not exist?

  • args – Positional arguments for subprocess.run().

  • kwargs – Keywords arguments for subprocess.run().

Raises:
execute(command: str, *args: IO | Word) tuple[Response, list[Line]]

Execute command and return the server’s response.

For example:

>>> conn.execute('listscripts')
(Response(response=Atom('OK'), code=(), message=None),
 [['foo.sieve', 'ACTIVE'], ['bar.sieve'], ['baz.sieve']])
Raises:

Note

Referrals are followed automatically.

getactive() str | None[source]

Get the name of the active script.

Raises:
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:
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'

Note

Only changes to the connection state effected by open(), close(), shutdown(), authenticate(), unauthenticate(), and referrals are tracked.

havespace(script: str, size: int)[source]

Check whether there is enough space for script.

Parameters:
  • script – Script name.

  • size – Script size in bytes.

Raises:
isalive(check: bool = False) bool

Check whether sock is alive.

Parameters:

check – Raise an error if sock has died.

Raises:

AppConnectionErrorsock has died. [3]

listscripts(cached: bool = False) list[tuple[str, bool]][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? [4]

Returns:

A list of script name/status tuples.

Raises:
logout()[source]

Log out.

Note

logout() should be called to close the connection unless SieveManager is used as a context manager.

Warning

Logging out is unsafe after a ProtocolError. Use shutdown() 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:
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 certificate was revoked?

Raises:
putscript(source: str | IO, target: str, backup: int | None = None)[source]

Upload source to the server as target.

The server should reject syntactically invalid scripts. It may issue a warning for semantically invalid scripts, but should accept them nonetheless. Updates are atomic.

For example:

>>> mgr.putscript('# empty', 'foo.sieve')
>>> with open('foo.sieve', 'br') as file:
>>>     mgr.putscript(file, 'foo.sieve')
Parameters:
  • source – Script (not script name).

  • target – Script name.

  • backup – How many backups to keep (default: backup).

Raises:

Important

Sieve scripts must be encoded in UTF-8.

receiveline() Line

Receive a line and parse it.

ACAP type

Python type

Atom

Atom

Literal

str

Nil

None

Number

int

Parenthesised List

list

String

str

For example:

>>> mgr.sendline(Atom('listscripts'))
>>> mgr.receiveline()
['foo.sieve', 'ACTIVE']
>>> mgr.receiveline()
['bar.sieve']
>>> mgr.receiveline()
['baz.sieve']
>>> mgr.receiveline()
['OK', 'Listscripts completed.']
Raises:

ValueError – Line is malformed.

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:
scriptexists(script: str, cached: bool = False) bool[source]

Check if script exists.

Parameters:
  • script – Script name.

  • cached – Return cached response? [4]

Raises:
sendline(*objs: IO[Any] | Word, whole: bool = True)

Convert objs to ACAP types and send them.

Python type

ACAP type

Atom

Atom

typing.IO

Literal

None

Nil

bytes

Literal or String [6]

list

Parenthesised List

int

Number [7]

str

Literal or String [6] [8]

For example:

>>> mgr.sendline(Atom('havespace'), 'script.sieve', 12345)
>>> mgr.receiveline()
['OK', 'Putscript would succeed.']

Pipeline commands:

>>> mgr.isalive(check=True)
>>> with open('foo.sieve') as script:
>>>     mgr.sendline(Atom('putscript', script, 'foo.sieve'))
>>> mgr.sendline(Atom('logout'))
>>> for _ in range(2):
>>>     mgr.collect(check=True)
Parameters:
  • objs – Objects to serialise.

  • whole – Conclude data with CRLF?

Raises:
  • ValueError – Number is not within the range [0, 4,294,967,295].

  • TypeError – Object cannot be represented as ACAP data type.

setactive(script: str)[source]

Mark script as the active script.

Raises:
shutdown()

Shut the connection down.

Note

Use only when logging out would be unsafe.

unauthenticate()[source]

Unauthenticate.

Raises:
unsetactive()[source]

Deactivate the active script.

Raises:
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:

ValueErrorscript is not valid. [9]

backup: int = 0

How many backups to keep.

capabilities: Capabilities | None = None

Server capabilities.

file: IO | BufferedRWPair | LogIOWrapper | None = None

File-like access to sock.

host: str | None = None

Remote address.

lock: allocate_lock = <unlocked _thread.lock object>

Operation lock.

logger: Logger = <Logger sievemgr (WARNING)>

Logger to use.

Messages are logged with the following priorities:

Priority

Used for

logging.ERROR

Non-fatal errors

logging.INFO

State changes

logging.DEBUG

Data 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)])
login: str = ''

Login name (authentication ID).

ocsp: bool

Check whether the server certificate was revoked?

owner: str = ''

User whose scripts are managed (authorisation ID).

poll: poll | None = None

Polling object for sock.

port: int | None = None

Remote port.

sock: socket | None = None

Underlying socket.

sslcontext: SSLContext = <ssl.SSLContext object>

Settings for negotiating Transport Layer Security (TLS).

Disable workarounds for broken X.509 certificates:

>>> with SieveManager() as mgr:
>>>     mgr.sslcontext.verify_flags |= ssl.VERIFY_X509_STRICT
>>>     mgr.open('imap.foo.example')
>>>     ...

Load client certificate/key pair:

>>> with SieveManager() as mgr:
>>>     mgr.sslcontext.load_cert_chain(cert='cert.pem')
>>>     mgr.open('imap.foo.example')
>>>     ...

Use a custom certificate authority:

>>> with SieveManager() as mgr:
>>>     mgr.sslcontext.load_verify_locations(cafile='ca.pem')
>>>     mgr.open('imap.foo.example')
>>>     ...
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.

property tls: str | None

TLS version.

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'

Note

Only set by collect(), execute(), checkscript(), and putscript().

See also

RFC 5804 (sec. 1.3)

ManageSieve “WARNINGS” response code.

class sievemgr.Atom[source]

Bases: str

ManageSieve keyword (e.g., LISTSCRIPTS, OK).

sievemgr.Line

List of Word-s.

sievemgr.Word

Alias for Atom, None, int, str, and Line.

class sievemgr.Capabilities[source]

Bases: object

Server capabilities.

__init__(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>) None
classmethod fromlines(lines: Iterable[Line]) CapabilitiesT[source]

Create a Capabilities object from a server response.

implementation: str | None = None

Server application.

language: str | None = None

Language.

maxredirects: int | None = None

Maximum number of redirect operations permitted in a script.

notify: tuple[str, ...] = ()

URI schema parts for supported notification methods.

notunderstood: dict

Capabilities not understood by SieveManager.

owner: str = ''

Canonical name of the user whose scripts are managed.

sasl: tuple[str, ...] = ()

Supported authentication methods.

sieve: tuple[str, ...] = ()

Supported Sieve modules.

starttls: bool = False

Is “STARTTLS” available?

unauthenticate: bool = False

Is “UNAUTHENTICATE” available?

version: str | None = None

ManageSieve protocol version.

class sievemgr.Response[source]

Bases: object

Server response to a command.

See also

RFC 5804 (secs. 1.2, 1.3, 4, 6.4, and passim)

ManageSieve responses

__init__(response: Atom, code: tuple[Word, ...] = (), message: str | None = None) None
__str__() str[source]

message or, if no message was returned, a stub message.

classmethod fromline(line: Line) ResponseT[source]

Create a Response object from a Line.

matches(*categories: str) bool[source]

Check if code matches any of the given categories.

Returns False if code is empty. Matching is case-insensitive.

For example:

>>> with open('script.sieve') 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') 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 Response into 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.

message: str | None = None

Human-readable message.

Warning

Servers need not return a message.

response: Atom

‘OK’, ‘NO’, or ‘BYE’.

Response

Meaning

‘OK’

Success

‘NO’

Failure

‘BYE’

Connection closed by server

class sievemgr.URL[source]

Bases: object

Sieve URL.

See also

RFC 5804 (sec. 3)

Sieve URL Scheme

__init__(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) None
__str__()[source]

Get a string representation of the URL.

classmethod fromstr(url: str) URLT[source]

Create a URL object 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.

Warning

This API will change at some point in the future.

sequenceDiagram participant auth as authenticate() participant mech as :AbstractAuth participant conn as :AbstractSASLAdapter 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.AbstractAuth[source]

Bases: ABC

Abstract base class for SASL mechanisms.

The ManageSieve “AUTHENTICATE” command performs a Simple Authentication and Security Layer (SASL) protocol exchange. SASL is a framework that comprises different authentication mechanisms (“SASL mechanisms”). SieveManager.authenticate() does not implement any such mechanism itself, but delegates the SASL protocol exchange to classes that do. Such classes must subclass this class and have a name attribute that indicates the mechanism they implemented.

Tip

Do not subclass AbstractAuth directly. Subclass BaseAuth instead.

See also

AbstractSASLAdapter

Abstract base class for sending and receiving SASL messages.

RFC 3454

Preparation of Internationalized Strings

RFC 4013

Stringprep Profile for User Names and Passwords

RFC 4422

Simple Authentication and Security Layer (SASL)

RFC 5804 (sec. 2.1)

ManageSieve “AUTHENTICATE” command

abstract __call__() Any | None[source]

Authenticate as authcid.

authcid is authorised as authzid if authzid is set (proxy authentication).

Returns:

Data returned by the server, if any.

Raises:
abstract __init__(conn: AbstractSASLAdapter, authcid: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]

Prepare authentication.

authcid and authzid are prepared according to RFC 3454 and RFC 4013 if the specification of the SASL mechanism mandates or recommends string preparation and prepare & SASLPrep.USERNAMES evaluates as true.

Parameters:
  • conn – Connection over which to authenticate.

  • authcid – Authentication ID (user to login as).

  • authzid – Authorisation ID (user whose rights to acquire).

  • prepare – Which credentials to prepare.

Raises:

ValueError – Bad characters in username.

classmethod getmechs(sort: bool = True, obsolete: bool = False) list[type[AbstractAuthT]][source]

Get authentication classes that subclass this class.

Parameters:
  • sort – Sort mechanisms by order?

  • obsolete – Return obsolete mechanisms?

authcid: str

Authentication ID (user to login as).

authzid: str = ''

Authorisation ID (user whose rights to acquire).

name: ClassVar[str]

Mechanism name.

obsolete: bool = False

Is this mechanism obsolete?

order: int = 0

Mechanism precedence.

class sievemgr.AbstractSASLAdapter[source]

Bases: ABC

Abstract base class for sending and receiving SASL messages.

Messages that comprise an SASL protocol exchange (“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

AbstractAuth

Abstract base class for SASL mechanisms.

RFC 4422

Simple Authentication and Security Layer (SASL)

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:
abstract end()[source]

Conclude authentication.

Raises:
abstract receive() bytes[source]

Receive and decode an SASL message.

Raises:
abstract send(data: bytes)[source]

Encode and send an SASL message.

Raises:
abstract property sock: socket | SSLSocket

Underlying socket.

class sievemgr.BaseAuth[source]

Bases: AbstractAuth, ABC

Base class for authentication mechanisms.

BaseAuth provides methods to prepare strings according to RFC 3454 and RFC 4013 and a layer over the underlying AbstractSASLAdapter object that calls AbstractSASLAdapter.begin() and AbstractSASLAdapter.end() transparently.

Credentials must be prepared in __init__(). Subclasses should pass connection, authcid, authzid, and prepare to super().__init__ and use prepare() to prepare the remaining credentials. For example:

def __init__(self, connection: AbstractSASLAdapter,
             authcid: str, password: str, authzid: str = '',
             prepare: SASLPrep = SASLPrep.ALL):
    """Prepare authentication.

    `authcid`, `password`, and `authzid` are prepared according to
    :rfc:`3454` and :rfc:`4013` if ``prepare & SASLPrep.USERNAMES``
    and/or ``prepare & SASLPrep.PASSWORDS`` are non-zero.

    Arguments:
        conn: Connection over which to authenticate.
        authcid: Authentication ID (user to login as).
        password: Password.
        authzid: Authorisation 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

The SASL exchange must be implemented in exchange(). Subclasses should use send() and receive() to exchange SASL messages. For example:

def exchange(self):
    data = '\0'.join((self.authzid, self.authcid, self.password))
    self.send(data.encode('utf8'))
__call__() Any | None[source]

Authenticate as authcid.

authcid is authorised as authzid if authzid is set (proxy authentication).

Returns:

Data returned by the server, if any.

Raises:

Note

Calls exchange() and end().

__init__(adapter: AbstractSASLAdapter, authcid: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]

Prepare authentication.

authcid and authzid are prepared according to RFC 3454 and RFC 4013 if prepare & SASLPrep.USERNAMES evaluates as true.

Parameters:
  • conn – Connection over which to authenticate.

  • authcid – Authentication ID (user to login as).

  • authzid – Authorisation 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:
end()[source]

Conclude authentication.

Raises:
abstract exchange()[source]

Exchange SASL messages.

static prepare(string: str) str[source]

Prepare string according to RFC 3454 and RFC 4013.

Returns:

Prepared string.

Raises:

ValueErrorString is malformed.

receive() bytes[source]

Receive and decode an SASL message.

Raises:

Note

Calls begin() if needed.

send(data: bytes)[source]

Encode and send an SASL message.

Raises:

Note

Calls begin() if needed.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str]

Mechanism name.

property sock: socket | SSLSocket

Underlying socket.

state: AuthState = 1

Current authentication state.

class sievemgr.BasePwdAuth[source]

Bases: BaseAuth, ABC

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):
        data = '\0'.join((self.authzid, self.authcid, self.password))
        self.send(data.encode('utf8'))

    name = 'PLAIN'
__init__(connection: AbstractSASLAdapter, authcid: str, password: str, authzid: str = '', prepare: SASLPrep = SASLPrep.ALL)[source]

Prepare authentication.

authcid, password, and authzid are prepared according to RFC 3454 and RFC 4013 if prepare & SASLPrep.USERNAMES and/or prepare & SASLPrep.PASSWORDS are non-zero.

Parameters:
  • conn – Connection over which to authenticate.

  • authcid – Authentication ID (user to login as).

  • password – Password.

  • authzid – Authorisation ID (user whose rights to acquire).

  • prepare – Which credentials to prepare.

Raises:

ValueError – Bad characters in username or password.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str]

Mechanism name.

password: str

Password.

class sievemgr.BaseScramAuth[source]

Bases: BasePwdAuth, ABC

Base class for SCRAM authentication mechanisms.

Implements exchange(), so that subclasses need only define a digest. 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-03

SCRAM-SHA-512 and SCRAM-SHA-512-PLUS.

https://datatracker.ietf.org/doc/html/draft-melnikov-scram-sha3-512-03

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.

exchange()[source]

Exchange SASL messages.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

cbdata: bytes = b''

TLS channel-binding data.

cbtype: str = ''

TLS channel-binding type.

abstract property digest: str

Digest name as used by hashlib and hmac.

name: ClassVar[str]

Mechanism name.

noncelen: int = 18

Client nonce length in bytes.

password: str

Password.

class sievemgr.BaseScramPlusAuth[source]

Bases: BaseScramAuth, ABC

Base 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
__init__(*args, **kwargs)[source]

Prepare authentication.

authcid, password, and authzid are prepared according to RFC 3454 and RFC 4013 if prepare & SASLPrep.USERNAMES and/or prepare & SASLPrep.PASSWORDS are non-zero.

Parameters:
  • conn – Connection over which to authenticate.

  • authcid – Authentication ID (user to login as).

  • password – Password.

  • authzid – Authorisation ID (user whose rights to acquire).

  • prepare – Which credentials to prepare.

Raises:

ValueError – Bad characters in username or password.

class sievemgr.AuthzUnsupportedMixin[source]

Bases: object

Mixin for SASL mechanisms that do not support authorisation.

For example:

__init__(*args, **kwargs)[source]

Prepare authentication.

Raises:

SASLCapabilityErrorauthzid is set.

class sievemgr.CramMD5Auth[source]

Bases: AuthzUnsupportedMixin, BasePwdAuth

CRAM-MD5 authentication.

See also

RFC 2195 (sec. 2)

Definition of CRAM-MD5.

exchange()[source]

Exchange SASL messages.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'CRAM-MD5'

Mechanism name.

obsolete: bool = True

Is this mechanism obsolete?

password: str

Password.

class sievemgr.ExternalAuth[source]

Bases: BaseAuth

EXTERNAL authentication.

See also

RFC 4422 (App. A)

Definition of the EXTERNAL mechanism.

__call__()[source]

Authenticate.

exchange()[source]

No-op.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'EXTERNAL'

Mechanism name.

class sievemgr.LoginAuth[source]

Bases: AuthzUnsupportedMixin, BasePwdAuth

LOGIN authentication.

See also

https://datatracker.ietf.org/doc/draft-murchison-sasl-login

Definition of the LOGIN mechanism.

__init__(*args, **kwargs)[source]

Prepare authentication.

Parameters:
  • conn – Connection over which to authenticate.

  • authcid – Authentication ID (user to login as).

  • password – Password.

  • authzid – Authorisation ID (user whose rights to acquire).

  • prepare – Which credentials to prepare.

Raises:

ValueError – Password contains CR, LF, or NUL.

exchange()[source]

Exchange SASL messages.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'LOGIN'

Mechanism name.

obsolete: bool = True

Is this mechanism obsolete?

password: str

Password.

class sievemgr.PlainAuth[source]

Bases: BasePwdAuth

PLAIN authentication.

See also

RFC 4616

PLAIN authentication mechanism.

exchange()[source]

Exchange SASL messages.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'PLAIN'

Mechanism name.

password: str

Password.

class sievemgr.ScramSHA1Auth[source]

Bases: BaseScramAuth

SCRAM-SHA-1 authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

property digest: str

Digest name as used by hashlib and hmac.

name: ClassVar[str] = 'SCRAM-SHA-1'

Mechanism name.

order: int = -10

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA1PlusAuth[source]

Bases: BaseScramPlusAuth, ScramSHA1Auth

SCRAM-SHA-1-PLUS authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'SCRAM-SHA-1-PLUS'

Mechanism name.

order: int = -1000

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA224Auth[source]

Bases: BaseScramAuth

SCRAM-SHA-224 authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

property digest: str

Digest name as used by hashlib and hmac.

name: ClassVar[str] = 'SCRAM-SHA-224'

Mechanism name.

order: int = -20

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA224PlusAuth[source]

Bases: BaseScramPlusAuth, ScramSHA224Auth

SCRAM-SHA-224-PLUS authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'SCRAM-SHA-224-PLUS'

Mechanism name.

order: int = -2000

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA256Auth[source]

Bases: BaseScramAuth

SCRAM-SHA-256 authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

property digest: str

Digest name as used by hashlib and hmac.

name: ClassVar[str] = 'SCRAM-SHA-256'

Mechanism name.

order: int = -30

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA256PlusAuth[source]

Bases: BaseScramPlusAuth, ScramSHA256Auth

SCRAM-SHA-256-PLUS authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'SCRAM-SHA-256-PLUS'

Mechanism name.

order: int = -3000

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA384Auth[source]

Bases: BaseScramAuth

SCRAM-SHA-384 authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

property digest: str

Digest name as used by hashlib and hmac.

name: ClassVar[str] = 'SCRAM-SHA-384'

Mechanism name.

order: int = -40

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA384PlusAuth[source]

Bases: BaseScramPlusAuth, ScramSHA384Auth

SCRAM-SHA-384-PLUS authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'SCRAM-SHA-384-PLUS'

Mechanism name.

order: int = -4000

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA512Auth[source]

Bases: BaseScramAuth

SCRAM-SHA-512 authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

property digest

Digest name as used by hashlib and hmac.

name: ClassVar[str] = 'SCRAM-SHA-512'

Mechanism name.

order: int = -50

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA512PlusAuth[source]

Bases: BaseScramPlusAuth, ScramSHA512Auth

SCRAM-SHA-512-PLUS authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'SCRAM-SHA-512-PLUS'

Mechanism name.

order: int = -5000

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA3_512Auth[source]

Bases: BaseScramAuth

SCRAM-SHA-512 authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

property digest

Digest name as used by hashlib and hmac.

name: ClassVar[str] = 'SCRAM-SHA3-512'

Mechanism name.

order: int = -60

Mechanism precedence.

password: str

Password.

class sievemgr.ScramSHA3_512PlusAuth[source]

Bases: BaseScramPlusAuth, ScramSHA3_512Auth

SCRAM-SHA-512-PLUS authentication.

adapter: AbstractSASLAdapter

Underlying SASL adapter.

authcid: str

Authentication ID (user to login as).

name: ClassVar[str] = 'SCRAM-SHA3-512-PLUS'

Mechanism name.

order: int = -6000

Mechanism precedence.

password: str

Password.

enum sievemgr.AuthState(value)[source]

Bases: IntEnum

State of the authentication process.

Member Type:

int

Valid values are as follows:

PREAUTH = <AuthState.PREAUTH: 1>
SENT = <AuthState.SENT: 2>
RECEIVED = <AuthState.RECEIVED: 3>
DONE = <AuthState.DONE: 4>

The Enum and its members also have the following methods:

__new__(value)
enum sievemgr.SASLPrep(value)[source]

Bases: IntEnum

Controls which strings are prepared for authentication.

See also

RFC 3454

Preparation of Internationalized Strings

RFC 4013

Stringprep Profile for User Names and Passwords

RFC 4422 (sec. 4)

SASL protocol requirements

Member Type:

int

Valid values are as follows:

NONE = <SASLPrep.NONE: 0>
USERNAMES = <SASLPrep.USERNAMES: 1>
PASSWORDS = <SASLPrep.PASSWORDS: 2>
ALL = <SASLPrep.ALL: 3>

The Enum and its members also have the following methods:

__new__(value)

ERRORS

exception sievemgr.Error[source]

Bases: Exception

Base class for errors.

exception sievemgr.ProtocolError[source]

Bases: Error

Base class for protocol errors.

Danger

Continuing after a ProtocolError may cause undefined behaviour.

exception sievemgr.SecurityError[source]

Bases: Error

Base class for security errors.

Danger

Continuing after a SecurityError compromises the connection.

exception sievemgr.CapabilityError[source]

Bases: Error

Base class for capability errors.

exception sievemgr.ConfigError[source]

Bases: Error

Base class for configuration errors.

exception sievemgr.DataError[source]

Bases: Error

Base class for data errors.

exception sievemgr.OperationError[source]

Bases: Error

Base class for operation errors.

exception sievemgr.UsageError[source]

Bases: Error

Base class for usage errors.

exception sievemgr.AppError[source]

Bases: Error

Base class for application errors.

exception sievemgr.AppConfigError[source]

Bases: AppError, ConfigError

Applicaiton configuration error.

exception sievemgr.AppConnectionError[source]

Bases: AppError, ConnectionError

Client-side connection error.

exception sievemgr.AppOperationError[source]

Bases: AppError, OperationError

Client-side operation error.

exception sievemgr.AppSecurityError[source]

Bases: AppError, SecurityError

Client security error.

exception sievemgr.OCSPError[source]

Bases: Error

Base class for OCSP errors.

exception sievemgr.OCSPDataError[source]

Bases: OCSPError, DataError

OCSP data error.

exception sievemgr.OCSPOperationError[source]

Bases: OCSPError, OperationError

OCSP operation error.

exception sievemgr.SASLError[source]

Bases: Error

Base class for SASL errors.

exception sievemgr.SASLCapabilityError[source]

Bases: Error

SASL capability error.

exception sievemgr.SASLProtocolError[source]

Bases: SASLError, ProtocolError

Server violated the SASL protocol.

exception sievemgr.SASLSecurityError[source]

Bases: SASLError, SecurityError

SASL security error.

exception sievemgr.SieveError[source]

Bases: Error

Base class for ManageSieve errors.

exception sievemgr.SieveCapabilityError[source]

Bases: SieveError, CapabilityError

Capability not supported by the server.

exception sievemgr.SieveConnectionError[source]

Bases: Response, SieveError, ConnectionError

Server said “BYE”.

__init__(response: Atom = Atom('BYE'), code: tuple[Word, ...] = (), message: str | None = None)[source]
exception sievemgr.SieveOperationError[source]

Bases: Response, SieveError, OperationError

Server said “NO”.

__init__(response: Atom = Atom('NO'), code: tuple[Word, ...] = (), message: str | None = None)[source]
exception sievemgr.SieveProtocolError[source]

Bases: SieveError, ProtocolError

Server violated the ManageSieve protocol error.

exception sievemgr.TLSError[source]

Bases: Error

Base class for TLS errors.

exception sievemgr.TLSCapabilityError[source]

Bases: TLSError, CapabilityError

TLS capability error.

exception sievemgr.TLSSecurityError[source]

Bases: TLSError, OperationError

TLS security error.