$Id: drac_auth.patch,v 1.30 2010/01/06 17:01:28 murch Exp $

Patch to add support for Dynamic Relay Authorization Control

For more information about DRAC, see:
	http://mail.cc.umanitoba.ca/drac/index.html


Installation
------------

1.  Apply this patch in the toplevel directory using the following command:

	# patch -p1 < contrib/drac_auth.patch

2.  Cleanup any previous builds:

	# make distclean

3a. If you DO NOT have 'smake' and 'autoconf' installed on your system, goto
    step 3b.

    Perform the following to reconfigure your build:

	# rm configure
	# sh SMakefile
	# ./configure ... --with-drac=<location of libdrac>

    NOTE: you can find your original configure command in config.status

    Proceed to step 4.

3b. Edit imap/Makefile and modify the following three variables:

	DEFS = ... -DDRAC_AUTH
	LIBS = ... -ldrac
	LDFLAGS = ... -L<location of libdrac>

4.  Build and install the software:

	# make
	# make install

5.  If dracd is not running on the same system as Cyrus (localhost),
    use the 'drachost' option in imapd.conf(5) to specify the hostname of
    the dracd server.

6.  Installation is complete!


Operation
---------

The behavior of DRAC is controlled by the value of the 'dracinterval' option
in imapd.conf(5).  If 'dracinterval' is 0 (zero), DRAC support is disabled.
Otherwise, DRAC support is enabled and has the following behavior:

pop3d: Whenever a client opens a user's INBOX, drac_auth() is called.

imapd: Once a client is logged in (via LOGIN or AUTHENTICATE),
       drac_send() will be called once every 'dracinterval' minutes.





diff --git a/configure.in b/configure.in
index e0e8cf4..7d237c0 100644
--- a/configure.in
+++ b/configure.in
@@ -1171,6 +1171,19 @@ dnl (agentx was depricated, but SNMP_SUBDIRS is conveinent as a placeholder)
 SNMP_SUBDIRS=""
 AC_SUBST(SNMP_SUBDIRS)
 
+dnl
+dnl Test for DRAC
+dnl
+DRACLIBS=
+AC_ARG_WITH(drac, [  --with-drac=DIR         use DRAC library in <DIR> [no] ],
+	if test -d "$withval"; then
+		LDFLAGS="$LDFLAGS -L${withval}"
+		AC_CHECK_LIB(drac, dracauth,
+			AC_DEFINE(DRAC_AUTH,[],[Build DRAC support?])
+			DRACLIBS="-ldrac")
+	fi)
+AC_SUBST(DRACLIBS)
+
 CMU_LIBWRAP
 CMU_UCDSNMP
 
diff --git a/imap/Makefile.in b/imap/Makefile.in
index b5e139b..1f887f3 100644
--- a/imap/Makefile.in
+++ b/imap/Makefile.in
@@ -66,6 +66,7 @@ SIEVE_OBJS = @SIEVE_OBJS@
 SIEVE_LIBS = @SIEVE_LIBS@
 IMAP_COM_ERR_LIBS = @IMAP_COM_ERR_LIBS@
 LIB_WRAP = @LIB_WRAP@
+DRAC_LIBS = @DRACLIBS@
 LIBS = $(IMAP_LIBS) $(IMAP_COM_ERR_LIBS)
 DEPLIBS = ../lib/libcyrus.a ../lib/libcyrus_min.a @DEPLIBS@
 
@@ -203,17 +204,17 @@ lmtpd.pure: lmtpd.o proxy.o $(LMTPOBJS) $(SIEVE_OBJS) \
 imapd: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE)
 	$(CC) $(LDFLAGS) -o imapd \
 	 $(SERVICE) $(IMAPDOBJS) mutex_fake.o \
-	libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP)
+	libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) $(DRAC_LIBS)
 
 imapd.pure: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE)
 	$(PURIFY) $(PUREOPT) $(CC) $(LDFLAGS) -o imapd.pure \
 	 $(SERVICE) $(IMAPDOBJS) mutex_fake.o libimap.a \
