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, backups: 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:
  • backups – How many backups to keep by default.

  • memory – See max_size in tempfile.SpooledTemporaryFile.

  • args – Positional arguments for SieveConn.open().

  • kwargs – Keyword arguments for SieveConn.open().

Raises:
authenticate(login: str, *auth, owner: str = '', sasl: type[AbstractAuth] | Iterable[type[AbstractAuth]] = (), logauth: bool = False, **kwauth)#

Authenticate as login.

How the user is logged in depends on the type of authentication mechanisms given in sasl (e.g., password-based, “EXTERNAL”). If no mechanisms are given, authentication is attempted with non-obsolete password-based mechanisms, starting with those with better security properties and progressing to those with worse security properties.

Unrecognised arguments are passed on to the constructors of those mechanisms. 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, not 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 the authentication exchange?

  • auth – Positional arguments for SASL mechanism constructors.

  • kwauth – Keyword arguments for SASL mechanism constructors.

Raises:
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 – Number of backups to keep.

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

Check whether script is semantically valid.

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

Should only be called if 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, backups: int | None = None)[source]#

Download source and re-upload it as target.

Parameters:
  • source – Source name.

  • target – Target name.

  • backups – Backup policy (default: backups).

deletescript(script: str)[source]#

Delete script.

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().

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.

getscript(script: str) str[source]#

Download script.

For example:

>>> with open('foo.sieve', 'w', encoding='utf8') as file:
>>>     file.write(mgr.getscript(file.name))
Parameters:

script – Script name.

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:

SieveOperationErrorNot enough space.

isalive(check: bool = False) bool#

Check whether sock is alive.

Parameters:

check – Raise an error if sock is dead?

Raises:

ClientConnectionErrorsock is dead. [2]

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? [3]

Returns:

A list of script name/status tuples.

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 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, backups: 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.

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.

  • backups – Backup policy (default: backups).

Raises:

SieveOperationErrorScript is not semantically valid.

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 – Server message 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 may be emulated by downloading source, re-uploading it as target, setting target to 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? [3]

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 [5]

list

Parenthesised List

int

Number [6]

str

Literal or String [5] [7]

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 in 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.

shutdown()#

Shut the connection down.

Note

Should only be used if logging out would be unsafe.

unauthenticate()[source]#

Unauthenticate.

Raises:

SieveCapabilityError – “UNAUTHENTICATE” not supported.

unsetactive()[source]#

Deactivate the active script.

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 and check is True.

backups: int = 0#

Number of backups to keep.

capabilities: Capabilities | None = None#

Server capabilities.

file: IO | 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 an individual 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 authorised as.

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 ``prepare & SASLPrep.PASSWORDS`` evaluate as true respectively.

    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 prepare & SASLPrep.PASSWORDS evaluate as true respectively.

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."""
    digest: ClassVar[str] = '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.

Implements channel-binding, so that subclasses need only define a digest. 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 prepare & SASLPrep.PASSWORDS evaluate as true respectively.

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).

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).

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).

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).

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).

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).

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 result in data loss.

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.ClientError[source]#

Bases: Error

Base class for client errors.

exception sievemgr.ClientConfigError[source]#

Bases: ClientError, ConfigError

Client configuration error.

exception sievemgr.ClientConnectionError[source]#

Bases: ClientError, ConnectionError

Connection error.

exception sievemgr.ClientOperationError[source]#

Bases: ClientError, OperationError

Client-side operation error.

exception sievemgr.ClientSecurityError[source]#

Bases: ClientError, 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.