[master] 19593074d Rework the solaris jail code

Nils Goroll nils.goroll at uplex.de
Tue Jun 2 09:22:06 UTC 2020


commit 19593074d901e002be3a09ada50c6d9c40d8d071
Author: Nils Goroll <nils.goroll at uplex.de>
Date:   Tue Jun 2 10:48:08 2020 +0200

    Rework the solaris jail code
    
    - simplify definition of privileges in a table file
    - only initialize priv sets once
    - implement the master jails

diff --git a/bin/varnishd/Makefile.am b/bin/varnishd/Makefile.am
index 74974525b..3b87ddfe6 100644
--- a/bin/varnishd/Makefile.am
+++ b/bin/varnishd/Makefile.am
@@ -80,6 +80,7 @@ varnishd_SOURCES = \
 	mgt/mgt_cli.c \
 	mgt/mgt_jail.c \
 	mgt/mgt_jail_solaris.c \
+	mgt/mgt_jail_solaris_tbl.h \
 	mgt/mgt_jail_unix.c \
 	mgt/mgt_main.c \
 	mgt/mgt_param.c \
diff --git a/bin/varnishd/mgt/mgt_jail_solaris.c b/bin/varnishd/mgt/mgt_jail_solaris.c
index 961ce1e8e..ef2b5423a 100644
--- a/bin/varnishd/mgt/mgt_jail_solaris.c
+++ b/bin/varnishd/mgt/mgt_jail_solaris.c
@@ -1,6 +1,6 @@
 /*-
  * Copyright (c) 2006-2011 Varnish Software AS
- * Copyright (c) 2011-2015 UPLEX - Nils Goroll Systemoptimierung
+ * Copyright 2011-2020 UPLEX - Nils Goroll Systemoptimierung
  * All rights reserved.
  *
  * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
@@ -209,6 +209,7 @@
 
 #ifdef HAVE_SETPPRIV
 
+#include <stdio.h>	// ARG_ERR
 #include <stdlib.h>
 #include <string.h>
 #include <unistd.h>
@@ -221,46 +222,35 @@
 #include <priv.h>
 #endif
 
-/* ============================================================
- * the real thing
- */
+/* renamed from sys/priv_const.h */
+#define VJS_EFFECTIVE		0
+#define VJS_INHERITABLE		1
+#define VJS_PERMITTED		2
+#define VJS_LIMIT		3
 
-// XXX @phk can we merge jail_subproc_e and jail_master_e please?
+#define VJS_NSET		(VJS_LIMIT + 1)
 
-#define JAILG_SHIFT 16
+#define VJS_MASK(x) (1U << (x))
 
