Index: include/conf.h
===================================================================
--- a/include/conf.h	(revision 519)
+++ b/include/conf.h	(working copy)
@@ -41,6 +41,7 @@
 			"stat_type",		"status",	\
 			"grey_mask",		"24",		\
 			"grey_delay",		"10",           \
+			"grey_tuple",		"user",		\
 			"syslog_facility",	"mail",		\
 			"blocker_port",		"4466",		\
 			"blocker_weight",	"1",		\
@@ -80,6 +81,7 @@
                         "log_level",			\
 			"grey_mask",			\
                         "grey_delay",               	\
+                        "grey_tuple",               	\
 			"check",			\
 			"protocol",			\
                         "syslog_facility",		\
Index: include/common.h
===================================================================
--- a/include/common.h	(revision 519)
+++ b/include/common.h	(working copy)
@@ -151,6 +151,12 @@
 # endif	/* bool */
 #endif /* HAVE_BOOL */
 
+typedef enum
+{
+	GREY_TUPLE_USER = 0,
+	GREY_TUPLE_SERVER,
+} greytupletype_t;
+
 typedef struct peer_s
 {
 	struct sockaddr_in peer_addr;
@@ -207,6 +213,7 @@
 	int grey_mask;
 	int protocols;
 	int greylist_delay;
+	greytupletype_t grey_tuple;
 	postfix_config_t postfix;
 	sjsms_config_t sjsms;
 	blocker_config_t blocker;
Index: src/gross.c
===================================================================
--- a/src/gross.c	(revision 519)
+++ b/src/gross.c	(working copy)
@@ -112,7 +112,7 @@
 {
 	int ret;
 	configlist_t *cp;
-	const char *updatestr;
+	const char *updatestr, *greytuplestr;
 	struct hostent *host = NULL;
 	char buffer[MAXLINELEN] = { '\0' };
 	params_t *pp;
@@ -214,6 +214,17 @@
 		daemon_shutdown(EXIT_CONFIG, "Invalid updatestyle: %s", updatestr);
 	}
 
+	greytuplestr = CONF("grey_tuple");
+	if ((greytuplestr == NULL) || (strcmp(greytuplestr, "user") == 0)) {
+		logstr(GLOG_DEBUG, "grey_tuple: USER");
+		ctx->config.grey_tuple = GREY_TUPLE_USER;
+	} else if (strcmp(greytuplestr, "server") == 0) {
+		logstr(GLOG_DEBUG, "grey_tuple: SERVER");
+		ctx->config.grey_tuple = GREY_TUPLE_SERVER;
+	} else {
+		daemon_shutdown(EXIT_CONFIG, "Invalid grey_tuple: %s", greytuplestr);
+	}
+
 	/* we must reset errno because strtol returns 0 if it fails */
 	errno = 0;
 	ctx->config.grey_mask = strtol(CONF("grey_mask"), (char **)NULL, 10);
Index: src/worker.c
===================================================================
--- a/src/worker.c	(revision 519)
+++ b/src/worker.c	(working copy)
@@ -186,6 +186,14 @@
 	}
 }
 
+static const char *domain_part(const char *email)
+{
+	char *p = strchr(email, '@');
+	if (p == NULL)
+		return email;
+	return p + 1;
+}
+
 int
 test_tuple(final_status_t *final, grey_tuple_t *request, tmout_action_t *ta)
 {
@@ -233,7 +241,14 @@
 	}
 
 	/* greylist */
-	snprintf(maskedtuple, MSGSZ, "%s %s %s", chkipstr, request->sender, request->recipient);
+	switch (ctx->config.grey_tuple) {
+	case GREY_TUPLE_USER:
+		snprintf(maskedtuple, MSGSZ, "%s %s %s", chkipstr, request->sender, request->recipient);
+		break;
+	case GREY_TUPLE_SERVER:
+		snprintf(maskedtuple, MSGSZ, "%s %s %s", chkipstr, domain_part(request->sender), request->helo_name);
+		break;
+	}
 	digest = sha256_string(maskedtuple);
 
 	querylog_entry = &final->querylog_entry;
Index: doc/examples/grossd.conf
===================================================================
--- a/doc/examples/grossd.conf	(revision 519)
+++ b/doc/examples/grossd.conf	(working copy)
@@ -93,6 +93,13 @@
 # only if response is STATUS_GREY 
 # DEFAULT: update = grey
 
+# 'grey_tuple' is the way greylisting tuples are created. Valid options are
+# 'normal' and 'loose'. If 'grey_tuple = user' the tuple for grey listing
+# will be: masked client-ip, sender email, recipient email. If 'grey_tuple =
+# server' the tuple for greylisting will be client-ip, domain of sender email
+# and helo.
+# DEFAULT: grey_tuple = normal
+
 # 'grey_mask' is the mask for grossd to use when matching client_ip
 # against the database. Default is 24, so grossd treats addresses
 # like a.b.c.d as a.b.c.0. Setting this to 32 makes grossd to 
Index: man/grossd.conf.5.in
===================================================================
--- a/man/grossd.conf.5.in	(revision 519)
+++ b/man/grossd.conf.5.in	(working copy)
@@ -66,6 +66,12 @@
 \fIgrossd\fP\|(8) will update the database only if the response is
 `\s-1STATUS_GREY\s+1'.  Setting it to `always' may reduce the impact on
 \s-1DNS\s+1 servers.
+.IP "\fBgrey_tuple\fP" 4
+is the greylisting tuple. Valid options are `user' and `server'. If set to
+`user', which is the default, \fIgrossd\fP\|(8) will create the tuple from
+the masked `smtp\-client\-ip', sender email and recipient email. If set to
+`server' it will create the tuple from the masked `smtp\-client\-ip', the sender
+email domain and helo message.
 .IP "\fBgrey_mask\fP" 4
 is the mask for \fIgrossd\fP\|(8) to use when matching the 
 `smtp\-client\-ip' against the database.  Default is 24, which makes \fIgrossd\fP\|(8)