-	$(DEPLIBS) $(LIBS) $(LIB_WRAP)
+	$(DEPLIBS) $(LIBS) $(LIB_WRAP) $(DRAC_LIBS)
 
 imapd.quant: $(IMAPDOBJS) mutex_fake.o libimap.a $(DEPLIBS) $(SERVICE)
 	$(QUANTIFY) $(QUANTOPT) $(CC) $(LDFLAGS) -o imapd.quant \
 	 $(SERVICE) $(IMAPDOBJS) mutex_fake.o libimap.a \
-	$(DEPLIBS) $(LIBS) $(LIB_WRAP)
+	$(DEPLIBS) $(LIBS) $(LIB_WRAP) $(DRAC_LIBS)
 
 mupdate: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o tls.o \
 	libimap.a $(DEPLIBS)
@@ -231,7 +232,7 @@ mupdate.pure: mupdate.o mupdate-slave.o mupdate-client.o mutex_pthread.o \
 pop3d: pop3d.o proxy.o backend.o tls.o mutex_fake.o libimap.a \
 	$(DEPLIBS) $(SERVICE)
 	$(CC) $(LDFLAGS) -o pop3d pop3d.o proxy.o backend.o tls.o $(SERVICE) \
-	 mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP)
+	 mutex_fake.o libimap.a $(DEPLIBS) $(LIBS) $(LIB_WRAP) $(DRAC_LIBS)
 
 nntpd: nntpd.o proxy.o backend.o index.o smtpclient.o spool.o tls.o \
 	 mutex_fake.o nntp_err.o libimap.a $(DEPLIBS) $(SERVICE)
diff --git a/imap/imapd.c b/imap/imapd.c
index d979f3b..e9f51d1 100644
--- a/imap/imapd.c
+++ b/imap/imapd.c
@@ -193,6 +193,18 @@ static struct proxy_context imapd_proxyctx = {
     1, 1, &imapd_authstate, &imapd_userisadmin, &imapd_userisproxyadmin
 };
 
+#ifdef DRAC_AUTH
+static struct {
+    int interval;		/* dracd "ping" interval; 0 = disabled */
+    unsigned long clientaddr;
+    struct prot_waitevent *event;
+} drac;
+
+extern int dracconn(char *server, char **errmsg);
+extern int dracsend(unsigned long userip, char **errmsg);
+extern int dracdisc(char **errmsg);
+#endif /* DRAC_AUTH */
+
 /* current sub-user state */
 struct index_state *imapd_index;
 
@@ -738,6 +750,23 @@ int service_init(int argc, char **argv, char **envp)
     /* setup for sending IMAP IDLE notifications */
     idle_enabled();
 
+#ifdef DRAC_AUTH
+    /* setup for sending DRAC "pings" */
+    drac.event = NULL;
+    drac.interval = config_getint(IMAPOPT_DRACINTERVAL);
+    if (drac.interval < 0) drac.interval = 0;
+    if (drac.interval) {
+	char *err;
+
+	if (dracconn((char*) config_getstring(IMAPOPT_DRACHOST), &err) != 0) {
+	    /* disable DRAC */
+	    drac.interval = 0;
+	    syslog(LOG_ERR, "dracconn: %s", err);
+	    syslog(LOG_ERR, "DRAC notifications disabled");
+	}
+    }
+#endif /* DRAC_AUTH */
+
     /* create connection to the SNMP listener, if available. */
     snmp_connect(); /* ignore return code */
     snmp_set_str(SERVER_NAME_VERSION,cyrus_version());
@@ -848,6 +877,15 @@ int service_main(int argc __attribute__((unused)),
 		imapd_haveaddr = 1;
 	    }
 	}
+
+#ifdef DRAC_AUTH
+	if (((struct sockaddr *)&imapd_remoteaddr)->sa_family == AF_INET)
+	    drac.clientaddr = ((struct sockaddr_in *)&imapd_remoteaddr)->sin_addr.s_addr;
+	else
+	    drac.clientaddr = 0;
+    } else {
+	drac.clientaddr = 0;
+#endif /* DRAC_AUTH */
     }
 
     /* create the SASL connection */
