[svn-commits] tilghman: trunk r114904 - /trunk/apps/

SVN commits to the Digium repositories svn-commits at lists.digium.com
Wed Apr 30 14:21:04 CDT 2008


Author: tilghman
Date: Wed Apr 30 14:21:04 2008
New Revision: 114904

URL: http://svn.digium.com/view/asterisk?view=rev&rev=114904
Log:
Lock around variables retrieved, and copy the values, if they stay persistent,
since another thread could remove them.
(closes issue #12541)
 Reported by: snuffy
 Patches: 
       bug_12156_apps.diff uploaded by snuffy (license 35)
       Several additional changes by me

Modified:
    trunk/apps/app_chanspy.c
    trunk/apps/app_externalivr.c
    trunk/apps/app_macro.c
    trunk/apps/app_meetme.c
    trunk/apps/app_minivm.c
    trunk/apps/app_morsecode.c
    trunk/apps/app_speech_utils.c
    trunk/apps/app_stack.c
    trunk/apps/app_voicemail.c
    trunk/apps/app_while.c

Modified: trunk/apps/app_chanspy.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_chanspy.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_chanspy.c (original)
+++ trunk/apps/app_chanspy.c Wed Apr 30 14:21:04 2008
@@ -596,12 +596,15 @@
 
 	if (ast_test_flag(flags, OPTION_EXIT)) {
 		const char *c;
-		if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT")))
+		ast_channel_lock(chan);
+		if ((c = pbx_builtin_getvar_helper(chan, "SPY_EXIT_CONTEXT"))) {
 			ast_copy_string(exitcontext, c, sizeof(exitcontext));
-		else if (!ast_strlen_zero(chan->macrocontext))
+		} else if (!ast_strlen_zero(chan->macrocontext)) {
 			ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
-		else
+		} else {
 			ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
+		}
+		ast_channel_unlock(chan);
 	}
 
 	ast_mutex_init(&chanspy_ds.lock);

Modified: trunk/apps/app_externalivr.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_externalivr.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_externalivr.c (original)
+++ trunk/apps/app_externalivr.c Wed Apr 30 14:21:04 2008
@@ -251,10 +251,13 @@
 			break;
 		}
 		
-		value = pbx_builtin_getvar_helper(chan, variable);
-		if(!value)
+		ast_channel_lock(chan);
+		if (!(value = pbx_builtin_getvar_helper(chan, variable))) {
 			value = "";
+		}
+
 		ast_str_append(&newstring, 0, "%s=%s,", variable, value);
+		ast_channel_unlock(chan);
 		ast_copy_string(outbuf, newstring->str, outbuflen);
 	}
 };

Modified: trunk/apps/app_macro.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_macro.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_macro.c (original)
+++ trunk/apps/app_macro.c Wed Apr 30 14:21:04 2008
@@ -165,20 +165,24 @@
 	}
 
 	/* does the user want a deeper rabbit hole? */
-	s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION");
-	if (s)
+	ast_channel_lock(chan);
+	if ((s = pbx_builtin_getvar_helper(chan, "MACRO_RECURSION"))) {
 		sscanf(s, "%d", &maxdepth);
-
+	}
+	
 	/* Count how many levels deep the rabbit hole goes */
-	s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH");
-	if (s)
+	if ((s = pbx_builtin_getvar_helper(chan, "MACRO_DEPTH"))) {
 		sscanf(s, "%d", &depth);
+	}
+	
 	/* Used for detecting whether to return when a Macro is called from another Macro after hangup */
 	if (strcmp(chan->exten, "h") == 0)
 		pbx_builtin_setvar_helper(chan, "MACRO_IN_HANGUP", "1");