-enum jail_gen_e {
-	JAILG_SUBPROC_VCC = JAIL_SUBPROC_VCC,
-	JAILG_SUBPROC_CC = JAIL_SUBPROC_CC,
-	JAILG_SUBPROC_VCLLOAD = JAIL_SUBPROC_VCLLOAD,
-	JAILG_SUBPROC_WORKER = JAIL_SUBPROC_WORKER,
+/* to denote sharing */
+#define JAIL_MASTER_ANY 0
 
-	JAILG_MASTER_LOW = JAIL_MASTER_LOW << JAILG_SHIFT,
-	JAILG_MASTER_STORAGE = JAIL_MASTER_STORAGE << JAILG_SHIFT,
-	JAILG_MASTER_PRIVPORT = JAIL_MASTER_PRIVPORT << JAILG_SHIFT
+const priv_ptype_t vjs_ptype[VJS_NSET] = {
+	[VJS_EFFECTIVE]		= PRIV_EFFECTIVE,
+	[VJS_INHERITABLE]	= PRIV_INHERITABLE,
+	[VJS_PERMITTED]		= PRIV_PERMITTED,
+	[VJS_LIMIT]		= PRIV_LIMIT
 };
 
-static inline enum jail_gen_e
-jail_subproc_gen(enum jail_subproc_e e)
-{
-	assert(e < (1 << JAILG_SHIFT));
-	return ((enum jail_gen_e)e);
-}
+static priv_set_t *vjs_sets[JAIL_LIMIT][VJS_NSET];
+static priv_set_t *vjs_inverse[JAIL_LIMIT][VJS_NSET];
+static priv_set_t *vjs_proc_setid;	// for vjs_setuid
 
-static inline enum jail_gen_e
-jail_master_gen(enum jail_master_e e)
-{
-	return ((enum jail_gen_e)(e << JAILG_SHIFT));
-}
+static void v_matchproto_(jail_master_f)
+	vjs_master(enum jail_master_e jme);
 
-static int v_matchproto_(jail_init_f)
-vjs_init(char **args)
-{
-	(void)args;
-	return 0;
-}
+/*------------------------------------------------------------*/
 
-/* for priv_delset() and priv_addset() */
 static inline int
 priv_setop_check(int a)
 {
@@ -273,13 +263,6 @@ priv_setop_check(int a)
 
 #define priv_setop_assert(a) assert(priv_setop_check(a))
 
-/*
- * we try to add all possible privileges to waive them later.
- *
- * when doing so, we need to expect EPERM
- */
-
-/* for setppriv */
 static inline int
 setppriv_check(int a)
 {
@@ -292,139 +275,163 @@ setppriv_check(int a)
 
 #define setppriv_assert(a) assert(setppriv_check(a))
 
-static void
-vjs_add_inheritable(priv_set_t *pset, enum jail_gen_e jge)
-{
-	switch (jge) {
-	case JAILG_SUBPROC_VCC:
-		break;
-	case JAILG_SUBPROC_CC:
-		priv_setop_assert(priv_addset(pset, PRIV_PROC_EXEC));
-		priv_setop_assert(priv_addset(pset, PRIV_PROC_FORK));
-		priv_setop_assert(priv_addset(pset, "file_read"));
-		priv_setop_assert(priv_addset(pset, "file_write"));
-		break;
-	case JAILG_SUBPROC_VCLLOAD:
-		break;
-	case JAILG_SUBPROC_WORKER:
-		break;
-	default:
-		INCOMPL();
-	}
-}
+/* ------------------------------------------------------------
+ * initialization of privilege sets from mgt_jail_solaris_tbl.h
+ * and implicit rules documented therein
+ */
 
-static void
-vjs_add_effective(priv_set_t *pset, enum jail_gen_e jge)
+static inline void
+vjs_add(priv_set_t *sets[VJS_NSET], unsigned mask, const char *priv)
 {
-	switch (jge) {
-	case JAILG_SUBPROC_VCC:
-		// open vmods
-		priv_setop_assert(priv_addset(pset, "file_read"));
-		// write .c output
-		priv_setop_assert(priv_addset(pset, "file_write"));
-		break;
-	case JAILG_SUBPROC_CC:
-		priv_setop_assert(priv_addset(pset, PRIV_PROC_EXEC));
-		priv_setop_assert(priv_addset(pset, PRIV_PROC_FORK));
-		priv_setop_assert(priv_addset(pset, "file_read"));
-		priv_setop_assert(priv_addset(pset, "file_write"));
-		break;
-	case JAILG_SUBPROC_VCLLOAD:
-		priv_setop_assert(priv_addset(pset, "file_read"));
-		break;
-	case JAILG_SUBPROC_WORKER:
-		priv_setop_assert(priv_addset(pset, "net_access"));
-		priv_setop_assert(priv_addset(pset, "file_read"));
-		priv_setop_assert(priv_addset(pset, "file_write"));
-		break;
-	default:
-		INCOMPL();
-	}
+	int i;
+	for (i = 0; i < VJS_NSET; i++)
+		if (mask & VJS_MASK(i))
+			priv_setop_assert(priv_addset(sets[i], priv));
 }
 
 /*
- * permitted is initialized from effective (see vjs_waive)
- * so only additionally required privileges need to be added here
+ * we reduce the limit set to the union of all jail level limit sets: first we
+ * try to enable all privileges which we possibly need, then we waive the
+ * inverse in vjs_init()
  */
 
-static void
-vjs_add_permitted(priv_set_t *pset, enum jail_gen_e jge)
+static int
+vjs_master_rules(void)
 {
-	(void) pset;
-	switch (jge) {
-	case JAILG_SUBPROC_VCC:
-	case JAILG_SUBPROC_CC:
-	case JAILG_SUBPROC_VCLLOAD:
-		break;
-	case JAILG_SUBPROC_WORKER:
-		/* vmod_unix getpeerucred() */
-		AZ(priv_addset(pset, PRIV_PROC_INFO));
-		break;
-	default:
-		INCOMPL();
+	priv_set_t *punion = priv_allocset();
+	int vs, vj;
+
+	AN(punion);
+
+	for (vs = VJS_INHERITABLE; vs <= VJS_PERMITTED; vs ++) {
+		priv_emptyset(punion);
+		for (vj = JAIL_SUBPROC; vj < JAIL_LIMIT; vj++)
+			priv_union(vjs_sets[vj][vs], punion);
+		priv_union(punion, vjs_sets[JAIL_MASTER_ANY][vs]);
 	}
+
+	priv_freeset(punion);
+
+	return (0);
 }
 
-/*
- * additional privileges needed by vjs_privsep -
- * will get waived in vjs_waive
- */
-static void
-vjs_add_initial(priv_set_t *pset, enum jail_gen_e jge)
+static priv_set_t *
+vjs_alloc(void)
 {
-	(void)jge;
+	priv_set_t *s;
 
-	/* for setgid/setuid */
-	AZ(priv_addset(pset, PRIV_PROC_SETID));
+	s = priv_allocset();
+	AN(s);
+	priv_emptyset(s);
+	return (s);
 }
 
-/*
- * if we are not yet privilege-aware already (ie we have been started
- * not-privilege aware with euid 0), we try to grab any privileges we
- * will need later.
- * We will reduce to least privileges in vjs_waive
- *
- * We need to become privilege-aware to avoid setuid resetting them.
- */
-
-static void
-vjs_setup(enum jail_gen_e jge)
+static int v_matchproto_(jail_init_f)
+vjs_init(char **args)
 {
-	priv_set_t *priv_all;
+	priv_set_t **sets;
+	int vj, vs;
 
-	if (!(priv_all = priv_allocset())) {
-		MGT_Complain(C_SECURITY,
-		    "Solaris Jail warning: "
-		    " vjs_setup - priv_allocset failed: errno=%d (%s)",
-		    errno, vstrerror(errno));
-		return;
+	if (args != NULL && *args != NULL) {
+		ARGV_ERR("-jsolaris takes no arguments.\n");
+		return (0);
 	}
 
-	priv_emptyset(priv_all);
+	/* init privset for vjs_setuid() */
+	vjs_proc_setid = priv_allocset();
+	AN(vjs_proc_setid);
+	priv_emptyset(vjs_proc_setid);
+	priv_setop_assert(priv_addset(vjs_proc_setid, PRIV_PROC_SETID));
 
-	vjs_add_inheritable(priv_all, jge);
-	vjs_add_effective(priv_all, jge);
-	vjs_add_permitted(priv_all, jge);
-	vjs_add_initial(priv_all, jge);
+	assert(JAIL_MASTER_ANY < JAIL_SUBPROC);
+	/* alloc privsets.
+	 * for master, anything but EFFECTIVE is shared
+	 */
+	for (vj = 0; vj < JAIL_SUBPROC; vj++)
+		for (vs = 0; vs < VJS_NSET; vs++) {
+			if (vj == JAIL_MASTER_ANY || vs == VJS_EFFECTIVE) {
+				vjs_sets[vj][vs] = vjs_alloc();
+				vjs_inverse[vj][vs] = vjs_alloc();
+			} else {
+				vjs_sets[vj][vs] =
+					vjs_sets[JAIL_MASTER_ANY][vs];
+				vjs_inverse[vj][vs] =
+					vjs_inverse[JAIL_MASTER_ANY][vs];
+			}
+		}
+
+	for (; vj < JAIL_LIMIT; vj++)
+		for (vs = 0; vs < VJS_NSET; vs++) {
+			vjs_sets[vj][vs] = vjs_alloc();
+			vjs_inverse[vj][vs] = vjs_alloc();
+		}
+
+	/* init from table */
+#define PRIV(name, mask, priv) vjs_add(vjs_sets[JAIL_ ## name], mask, priv);
+#include "mgt_jail_solaris_tbl.h"
+
+	/* SUBPROC implicit rules */
+	for (vj = JAIL_SUBPROC; vj < JAIL_LIMIT; vj++) {
+		sets = vjs_sets[vj];
+		priv_union(sets[VJS_EFFECTIVE], sets[VJS_PERMITTED]);
+		priv_union(sets[VJS_PERMITTED], sets[VJS_LIMIT]);
+		priv_union(sets[VJS_INHERITABLE], sets[VJS_LIMIT]);
+	}
+
+	vjs_master_rules();
+
+	/* MASTER implicit rules */
+	for (vj = 0; vj < JAIL_SUBPROC; vj++) {
+		sets = vjs_sets[vj];
+		priv_union(sets[VJS_EFFECTIVE], sets[VJS_PERMITTED]);
+		priv_union(sets[VJS_PERMITTED], sets[VJS_LIMIT]);
+		priv_union(sets[VJS_INHERITABLE], sets[VJS_LIMIT]);
+	}
+
+	/* attempt to enable privileges */
+	for (vs = VJS_PERMITTED; vs > VJS_EFFECTIVE; vs--)
+		setppriv_assert(setppriv(PRIV_ON, vjs_ptype[vs],
+		    vjs_sets[JAIL_MASTER_ANY][vs]));
 
-	/* try to get all possible privileges, expect EPERM here */
-	setppriv_assert(setppriv(PRIV_ON, PRIV_PERMITTED, priv_all));
-	setppriv_assert(setppriv(PRIV_ON, PRIV_EFFECTIVE, priv_all));
-	setppriv_assert(setppriv(PRIV_ON, PRIV_INHERITABLE, priv_all));
+	/* generate inverse */
+	for (vj = 0; vj < JAIL_LIMIT; vj++)
+		for (vs = 0; vs < VJS_NSET; vs++) {
+			priv_copyset(vjs_sets[vj][vs], vjs_inverse[vj][vs]);
+			priv_inverse(vjs_inverse[vj][vs]);
+		}
 
-	priv_freeset(priv_all);
+	vjs_master(JAIL_MASTER_LOW);
+
+	/* XXX LEAK: no _fini for priv_freeset() */
+	return (0);
 }
 
 static void
-vjs_privsep(enum jail_gen_e jge)
+vjs_waive(int jail)
 {
-	(void)jge;
+	priv_set_t **sets;
+	int i;
+
+	assert(jail >= 0);
+	assert(jail < JAIL_LIMIT);
 
+	sets = vjs_inverse[jail];
+
+	for (i = 0; i < VJS_NSET; i++)
+		AZ(setppriv(PRIV_OFF, vjs_ptype[i], sets[i]));
+}
+
+static void
+vjs_setuid(void)
+{
+	setppriv_assert(setppriv(PRIV_ON, PRIV_EFFECTIVE, vjs_proc_setid));
 	if (priv_ineffect(PRIV_PROC_SETID)) {
 		if (getgid() != mgt_param.gid)
 			XXXAZ(setgid(mgt_param.gid));
 		if (getuid() != mgt_param.uid)
 			XXXAZ(setuid(mgt_param.uid));
+		AZ(setppriv(PRIV_OFF, PRIV_EFFECTIVE, vjs_proc_setid));
+		AZ(setppriv(PRIV_OFF, PRIV_PERMITTED, vjs_proc_setid));
 	} else {
 		MGT_Complain(C_SECURITY,
 		    "Privilege %s missing, will not change uid/gid",
@@ -432,95 +439,27 @@ vjs_privsep(enum jail_gen_e jge)
 	}
 }
 
-/*
- * Waive most privileges in the child
- *
- * as of onnv_151a, we should end up with:
- *
- * > ppriv -v #pid of varnish child
- * PID:  .../varnishd ...
- * flags = PRIV_AWARE
- *      E: file_read,file_write,net_access
- *      I: none
- *      P: file_read,file_write,net_access,sys_resource
- *      L: file_read,file_write,net_access,sys_resource
- *
- * We should keep sys_resource in P in order to adjust our limits if we need to
- */
-
-static void
-vjs_waive(enum jail_gen_e jge)
-{
-	priv_set_t *effective, *inheritable, *permitted, *limited;
-
-	if (!(effective = priv_allocset()) ||
-	    !(inheritable = priv_allocset()) ||
-	    !(permitted = priv_allocset()) ||
-	    !(limited = priv_allocset())) {
-		MGT_Complain(C_SECURITY,
-		    "Solaris Jail warning: "
-		    " vjs_waive - priv_allocset failed: errno=%d (%s)",
-		    errno, vstrerror(errno));
-		return;
-	}
-
-	/*
-	 * inheritable and effective are distinct sets
-	 * effective is a subset of permitted
-	 * limit is the union of all
-	 */
-
-	priv_emptyset(inheritable);
-	vjs_add_inheritable(inheritable, jge);
-
-	priv_emptyset(effective);
-	vjs_add_effective(effective, jge);
-
-	priv_copyset(effective, permitted);
-	vjs_add_permitted(permitted, jge);
-
-	priv_copyset(inheritable, limited);
-	priv_union(permitted, limited);
-	/*
-	 * invert the sets and clear privileges such that setppriv will always
-	 * succeed
-	 */
-	priv_inverse(limited);
-	priv_inverse(permitted);
-	priv_inverse(effective);
-	priv_inverse(inheritable);
-
-	AZ(setppriv(PRIV_OFF, PRIV_LIMIT, limited));
-	AZ(setppriv(PRIV_OFF, PRIV_PERMITTED, permitted));
-	AZ(setppriv(PRIV_OFF, PRIV_EFFECTIVE, effective));
-	AZ(setppriv(PRIV_OFF, PRIV_INHERITABLE, inheritable));
-
-	priv_freeset(limited);
-	priv_freeset(permitted);
-	priv_freeset(effective);
-	priv_freeset(inheritable);
-}
-
 static void v_matchproto_(jail_subproc_f)
 vjs_subproc(enum jail_subproc_e jse)
 {
-	enum jail_gen_e jge = jail_subproc_gen(jse);
-	vjs_setup(jge);
-	vjs_privsep(jge);
-	vjs_waive(jge);
+	vjs_setuid();
+	vjs_waive(jse);
 }
 
 static void v_matchproto_(jail_master_f)
 vjs_master(enum jail_master_e jme)
 {
-	enum jail_gen_e jge = jail_master_gen(jme);
-	(void)jge;
-/*
-	if (jme == JAILG_MASTER_HIGH)
-		AZ(seteuid(0));
-	else
-		AZ(seteuid(vju_uid));
-*/
+	priv_set_t **sets;
+	int i;
+
+	assert(jme < JAIL_SUBPROC);
+
+	sets = vjs_sets[jme];
+
+	i = VJS_EFFECTIVE;
+	setppriv_assert(setppriv(PRIV_ON, vjs_ptype[i], sets[i]));
+
+	vjs_waive(jme);
 }
 
 const struct jail_tech jail_tech_solaris = {
diff --git a/bin/varnishd/mgt/mgt_jail_solaris_tbl.h b/bin/varnishd/mgt/mgt_jail_solaris_tbl.h
new file mode 100644
index 000000000..dfe912094
--- /dev/null
+++ b/bin/varnishd/mgt/mgt_jail_solaris_tbl.h
@@ -0,0 +1,93 @@
+/*-
+ * Copyright 2020 UPLEX - Nils Goroll Systemoptimierung
+ * All rights reserved.
+ *
+ * Author: Nils Goroll <nils.goroll at uplex.de>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * definition of privileges to use for the different Varnish Jail (VJ)
+ * levels
+ */
+#define E VJS_MASK(VJS_EFFECTIVE)	// as-is
+#define I VJS_MASK(VJS_INHERITABLE)	// as-is
+#define P VJS_MASK(VJS_PERMITTED)	// joined with effective
+#define L VJS_MASK(VJS_LIMIT)		// joined with of all the above
+
+/* ------------------------------------------------------------
+ * MASTER
+ * - only MASTER_EFFECTIVE is per JAIL state
+ * - other priv sets are shared across all MASTER_* JAIL states
+ *
+ * MASTER implicit rules (vjs_master_rules())
+ * - INHERITABLE and PERMITTED joined from SUBPROC*
+ * - implicit rules from above
+ */
+PRIV(MASTER_LOW,	E	, PRIV_PROC_EXEC)	// XXX fork
+PRIV(MASTER_LOW,	E	, PRIV_PROC_FORK)	// XXX fork
+PRIV(MASTER_LOW,	E	, "file_write")	// XXX vcl_boot
+PRIV(MASTER_LOW,	E	, "file_read")	// XXX library open
+PRIV(MASTER_LOW,	E	, "net_access")
+
+PRIV(MASTER_FILE,	E	, PRIV_PROC_EXEC)	// XXX rm -rf in shm
+PRIV(MASTER_FILE,	E	, PRIV_PROC_FORK)	// XXX rm -rf in shm
+PRIV(MASTER_FILE,	E	, "file_read")
+PRIV(MASTER_FILE,	E	, "file_write")
+
+PRIV(MASTER_STORAGE,	E	, "file_read")
+PRIV(MASTER_STORAGE,	E	, "file_write")
+
+PRIV(MASTER_PRIVPORT,	E	, "file_write")	// bind(AF_UNIX)
+PRIV(MASTER_PRIVPORT,	E	, "net_access")
+PRIV(MASTER_PRIVPORT,	E	, PRIV_NET_PRIVADDR)
+
+PRIV(MASTER_KILL,	E	, PRIV_PROC_OWNER)
+
+/* ------------------------------------------------------------
+ * SUBPROC
+ */
+PRIV(SUBPROC_VCC,	E	, PRIV_PROC_SETID)	// waived after setuid
+PRIV(SUBPROC_VCC,	E	, "file_read")
+PRIV(SUBPROC_VCC,	E	, "file_write")
+
+PRIV(SUBPROC_CC,	E	, PRIV_PROC_SETID)	// waived after setuid
+PRIV(SUBPROC_CC,	E|I	, PRIV_PROC_EXEC)
+PRIV(SUBPROC_CC,	E|I	, PRIV_PROC_FORK)
+PRIV(SUBPROC_CC,	E|I	, "file_read")
+PRIV(SUBPROC_CC,	E|I	, "file_write")
+
+PRIV(SUBPROC_VCLLOAD,	E	, PRIV_PROC_SETID)	// waived after setuid
+PRIV(SUBPROC_VCLLOAD,	E	, "file_read")
+
+PRIV(SUBPROC_WORKER,	E	, PRIV_PROC_SETID)	// waived after setuid
+PRIV(SUBPROC_WORKER,	E	, "net_access")
+PRIV(SUBPROC_WORKER,	E	, "file_read")
+PRIV(SUBPROC_WORKER,	E	, "file_write")
+PRIV(SUBPROC_WORKER,	P	, PRIV_PROC_INFO)	/* vmod_unix */
+
+#undef E
+#undef I
+#undef P
+#undef L
+#undef PRIV


More information about the varnish-commit mailing list