@@ -892,6 +930,11 @@ int service_main(int argc __attribute__((unused)),
     prot_flush(imapd_out);
     snmp_increment(ACTIVE_CONNECTIONS, -1);
 
+#ifdef DRAC_AUTH
+    if (drac.event) prot_removewaitevent(imapd_in, drac.event);
+    drac.event = NULL;
+#endif /* DRAC_AUTH */
+
     /* cleanup */
     imapd_reset();
 
@@ -1004,6 +1047,10 @@ void shut_down(int code)
 
     cyrus_done();
 
+#ifdef DRAC_AUTH
+    if (drac.interval) (void) dracdisc((char **)NULL);
+#endif /* DRAC_AUTH */
+
     exit(code);
 }
 
@@ -1064,6 +1111,36 @@ static void imapd_check(struct backend *be, int usinguid)
     }
 }
 
+#ifdef DRAC_AUTH
+/*
+ * Ping dracd every 'drac.interval' minutes
+ * to let it know that we are still connected
+ */
+struct prot_waitevent *drac_ping(struct protstream *s,
+				 struct prot_waitevent *ev,
+				 void *rock __attribute__((unused)))
+{
+    char *err;
+    static int nfailure = 0;
+
+    if (dracsend(drac.clientaddr, &err) != 0) {
+	syslog(LOG_ERR, "dracsend: %s", err);
+	if (++nfailure >= 3) {
+	    /* can't contact dracd for 3 consecutive tries - disable DRAC */
+	    prot_removewaitevent(s, ev);
+	    drac.event = NULL;
+	    syslog(LOG_ERR, "DRAC notifications disabled");
+	    return NULL;
+	}
+    }
+    else
+	nfailure = 0;
+
+    ev->mark = time(NULL) + (drac.interval * 60);
+    return ev;
+}
+#endif /* DRAC_AUTH */
+
 /*
  * Top-level command loop parsing
  */
@@ -2239,6 +2316,11 @@ void cmd_login(char *tag, char *user)
     capa_response(CAPA_PREAUTH|CAPA_POSTAUTH);
     prot_printf(imapd_out, "] %s\r\n", reply);
 
+#ifdef DRAC_AUTH
+    if (!imapd_userisproxyadmin && drac.interval && drac.clientaddr)
+	drac.event = prot_addwaitevent(imapd_in, 0 /* now */, drac_ping, NULL);
+#endif /* DRAC_AUTH */
+
     /* Create telemetry log */
     imapd_logfd = telemetry_log(imapd_userid, imapd_in, imapd_out, 0);
 
@@ -2397,6 +2479,11 @@ cmd_authenticate(char *tag, char *authtype, char *resp)
     prot_setsasl(imapd_in,  imapd_saslconn);
     prot_setsasl(imapd_out, imapd_saslconn);
 
+#ifdef DRAC_AUTH
+    if (!imapd_userisproxyadmin && drac.interval && drac.clientaddr)
+	drac.event = prot_addwaitevent(imapd_in, 0 /* now */, drac_ping, NULL);
+#endif /* DRAC_AUTH */
+
     /* Create telemetry log */
     imapd_logfd = telemetry_log(imapd_userid, imapd_in, imapd_out, 0);
 
diff --git a/imap/pop3d.c b/imap/pop3d.c
index 1a11599..ae5722f 100644
--- a/imap/pop3d.c
+++ b/imap/pop3d.c
@@ -109,6 +109,10 @@ extern char *optarg;
 extern int opterr;
 
 
+#ifdef DRAC_AUTH
+static int drac_enabled;
+extern int dracauth(char *server, unsigned long userip, char **errmsg);
+#endif /* DRAC_AUTH */
 
 #ifdef HAVE_SSL
 static SSL *tls_conn;
@@ -120,6 +124,7 @@ int popd_timeout;
 char *popd_userid = 0, *popd_subfolder = 0;
 struct mailbox *popd_mailbox = NULL;
 struct auth_state *popd_authstate = 0;
