[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[pysieved] patch for Dovecot auth and lookup



Greetings,

While trying to get pysieved to work with my Dovecot
installation where all users are virtual and share
the same uid/gid, I made the following changes to the
current HEAD revision.

This is mostly based on the code contributed by Koen
Vermeer and partly merged by Neale Pickett.

I went back to opening the sockets on-demand in order
to avoid problems when Dovecot's authentication daemon
is restarted while pysieved is running.

I also added the lookup function (which doesn't need
to know the password, after all).

I also added code to switch uid/gid early if they are
already known, so that pysieved can run as non-root,
but then you need special care with permissions.

A few name mismatches were corrected along the way
(passwd -> password, create -> create_storage).

And finally, I added a little write-up on how I got
the whole thing to work for me.

I hope this can help others out there.

			Philippe.

-- 
Philippe Levan - Frontier/epix Systems


diff -crN pysieved/README.dovecot pysieved.new/README.dovecot
*** pysieved/README.dovecot	Wed Dec 31 19:00:00 1969
--- pysieved.new/README.dovecot	Sun Jul 22 00:38:23 2007
***************
*** 0 ****
--- 1,125 ----
+ Dovecot authentication
+ ----------------------
+ 
+ If you want to use Dovecot authentication, set the mux config item
+ in the [Dovecot] session to the path to a client socket as defined
+ in dovecot.conf, e.g. :
+ 
+ dovecot.conf :
+   auth default {
+     socket listen {
+       client {
+         path = /var/spool/postfix/auth/dovecot
+         mode = 0666
+         user = postfix
+         group = postfix
+       }
+     }
+   }
+ 
+ pysieved.ini :
+   [Dovecot]
+   mux = /var/spool/postfix/auth/dovecot
+ 
+ 
+ 
+ Dovecot userdb lookup
+ ---------------------
+ 
+ If you want to use Dovecot userdb to get the uid/gid/home of the
+ user, set the master config item in the [Dovecot] session to the
+ path to a master socket as defined in dovecot.conf, e.g. :
+ 
+ dovecot.conf :
+   auth default {
+     socket listen {
+       master {
+         path = /var/run/dovecot/auth-master
+         mode = 0600
+         user = vmail
+         group = vmail
+       }
+     }
+   }
+ 
+ pysieved.ini :
+   [Dovecot]
+   master = /var/run/dovecot/auth-master
+ 
+ 
+ 
+ Dovecot socket permissions
+ --------------------------
+ 
+ In an environment where all users are virtual and share the same
+ uid/gid, it is possible to run pysieved as non-root by specifying
+ NUMERIC uid/gid values in the [Dovecot] section, e.g. :
+ 
+ pysieved.ini :
+   [Dovecot]
+   uid=1000
+   gid=100
+ 
+ In that case, special care may be needed so that pysieved can
+ access the 'mux' and 'master' sockets (and write the pidfile).
+ 
+ The pidfile can be moved to a directory that is writeable
+ by the specified uid/gid, e.g. :
+ 
+ pysieved.ini :
+   [main]
+   pidfile = /var/run/pysieved/pysieved.pid
+ 
+ The Dovecot client socket (mux) can safely be exported with 0666
+ permissions. But a common scenario places that socket inside the
+ Postfix chroot area, as /var/spool/postfix/private/auth where
+ the private directory is accessible only by the postfix user. 
+ Moving it to a different, public, directory should work just as
+ well, e.g. :
+ 
+ dovecot.conf :
+   auth default {
+     socket listen {
+       client {
+         path = /var/spool/postfix/auth/dovecot
+         mode = 0666
+         user = postfix
+         group = postfix
+       }
+     }
+   }
+ 
+ pysieved.ini :
+   [Dovecot]
+   mux = /var/spool/postfix/auth/dovecot
+ 
+ main.cf :
+   smtpd_sasl_type = dovecot
+   smtpd_sasl_path = auth/dovecot
+ 
+ As for the master socket, it can have restrictive permissions,
+ as recommended in the Dovecot documentation, if you make it
+ owned by the same user who owns the mail storage (which works
+ out well if using the Dovecot deliver LDA with Postfix). E.g. :
+ 
+ dovecot.conf :
+   auth default {
+     socket listen {
+       master {
+         path = /var/run/dovecot/auth-master
+         mode = 0600
+         user = vmail
+         group = vmail
+       }
+     }
+   }
+ 
+ pysieved.ini :
+   [Dovecot]
+   master = /var/run/dovecot/auth-master
+ 
+ master.cf :
+   dovecot         unix    -       n       n       -       -       pipe
+     flags=DRhu user=vmail:vmail
+     argv=/usr/local/libexec/dovecot/deliver -f $sender -d $recipient
+ 
diff -crN pysieved/plugins/dovecot.py pysieved.new/plugins/dovecot.py
*** pysieved/plugins/dovecot.py	Wed May  9 00:13:32 2007
--- pysieved.new/plugins/dovecot.py	Fri Jul 20 08:48:50 2007
***************
*** 177,216 ****
  
      def init(self, config):
          self.mux = config.get('Dovecot', 'mux', False)
          self.service = config.get('Dovecot', 'service', 'pysieved')
          self.sievec = config.get('Dovecot', 'sievec',
                                   '/usr/lib/dovecot/sievec')
          self.scripts_dir = config.get('Dovecot', 'scripts', '.pysieved')
  
!         # Only try to connect the auth socket if a mux was specified in
!         # the configuration file
!         if self.mux:
!             self.auth_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
!             self.auth_sock.connect(self.mux)
! 
!         self.pid = os.getpid()
  
  
      def auth(self, params):
!         handshake_string = self.auth_sock.recv(1024)
! 
!         self.auth_sock.sendall('VERSION\t1\t0\nCPID\t%d\n' % self.pid)
  
          auth_string = ('AUTH\t%d\tPLAIN\tservice=%s\tresp=%s' %
!                        (self.pid,
                          self.service,
                          base64.encodestring(params['username'] + '\0' +
                                              params['username'] + '\0' +
!                                             params['passwd'])))
          self.auth_sock.sendall(auth_string + '\n')
          ret = self.auth_sock.recv(1024)
  
!         self.log(2, 'Auth returns %r' % ret)
          if ret.startswith('OK'):
              return True
          return False
  
  
      def create_storage(self, params):
          return ScriptStorage(self.sievec,
                               self.scripts_dir,
--- 177,266 ----
  
      def init(self, config):
          self.mux = config.get('Dovecot', 'mux', False)
+         self.master = config.get('Dovecot', 'master', False)
          self.service = config.get('Dovecot', 'service', 'pysieved')
          self.sievec = config.get('Dovecot', 'sievec',
                                   '/usr/lib/dovecot/sievec')
          self.scripts_dir = config.get('Dovecot', 'scripts', '.pysieved')
+         self.uid = config.getint('Dovecot', 'uid', None)
+         self.gid = config.getint('Dovecot', 'gid', None)
  
!         # Drop privileges here if all users share the same uid/gid
!         if self.gid >= 0:
!             os.setgid(self.gid)
!         if self.uid >= 0:
!             os.setuid(self.uid)
  
  
      def auth(self, params):
!         # We can do this only if a MUX socket was specified
!         if self.mux:
!             self.auth_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
!             self.auth_sock.connect(self.mux)
!             handshake_string = self.auth_sock.recv(1024)
!             self.auth_sock.sendall('VERSION\t1\t0\nCPID\t%d\n' % os.getpid())
!         else:
!             raise ValueError('No MUX socket was specified')
  
+         # Since this socket will be used only once, use a hardcoded request ID
          auth_string = ('AUTH\t%d\tPLAIN\tservice=%s\tresp=%s' %
!                        (1,
                          self.service,
                          base64.encodestring(params['username'] + '\0' +
                                              params['username'] + '\0' +
!                                             params['password'])))
          self.auth_sock.sendall(auth_string + '\n')
          ret = self.auth_sock.recv(1024)
  
!         self.auth_sock.close();
! 
          if ret.startswith('OK'):
              return True
          return False
  
  
+     def lookup(self, params):
+         # We can do this only if a master socket was specified
+         if self.master:
+             self.user_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+             self.user_sock.connect(self.master)
+             master_handshake_string = self.user_sock.recv(1024)
+             self.user_sock.sendall('VERSION\t1\t0\n')
+         else:
+             raise ValueError('No master socket was specified')
+ 
+         # Since this socket will be used only once, use a hardcoded request ID
+         lookup_string = ('USER\t%d\t%s\tservice=%s' %
+                          (1,
+                           params['username'],
+                           self.service))
+         self.user_sock.sendall(lookup_string + '\n')
+         ret = self.user_sock.recv(1024)
+ 
+         self.user_sock.close();
+ 
+         if ret.startswith('USER\t'):
+             uid = ret[ret.find('\tuid=')+5:]
+             uid = uid[:uid.find('\t')]
+             gid = ret[ret.find('\tgid=')+5:]
+             gid = gid[:gid.find('\t')]
+             home = ret[ret.find('\thome=')+6:]
+             home = home[:home.find('\t')]
+ 
+             # Assuming we were started with elevated privileges, drop them now
+             if not self.gid >= 0:
+                 if int(gid) >= 0:
+                     os.setgid(int(gid))
+ 
+             if not self.uid >= 0:
+                 if int(uid) >= 0:
+                     os.setuid(int(uid))
+ 
+             return home
+         else:
+             return None
+ 
+ 
      def create_storage(self, params):
          return ScriptStorage(self.sievec,
                               self.scripts_dir,
diff -crN pysieved/pysieved.ini pysieved.new/pysieved.ini
*** pysieved/pysieved.ini	Wed May  9 00:13:32 2007
--- pysieved.new/pysieved.ini	Sat Jul 21 23:52:02 2007
***************
*** 54,60 ****
  
  [Dovecot]
  # Path to Dovecot's auth socket (do not set unless you're using Dovecot auth)
! # mux = var/run/saslauthd/mux
  
  # Path to sievec
  sievec = /usr/lib/dovecot/sievec
--- 54,63 ----
  
  [Dovecot]
  # Path to Dovecot's auth socket (do not set unless you're using Dovecot auth)
! #mux = /var/spool/postfix/auth/dovecot
! 
! # Path to Dovecot's master socket (if using Dovecot userdb lookup)
! #master = /var/run/dovecot/auth-master
  
  # Path to sievec
  sievec = /usr/lib/dovecot/sievec
***************
*** 62,64 ****
--- 65,70 ----
  # Where in user directory to store scripts
  scripts = .pysieved
  
+ # What user/group owns the mail storage
+ uid = -1
+ gid = -1
diff -crN pysieved/pysieved.py pysieved.new/pysieved.py
*** pysieved/pysieved.py	Wed May  9 00:13:32 2007
--- pysieved.new/pysieved.py	Thu Jul 19 15:13:31 2007
***************
*** 103,109 ****
  
          def new_storage(self, homedir):
              self.params['homedir'] = homedir
!             return store.create(self.params)
  
      if options.stdin:
          sock = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)
--- 103,109 ----
  
          def new_storage(self, homedir):
              self.params['homedir'] = homedir
!             return store.create_storage(self.params)
  
      if options.stdin:
          sock = socket.fromfd(0, socket.AF_INET, socket.SOCK_STREAM)