On Fri, 12 Dec 2008 04:05:58 +0100, Sam Varshavchik <mr...@courier-mta.com> 
wrote:

> Martin Strand writes:
>
>> On Fri, 12 Dec 2008 00:51:23 +0100, Sam Varshavchik <mr...@courier-mta.com> 
>> wrote:
>>
>>> Martin Strand writes:
>>>
>>>> Using courier-pop3d, is there any way to disable simple USER/PASS login so 
>>>> that only AUTH login with CRAM-SHA1 works?
>>>> I tried setting POP3DAUTH="CRAM-SHA1" but USER/PASS still works...
>>>
>>> USER/PASS is always permitted. There is no toggle to turn it off.
>>
>> Thanks.
>> Does anyone have any good tips on how to work around this at the moment?
>> Is there perhaps a way to configure a set of "banned" commands?
>> Maybe I could write a small pop3 proxy in Perl and intercept any USER/PASS 
>> commands?
>>
>> As someone pointed out, SSL would make more sense but unfortunately that's 
>> not an option in this case.
>
> POP3 is actually a very simple protocol. Writing a POP3 proxy would actually
> be a fairly trivial excersize, especially for something like this.
>
> Actually, one just option that occured to me. You can use the authuserdb
> module which optionally uses a separate field for pre-hashed CRAM passwords.
> Those fields are not used for user/pass authentication.
>
> userdbpw -hmac-sha1 | userdb {username} set hmac-sha1pw
>
> This would set the CRAM-SHA1 password for {username}. So, you would proceed
> to set CRAM-SHA1 passwords for all accounts, and set a dummy, invalid
> password (and NOT a blank password) otherwise, so that USER/PASS
> authentication would not work, but CRAM-SHA1 authentication would work just
> fine. This process is documented further in the man pages and the associated
> INSTALL files. This only works when using the userdb module, it's one of its
> quirks, in the way that it was written, originally.

Thanks, Sam.
I'm using authmysql though so no luck (multiple servers, 10k users, frequent 
userdb updates)

I put together a basic proxy in Python, using ideas from here:
http://code.activestate.com/recipes/483732/

The proxy rewrites all forbidden commands by adding a "REJECTED-" prefix and 
then forwarding it to the server. The server can then respond with its usual 
error message. Courier's max connections per ip will get fooled, and logging 
won't be quite the same, the proxy may be vulnerable, but it's an interim 
solution so let's just hope it works ok for a while. I'm definitely no Python 
guru so use at your own risk!


Example:

$ pop3proxy --reject user --reject pass &
$ nc localhost 10110
+OK Hello there.
user asdf
-ERR Invalid command.
quit
+OK Better luck next time.







#!/usr/bin/python

import sys
import asynchat
import asyncore
import socket
import optparse
import logging

class POP3Forwarder(asyncore.dispatcher):

        def __init__(self, listen_address, listen_port, remote_address, 
remote_port, reject, backlog = 5):
                asyncore.dispatcher.__init__(self)
                self.remote_address = remote_address
                self.remote_port = remote_port
                self.reject = reject
                
                self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
                self.set_reuse_addr()
                self.bind((listen_address, listen_port))
                self.listen(backlog)

        def handle_accept(self):
                connection, address = self.accept()
                logging.debug("Client %s connected", connection.getpeername())
                POP3Client(POP3Server(connection, self.reject), 
self.remote_address, self.remote_port)

class POP3Server(asynchat.async_chat):

        def __init__(self, connection, reject):
                asynchat.async_chat.__init__(self, connection)
                self.set_terminator("\n")
                self.reject = reject
                self.client = None
                self.buffer = ""

        def handle_connect(self):
                pass

        def collect_incoming_data(self, data):
                self.buffer += data

        def found_terminator(self):
                logging.debug("C: %s", self.buffer)
                
                command = self.buffer
                for rejected in self.reject:
                        if command.upper().startswith(rejected.upper()):
                                logging.debug("Rejecting command: %s", command)
                                command = "REJECTED-" + command.upper()
                                break
                command += "\r\n" 
                
                self.client.push(command)
                self.buffer = ""

        def handle_close(self):
                logging.debug("Client %s disconnected", 
self.socket.getpeername())
                self.close()
                if self.client:
                        if len(self.buffer) > 0:
                                self.client.push(self.buffer)
                                self.buffer = ""
                        self.client.close()

class POP3Client(asynchat.async_chat):
        
        def __init__(self, server, remote_address, remote_port):
                asynchat.async_chat.__init__(self)
                self.set_terminator("\n")
                self.server = server
                self.server.client = self
                self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
                logging.debug("Connecting to server %s:%s", remote_address, 
remote_port)
                self.connect((remote_address, remote_port))
                self.buffer = ""

        def handle_connect(self):
                pass
        
        def collect_incoming_data(self, data):
                self.buffer += data 

        def found_terminator(self):
                logging.debug("S: %s", self.buffer)
                response = self.buffer.strip("\r\n")
                response += "\r\n"
                self.server.push(response)
                self.buffer = ""

        def handle_close(self):
                logging.debug("Disconnecting from server")
                self.close()
                self.server.close()

if __name__ == '__main__':

        parser = optparse.OptionParser(usage = "%prog [options]", description = 
"A POP3 proxy which rejects certain commands",)
        parser.add_option("--listen-address", action = "store", type = 
"string", dest = "listen_address", help = "Listen address [default: %default]")
        parser.add_option("--listen-port", action = "store", type = "int", dest 
= "listen_port", help = "Listen port [default: %default]")
        parser.add_option("--remote-address", action = "store", type = 
"string", dest = "remote_address", help = "Remote address [default: %default]")
        parser.add_option("--remote-port", action = "store", type = "int", dest 
= "remote_port", help = "Remote port [default: %default]")
        parser.add_option("--reject", action = "append", type = "string", dest 
= "reject", help = "Rejected commands, can be passed multiple times [default: 
%default]")
        parser.add_option("--debug", action = "store_true", dest = "debug", 
help = "Debug messages")
        parser.set_defaults(
                                reject = [],
                                remote_address = "127.0.0.1",
                                remote_port = 110,
                                listen_address = "127.0.0.1",
                                listen_port = 10110,
                                debug = False
                                                                                
                                                                                
                                                                                
                )
        (options, args) = parser.parse_args()

        # Setup logging
        if options.debug:
                level = logging.DEBUG
        else:
                level = logging.INFO
        logger = logging.getLogger()
        logger.setLevel(level)
        handler = logging.StreamHandler()
        handler.setLevel(level)
        formatter = logging.Formatter("%(message)s")
        handler.setFormatter(formatter)
        logger.addHandler(handler)

        POP3Forwarder(options.listen_address, options.listen_port, 
options.remote_address, options.remote_port, options.reject)
        asyncore.loop()


------------------------------------------------------------------------------
SF.Net email is Sponsored by MIX09, March 18-20, 2009 in Las Vegas, Nevada.
The future of the web can't happen without you.  Join us at MIX09 to help
pave the way to the Next Web now. Learn more and register at
http://ad.doubleclick.net/clk;208669438;13503038;i?http://2009.visitmix.com/
_______________________________________________
courier-users mailing list
courier-users@lists.sourceforge.net
Unsubscribe: https://lists.sourceforge.net/lists/listinfo/courier-users

Reply via email to