-	inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP");
-	if (!ast_strlen_zero(inhangupc))
+	
+	if ((inhangupc = pbx_builtin_getvar_helper(chan, "MACRO_IN_HANGUP"))) {
 		sscanf(inhangupc, "%d", &inhangup);
+	}
+	ast_channel_unlock(chan);
 
 	if (depth >= maxdepth) {
 		ast_log(LOG_ERROR, "Macro():  possible infinite loop detected.  Returning early.\n");
@@ -247,17 +251,19 @@
 	ast_copy_string(chan->context, fullmacro, sizeof(chan->context));
 	chan->priority = 1;
 
+	ast_channel_lock(chan);
 	while((cur = strsep(&rest, ",")) && (argc < MAX_ARGS)) {
 		const char *s;
   		/* Save copy of old arguments if we're overwriting some, otherwise
 	   	let them pass through to the other macro */
   		snprintf(varname, sizeof(varname), "ARG%d", argc);
-		s = pbx_builtin_getvar_helper(chan, varname);
-		if (s)
+		if ((s = pbx_builtin_getvar_helper(chan, varname))) {
 			oldargs[argc] = ast_strdup(s);
+		}
 		pbx_builtin_setvar_helper(chan, varname, cur);
 		argc++;
 	}
+	ast_channel_unlock(chan);
 	autoloopflag = ast_test_flag(chan, AST_FLAG_IN_AUTOLOOP);
 	ast_set_flag(chan, AST_FLAG_IN_AUTOLOOP);
 	while(ast_exists_extension(chan, chan->context, chan->exten, chan->priority, chan->cid.cid_num)) {
@@ -431,6 +437,7 @@
 			/* Copy the extension, so long as we're not in softhangup, where we could be given an asyncgoto */
 			const char *offsets;
 			ast_copy_string(chan->exten, oldexten, sizeof(chan->exten));
+			ast_channel_lock(chan);
 			if ((offsets = pbx_builtin_getvar_helper(chan, "MACRO_OFFSET"))) {
 				/* Handle macro offset if it's set by checking the availability of step n + offset + 1, otherwise continue
 			   	normally if there is any problem */
@@ -440,6 +447,7 @@
 					}
 				}
 			}
+			ast_channel_unlock(chan);
 		}
 	}
 

Modified: trunk/apps/app_meetme.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_meetme.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_meetme.c (original)
+++ trunk/apps/app_meetme.c Wed Apr 30 14:21:04 2008
@@ -374,8 +374,8 @@
 	pthread_t recordthread;                 /*!< thread for recording */
 	ast_mutex_t recordthreadlock;           /*!< control threads trying to start recordthread */
 	pthread_attr_t attr;                    /*!< thread attribute */
-	const char *recordingfilename;          /*!< Filename to record the Conference into */
-	const char *recordingformat;            /*!< Format to record the Conference in */
+	char *recordingfilename;                /*!< Filename to record the Conference into */
+	char *recordingformat;                  /*!< Format to record the Conference in */
 	char pin[MAX_PIN];                      /*!< If protected by a PIN */
 	char pinadmin[MAX_PIN];                 /*!< If protected by a admin PIN */
 	char uniqueid[32];
@@ -1350,7 +1350,12 @@
 		ast_hangup(conf->chan);
 	if (conf->fd >= 0)
 		close(conf->fd);
-
+	if (conf->recordingfilename) {
+		ast_free(conf->recordingfilename);
+	}
+	if (conf->recordingformat) {
+		ast_free(conf->recordingformat);
+	}
 	ast_mutex_destroy(&conf->playlock);
 	ast_mutex_destroy(&conf->listenlock);
 	ast_mutex_destroy(&conf->recordthreadlock);
@@ -1507,8 +1512,8 @@
 	struct timeval now;
 	struct ast_dsp *dsp = NULL;
 	struct ast_app *app;
-	const char *agifile;
-	const char *agifiledefault = "conf-background.agi";
+	char *agifile;
+	const char *agifiledefault = "conf-background.agi", *tmp;
 	char meetmesecs[30] = "";
 	char exitcontext[AST_MAX_CONTEXT] = "";
 	char recordingtmp[AST_MAX_EXTENSION] = "";
@@ -1575,12 +1580,22 @@
  					play_warning = warning_freq = 0;
  			}
  		}
- 			
- 		var = pbx_builtin_getvar_helper(chan, "CONF_LIMIT_WARNING_FILE");
+ 		
+		ast_channel_lock(chan);
+		if ((var = pbx_builtin_getvar_helper(chan, "CONF_LIMIT_WARNING_FILE"))) {
+			var = ast_strdupa(var);
+		}
+		ast_channel_unlock(chan);
+
  		warning_sound = var ? var : "timeleft";
  		