+static int popd_userisproxyadmin = 0;
 int config_popuseacl, config_popuseimapflags;
 struct sockaddr_storage popd_localaddr, popd_remoteaddr;
 int popd_haveaddr = 0;
@@ -150,7 +155,7 @@ static mailbox_decideproc_t expungedeleted;
 
 /* the sasl proxy policy context */
 static struct proxy_context popd_proxyctx = {
-    0, 1, &popd_authstate, NULL, NULL
+    0, 1, &popd_authstate, NULL, &popd_userisproxyadmin
 };
 
 /* signal to config.c */
@@ -395,6 +400,7 @@ static void popd_reset(void)
 	auth_freestate(popd_authstate);
 	popd_authstate = NULL;
     }
+    popd_userisproxyadmin = 0;
     if (popd_saslconn) {
 	sasl_dispose(&popd_saslconn);
 	popd_saslconn = NULL;
@@ -580,6 +586,10 @@ int service_main(int argc __attribute__((unused)),
     prot_settimeout(popd_in, popd_timeout);
     prot_setflushonread(popd_in, popd_out);
 
+#ifdef DRAC_AUTH
+    drac_enabled = (config_getint(IMAPOPT_DRACINTERVAL) > 0);
+#endif /* DRAC_AUTH */
+
     if (kflag) kpop();
 
     /* we were connected on pop3s port so we should do 
@@ -1712,6 +1722,21 @@ int openinbox(void)
 	goto fail;
     }
 
+#ifdef DRAC_AUTH
+    if (!popd_userisproxyadmin && drac_enabled &&
+	((struct sockaddr *)&popd_remoteaddr)->sa_family == AF_INET) {
+	char *err;
+
+	if (dracauth((char*) config_getstring(IMAPOPT_DRACHOST),
+		     ((struct sockaddr_in *)&popd_remoteaddr)->sin_addr.s_addr, &err) != 0) {
+	    /* disable DRAC */
+	    drac_enabled = 0;
+	    syslog(LOG_ERR, "dracauth: %s", err);
+	    syslog(LOG_ERR, "DRAC notifications disabled");
+	}
+    }
+#endif /* DRAC_AUTH */
+
     if (mbentry.mbtype & MBTYPE_REMOTE) {
 	/* remote mailbox */
 	char *server = mbentry.partition;
diff --git a/imap/version.c b/imap/version.c
index 8438108..7e46016 100644
--- a/imap/version.c
+++ b/imap/version.c
@@ -159,6 +159,10 @@ void id_response(struct protstream *pout)
     snprintf(env_buf + strlen(env_buf), MAXIDVALUELEN - strlen(env_buf),
 	     "; %s", SIEVE_VERSION);
 #endif
+#ifdef DRAC_AUTH
+    snprintf(env_buf + strlen(env_buf), MAXIDVALUELEN - strlen(env_buf),
+	     "; DRAC");
+#endif
 #ifdef HAVE_LIBWRAP
     snprintf(env_buf + strlen(env_buf), MAXIDVALUELEN - strlen(env_buf),
 	     "; TCP Wrappers");
diff --git a/lib/imapoptions b/lib/imapoptions
index 3018761..70cd561 100644
--- a/lib/imapoptions
+++ b/lib/imapoptions
@@ -296,6 +296,14 @@ Blank lines and lines beginning with ``#'' are ignored.
    session.  Otherwise, the missing mailbox is treated as empty while
    in use by the client.*/
 
+{ "dracinterval", 5, INT }
+/* If nonzero, enables the use of DRAC (Dynamic Relay Authorization
+   Control) by the pop3d and imapd daemons.  Also sets the interval
+   (in minutes) between re-authorization requests made by imapd. */
+
+{ "drachost", "localhost", STRING }
+/* Hostname of the RPC dracd server. */
+
 { "duplicate_db", "skiplist", STRINGLIST("berkeley", "berkeley-nosync", "berkeley-hash", "berkeley-hash-nosync", "skiplist", "sql")}
 /* The cyrusdb backend to use for the duplicate delivery suppression
    and sieve. */
