Skip to main content

CheckPassword authentication in Dovecot: a practical example in Shell

In this post I’ll show how to set up authentication in Dovecot via custom CheckPassword script written in Shell. While this is not generally suitable for any kind of production deployment for performance reasons, employing CheckPassword authentication is great for prototyping custom authentication that cannot be done via other available standard drivers.

Disclaimer: This post will not contain any new useful information for Plesk users. Configuration described here is not and will not be used in Plesk. This post is primarily aimed for those who develop some kind of integration with Dovecot.

Dovecot as seen by developer

I thought this post will be a good opportunity to share some thoughts on Dovecot as a software product from developer point of view. The promised example will follow shortly.

After working with Dovecot for quite a while to integrate it into Plesk perfectly, I’ve come to understand that it is a really great piece of software. It is easy to build, easy to configure, and easy to troubleshoot. The source code is sleek and for the most part easy to read and track control flow. If I had to pick three design solutions I liked the most, they would be:

  1. The overall architecture of Dovecot regarding process and privilege separation. There’s really not much to add here, you need to feel and experience it for yourself to see how flexible and useful it is. Oh, and separate processes’ (services in terms of Dovecot configuration) permissions are highly configurable — you may set up user and group(s) for them to run under, chroot them, restrict what other processes they are allowed to talk to (via unix socket permissions), limit maximum memory, number of instances and number of client connections. Usually each service is spawned from a separate executable. This also adds to code readability since it makes the code cleaner and less dependent on the code of other services.
  2. Highly robust and easy to use memory pools. Dovecot is written in C language. The language requires explicitly allocating and freeing memory. Failure to do so properly usually causes a number of serious problems, e.g. your program may crash or continuously eat more and more memory as it runs (leak memory). Errors in manual memory management are usually relatively hard to pinpoint. Dovecot solves these and other problems I haven’t mentioned here by using memory allocations from custom memory pools pretty much throughout all of its code.
    First of all, while memory pools have a single interface to access them, they may implement and/or enforce different memory management policies. For example, while writing custom authentication module you’ll most likely use only two pools — temporary pool for temporary variables and structures and request pool for allocations within one authentication request. Once you’ve allocated memory from a memory pool, you don’t need to explicitly deallocate it somewhere. Memory will be deallocated at a later stage depending on the pool memory management policy. E.g., allocations from request pools are freed once the request is finished. Scope where allocations from temporary pool remain valid may be controlled by enclosing it into handy  T_BEGIN {  and  } T_END  macros pair. A number of widely used memory and string management functions (like realloc() , strdup() , and asprintf() ) are also reimplemented to use memory pools. This allows for clean and readable code not cluttered with manual memory management. Using memory pools also serves a security purpose — currently all allocations from memory pools are zeroed out, preventing unsafe use of uninitialized dynamically allocated memory.
    All in all, using memory pools is not new, but it is a great find. It’s not without its disadvantages, however it fits greatly in such software as Dovecot.
  3. Error message generation and handling. One of the strengths of Dovecot is its comprehensive error reporting. If something went wrong, log messages would often contain not only enough information to locate the issue reason, but will also provide hints at its resolution. For example, if a certain configuration directive causes troubles, then log message would often contain both reason of failure and point to the exact configuration directive that caused it.
    In C programs you usually have only so much of the context to give a reasonably helpful error message. This can be partially explained by the lack of such commonly used mechanic as exceptions and partially by the desire to keep knowledge about parts of the source code (mostly) unrelated to a given one to a minimum. In Dovecot many functions have  const char **error_r  as a last parameter. It is used exclusively to hold error message in case an error occurred.  error_r  argument is usually passed to all child functions that accept such parameter as well. That way on each execution stack frame (or abstraction layer, if you will) the error message may be extended with useful information known only on a given frame (or layer). This is a slightly cumbersome approach, yet an extremely simple and a very powerful one.
    However it is relatively rarely seen in other C projects, most probably due to the constant need to either manage one more entity allocated from dynamic memory or bear with limitations of statically allocated storage for error messages. This is where memory pools mentioned above come into play. Thanks to them the code that manages  error_r  values becomes very simple and straightforward.
    I think it’s also interesting to highlight the differences and similarities between this approach and classic exceptions approach (which would be used instead in C++, for example). The first important thing to note is that setting  error_r  does not signify an error — instead error condition in Dovecot is always indicated by function return value. Once a function call fails, the error together with an error message is usually propagated upwards using proper function return codes until it hits a place where it can either be recovered or reported to user and/or to log. This is very similar to the way exceptions work unwinding the call stack, but is done in an explicit way. Exceptions also may pass arbitrary information from the point of error origin to the point of error handling. Usually this information includes the error message — exactly what is passed via  error_r  variable in Dovecot. However since error handling in C is explicit and  error_r  function parameter is there in the signature, it is much harder to forget to handle error or extend error message on a given abstraction layer. This arguably results in a better, more detailed and useful error reporting. Of course you need to pay the price of explicit error handling throughout your project for that.