- 		var = pbx_builtin_getvar_helper(chan, "CONF_LIMIT_TIMEOUT_FILE");
- 		end_sound = var ? var : NULL;
+		ast_channel_lock(chan);
+		if ((var = pbx_builtin_getvar_helper(chan, "CONF_LIMIT_TIMEOUT_FILE"))) {
+			var = ast_strdupa(var);
+		}
+		ast_channel_unlock(chan);
+ 		
+		end_sound = var ? var : NULL;
  			
  		/* undo effect of S(x) in case they are both used */
  		calldurationlimit = 0;
@@ -1608,15 +1623,21 @@
 	
 	if (confflags & CONFFLAG_RECORDCONF) {
 		if (!conf->recordingfilename) {
-			conf->recordingfilename = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE");
+			const char *var;
+			ast_channel_lock(chan);
+			if ((var = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFILE"))) {
+				conf->recordingfilename = ast_strdup(var);
+			}
+			if ((var = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT"))) {
+				conf->recordingformat = ast_strdup(var);
+			}
+			ast_channel_unlock(chan);
 			if (!conf->recordingfilename) {
 				snprintf(recordingtmp, sizeof(recordingtmp), "meetme-conf-rec-%s-%s", conf->confno, chan->uniqueid);
-				conf->recordingfilename = ast_strdupa(recordingtmp);
+				conf->recordingfilename = ast_strdup(recordingtmp);
 			}
-			conf->recordingformat = pbx_builtin_getvar_helper(chan, "MEETME_RECORDINGFORMAT");
 			if (!conf->recordingformat) {
-				ast_copy_string(recordingtmp, "wav", sizeof(recordingtmp));
-				conf->recordingformat = ast_strdupa(recordingtmp);
+				conf->recordingformat = ast_strdup("wav");
 			}
 			ast_verb(4, "Starting recording of MeetMe Conference %s into file %s.%s.\n",
 				    conf->confno, conf->recordingfilename, conf->recordingformat);
@@ -1736,12 +1757,15 @@
 	pbx_builtin_setvar_helper(chan, "MEETMEUNIQUEID", conf->uniqueid);
 
 	if (confflags & CONFFLAG_EXIT_CONTEXT) {
-		if ((agifile = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT"))) 
-			ast_copy_string(exitcontext, agifile, sizeof(exitcontext));
-		else if (!ast_strlen_zero(chan->macrocontext)) 
+		ast_channel_lock(chan);
+		if ((tmp = pbx_builtin_getvar_helper(chan, "MEETME_EXIT_CONTEXT"))) {
+			ast_copy_string(exitcontext, tmp, sizeof(exitcontext));
+		} else if (!ast_strlen_zero(chan->macrocontext)) {
 			ast_copy_string(exitcontext, chan->macrocontext, sizeof(exitcontext));
-		else
+		} else {
 			ast_copy_string(exitcontext, chan->context, sizeof(exitcontext));
+		}
+		ast_channel_unlock(chan);
 	}
 
 	if (!(confflags & (CONFFLAG_QUIET | CONFFLAG_NOONLYPERSON))) {
@@ -1932,10 +1956,14 @@
 		/* Get name of AGI file to run from $(MEETME_AGI_BACKGROUND)
 		   or use default filename of conf-background.agi */
 
-		agifile = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND");
-		if (!agifile)
-			agifile = agifiledefault;
-
+		ast_channel_lock(chan);
+		if ((tmp = pbx_builtin_getvar_helper(chan, "MEETME_AGI_BACKGROUND"))) {
+			agifile = ast_strdupa(tmp);
+		} else {
+			agifile = ast_strdupa(agifiledefault);
+		}
+		ast_channel_unlock(chan);
+		
 		if (user->zapchannel) {
 			/*  Set CONFMUTE mode on Zap channel to mute DTMF tones */
 			x = 1;
@@ -1944,8 +1972,7 @@
 		/* Find a pointer to the agi app and execute the script */
 		app = pbx_findapp("agi");
 		if (app) {
-			char *s = ast_strdupa(agifile);
-			ret = pbx_exec(chan, app, s);
+			ret = pbx_exec(chan, app, agifile);
 		} else {
 			ast_log(LOG_WARNING, "Could not find application (agi)\n");
 			ret = -2;

Modified: trunk/apps/app_minivm.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_minivm.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_minivm.c (original)
+++ trunk/apps/app_minivm.c Wed Apr 30 14:21:04 2008
@@ -1412,7 +1412,12 @@
 
 
 	/* Read counter if available */
-	counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER");
+	ast_channel_lock(chan);
+	if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
+		counter = ast_strdupa(counter);
+	}
+	ast_channel_unlock(chan);
+
 	if (ast_strlen_zero(counter)) {
 		ast_debug(2, "-_-_- MVM_COUNTER not found\n");
 	} else {
@@ -1659,14 +1664,24 @@
 		pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
 		return -1;
 	}
-	
-	filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME");
-	format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT");
-	duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION");
+
+	ast_channel_lock(chan);
+	if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
+		filename = ast_strdupa(filename);
+	}
+	ast_channel_unlock(chan);
 	/* Notify of new message to e-mail and pager */
 	if (!ast_strlen_zero(filename)) {
+		ast_channel_lock(chan);	
+		if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
+			format = ast_strdupa(format);
+		}
+		if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
+			duration_string = ast_strdupa(duration_string);
+		}
+		ast_channel_unlock(chan);
 		res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
-	};
+	}
 
 	pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
 
@@ -1928,10 +1943,13 @@
 	int res = 0;
 	char filename[BUFSIZ];
 		
-	if (!ast_strlen_zero(data))
+	if (!ast_strlen_zero(data)) {
 		ast_copy_string(filename, (char *) data, sizeof(filename));
-	else
+	} else {
+		ast_channel_lock(chan);
 		ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
+		ast_channel_unlock(chan);
+	}
 
 	if (ast_strlen_zero(filename)) {
 		ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");

Modified: trunk/apps/app_morsecode.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_morsecode.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_morsecode.c (original)
+++ trunk/apps/app_morsecode.c Wed Apr 30 14:21:04 2008
@@ -111,16 +111,20 @@
 	}
 
 	/* Use variable MORESEDITLEN, if set (else 80) */
+	ast_channel_lock(chan);
 	ditlenc = pbx_builtin_getvar_helper(chan, "MORSEDITLEN");
 	if (ast_strlen_zero(ditlenc) || (sscanf(ditlenc, "%d", &ditlen) != 1)) {
 		ditlen = 80;
 	}
+	ast_channel_unlock(chan);
 
 	/* Use variable MORSETONE, if set (else 800) */
+	ast_channel_lock(chan);
 	tonec = pbx_builtin_getvar_helper(chan, "MORSETONE");
 	if (ast_strlen_zero(tonec) || (sscanf(tonec, "%d", &tone) != 1)) {
 		tone = 800;
 	}
+	ast_channel_unlock(chan);
 
 	for (digit = data; *digit; digit++) {
 		int digit2 = *digit;

Modified: trunk/apps/app_speech_utils.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_speech_utils.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_speech_utils.c (original)
+++ trunk/apps/app_speech_utils.c Wed Apr 30 14:21:04 2008
@@ -559,9 +559,11 @@
 	}
 
 	/* See if the maximum DTMF length variable is set... we use a variable in case they want to carry it through their entire dialplan */
-	if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2))
+	ast_channel_lock(chan);
+	if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_MAXLEN")) && !ast_strlen_zero(tmp2)) {
 		max_dtmf_len = atoi(tmp2);
-
+	}
+	
 	/* See if a terminator is specified */
 	if ((tmp2 = pbx_builtin_getvar_helper(chan, "SPEECH_DTMF_TERMINATOR"))) {
 		if (ast_strlen_zero(tmp2))
@@ -569,6 +571,7 @@
 		else
 			dtmf_terminator = tmp2[0];
 	}
+	ast_channel_unlock(chan);
 
 	/* Before we go into waiting for stuff... make sure the structure is ready, if not - start it again */
 	if (speech->state == AST_SPEECH_STATE_NOT_READY || speech->state == AST_SPEECH_STATE_DONE) {

Modified: trunk/apps/app_stack.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_stack.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_stack.c (original)
+++ trunk/apps/app_stack.c Wed Apr 30 14:21:04 2008
@@ -351,8 +351,11 @@
 	frame = AST_LIST_FIRST(oldlist);
 	AST_LIST_TRAVERSE(&frame->varshead, variables, entries) {
 		if (!strcmp(data, ast_var_name(variables))) {
-			const char *tmp = pbx_builtin_getvar_helper(chan, data);
+			const char *tmp;
+			ast_channel_lock(chan);
+			tmp = pbx_builtin_getvar_helper(chan, data);
 			ast_copy_string(buf, S_OR(tmp, ""), len);
+			ast_channel_unlock(chan);
 			break;
 		}
 	}

Modified: trunk/apps/app_voicemail.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_voicemail.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_voicemail.c (original)
+++ trunk/apps/app_voicemail.c Wed Apr 30 14:21:04 2008
@@ -3522,7 +3522,11 @@
 	if (tmpptr)
 		*tmpptr++ = '\0';
 
-	category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
+	ast_channel_lock(chan);
+	if ((category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY"))) {
+		category = ast_strdupa(category);
+	}
+	ast_channel_unlock(chan);
 
 	ast_debug(3, "Before find_user\n");
 	if (!(vmu = find_user(&svm, context, ext))) {
@@ -4741,8 +4745,14 @@
 {
 	char todir[PATH_MAX], fn[PATH_MAX], ext_context[PATH_MAX], *stringp;
 	int newmsgs = 0, oldmsgs = 0;
-	const char *category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
+	const char *category;
 	char *myserveremail = serveremail;
+
+	ast_channel_lock(chan);
+	if ((category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY"))) {
+		category = ast_strdupa(category);
+	}
+	ast_channel_unlock(chan);
 
 	make_dir(todir, sizeof(todir), vmu->context, vmu->mailbox, "INBOX");
 	make_file(fn, sizeof(fn), todir, msgnum);

Modified: trunk/apps/app_while.c
URL: http://svn.digium.com/view/asterisk/trunk/apps/app_while.c?view=diff&rev=114904&r1=114903&r2=114904
==============================================================================
--- trunk/apps/app_while.c (original)
+++ trunk/apps/app_while.c Wed Apr 30 14:21:04 2008
@@ -184,22 +184,19 @@
 	memset(my_name, 0, size);
 	snprintf(my_name, size, "%s_%s_%d", chan->context, chan->exten, chan->priority);
 	
-	if (ast_strlen_zero(label)) {
-		if (end) 
-			label = used_index;
-		else if (!(label = pbx_builtin_getvar_helper(chan, my_name))) {
-			label = new_index;
-			pbx_builtin_setvar_helper(chan, my_name, label);
-		}
-		
-	}
-	
+	ast_channel_lock(chan);
+	if (end) {
+		label = used_index;
+	} else if (!(label = pbx_builtin_getvar_helper(chan, my_name))) {
+		label = new_index;
+		pbx_builtin_setvar_helper(chan, my_name, label);
+	}
 	snprintf(varname, VAR_SIZE, "%s_%s", prefix, label);
-	while_pri = pbx_builtin_getvar_helper(chan, varname);
-	
 	if ((while_pri = pbx_builtin_getvar_helper(chan, varname)) && !end) {
+		while_pri = ast_strdupa(while_pri);
 		snprintf(end_varname,VAR_SIZE,"END_%s",varname);
 	}
+	ast_channel_unlock(chan);
 	
 
 	if ((!end && !pbx_checkcondition(condition)) || (end == 2)) {
@@ -208,7 +205,8 @@
 		pbx_builtin_setvar_helper(chan, varname, NULL);
 		pbx_builtin_setvar_helper(chan, my_name, NULL);
 		snprintf(end_varname,VAR_SIZE,"END_%s",varname);
-		if ((goto_str=pbx_builtin_getvar_helper(chan, end_varname))) {
+		ast_channel_lock(chan);
+		if ((goto_str = pbx_builtin_getvar_helper(chan, end_varname))) {
 			ast_parseable_goto(chan, goto_str);
 			pbx_builtin_setvar_helper(chan, end_varname, NULL);
 		} else {
@@ -220,6 +218,7 @@
 				ast_log(LOG_WARNING, "Couldn't find matching EndWhile? (While at %s@%s priority %d)\n", chan->context, chan->exten, chan->priority);
 			}
 		}
+		ast_channel_unlock(chan);
 		return res;
 	}
 
@@ -230,7 +229,7 @@
 		memset(goto_str, 0, size);
 		snprintf(goto_str, size, "%s,%s,%d", chan->context, chan->exten, chan->priority);
 		pbx_builtin_setvar_helper(chan, varname, goto_str);
-	} 
+	}
 
 	else if (end && while_pri) {
 		/* END of loop */




More information about the svn-commits mailing list