Certainly there are more interesting and useful design decisions and tricks to be gleaned from Dovecot source code. It’s definitely a good read for a C programmer. Be warned though: development and small part of end user documentation over at wiki2.dovecot.org is outdated up to the point when it’s plain wrong. Nec aspera terrent: you may always ask the Dovecot community to help you figure things out.

OK, enough of my ramblings. Let’s get to the example 🙂

Example of CheckPassword authentication via Shell scipt

A practical authentication problem

Let’s say  /etc/dovecot/users  has the following content:

It will be our authentication database. Each line corresponds to a separate mail account and has the following format: <full username>:<plaintext password> .

All mail accounts will be virtual. That is all of them will be mapped to a single system user named popuser . Mail storage will be in Maildir++ format with maildirs located at /var/qmail/mailnames/<domain>/<mailname>/Maildir , e.g.  /var/qmail/mailnames/test2.a10-52-181-149.qa.plesk.ru/mail2/Maildir . Both  <domain>  and  <mailname>  must be in lowercase, though logins with mixed case user names are allowed. For example logging in as  [email protected]  will be treated as logging in as [email protected] . This is roughly similar to the way mail subsystem is set up in Plesk with the exception of authentication database, which is not a text file.

Solution via passwd-file driver

Normally one would use passwd-file passdb driver in Dovecot configuration to authenticate against /etc/passwd -like file as in our case. So the relevant part of the configuration would look like:

Solution via checkpassword driver with custom Shell script

Now let’s solve the same problem via checkpassword passdb driver. To use custom CheckPassword script located at  /etc/dovecot/checkpassword.sh  as passdb and run it with superuser privileges, following Dovecot configuration should be used:

And here is example of the  /etc/dovecot/checkpassword.sh  script itself. It is rather well-commented to be easily understandable.

Due to WordPress syntax highlighter plug-in quirkiness I had to slightly modify the source. Original version can be found here.

You may naturally ask “how is this better than the much shorter passwd-file example given earlier?” The answer is you may have virtually any authentication behavior without writing a full-blown custom authentication module. In the simplest case you would only need to adjust the lines highlighted in the script above to specify your own custom credentials lookup and/or verification procedures. For example, you may generate account passwords based on their user names, or fetch credentials from a storage which is not directly supported by Dovecot.

The example CheckPassword script above was written for Bash shell, however it should work in Dash shell as well. It should also give a good idea how to write similar scripts in other programming languages. Basically, the input data is passed on the file descriptor #3 (user name and password separated by a zero byte) and through environment variables. The script should set up environment ( export  statements in the script above) in a specific way to indicate output data and call the  checkpassword-reply  binary in case of success, path to which is passed to the script as the first and only argument when Dovecot calls it.

Testing custom authentication

Personally, I prefer testing authentication changes manually via telnet. This gives a certain degree of control and understanding of how things really work. This is especially simple when using plaintext authentication in POP3 or IMAP. There are many examples on the Internet that show how to authenticate manually.

Another useful tool is imtest utility from the Cyrus suite. It provides a really easy way to test any IMAP authentication mechanism, including shared secret mechanisms like CRAM-MD5 — just specify the one via  -m  switch. The tool is usually available from one of the cyrus clients packages in your OS of choice.

If you have unexpected authentication problems, look into system mail log (BTW, it’s  /var/log/maillog  since Plesk 12.0). Both entries logged by Dovecot and CheckPassword script would end up there. You may also alter the CheckPassword script log destination by modifying the  LOG  variable value in the script example posted above.

The CheckPassword script posted above also has a “test” mode that allows you to check its operation outside of Dovecot. Just run   /etc/dovecot/checkpassword.sh test  to use it, e.g.:

Conclusion

Examples in this post are available from my Bitbucket repository. Feel free to use them in any way you see fit.

Please take care when using CheckPassword authentication.  It is not the best choice in terms of security or performance, but great to quickly get a custom authentication up and running. Dovecot also has quite a lot of other passdb drivers available fit for various cases. Consider using one of them instead — it might as well end up being easier to configure and more efficient to use.

Leave a Reply

Your email address will not be published. Required fields are marked *

0 Shares
Tweet
Share
Share
Buffer
Reddit
+1