diff --git a/src/sbbs3/GNUmakefile b/src/sbbs3/GNUmakefile
index 4cd27ca3a9c783c9aa84ccec69833fea03fc1315..189d2788abe7284e7e2c5e209ef426da7253a4c1 100644
--- a/src/sbbs3/GNUmakefile
+++ b/src/sbbs3/GNUmakefile
@@ -104,9 +104,10 @@ CON_OBJS	= $(LIBODIR)/sbbscon.o $(LIBODIR)/conwrap.o \
 CON_LDFLAGS	= -lftpsrvr -lwebsrvr -lmailsrvr -lservices
 FTP_OBJS	= $(LIBODIR)/ftpsrvr.o
 MAIL_OBJS	= $(LIBODIR)/mailsrvr.o $(LIBODIR)/mxlookup.o \
- 		  $(LIBODIR)/mime.o $(LIBODIR)/base64.o $(LIBODIR)/ini_file.o
+ 		  $(LIBODIR)/mime.o $(LIBODIR)/base64.o $(LIBODIR)/ini_file.o \
+		  $(LIBODIR)/str_list.o
 WEB_OBJS	= $(LIBODIR)/websrvr.o $(LIBODIR)/sockwrap.o $(LIBODIR)/base64.o
-SERVICE_OBJS	= $(LIBODIR)/services.o $(LIBODIR)/ini_file.o
+SERVICE_OBJS	= $(LIBODIR)/services.o $(LIBODIR)/ini_file.o $(LIBODIR)/str_list.o
 
 MONO_OBJS	= $(CON_OBJS) $(FTP_OBJS) $(WEB_OBJS) \
 			$(MAIL_OBJS) $(SERVICE_OBJS)
diff --git a/src/sbbs3/objects.mk b/src/sbbs3/objects.mk
index 8b7566e06b417d5cb57fa3c5a262347af4c7d31d..992dc061d71ecb03eb4f2eb92064c1fc3cac0392 100644
--- a/src/sbbs3/objects.mk
+++ b/src/sbbs3/objects.mk
@@ -93,6 +93,7 @@ OBJS	=	$(LIBODIR)$(SLASH)ansiterm.$(OFILE)\
 			$(LIBODIR)$(SLASH)sockopts.$(OFILE)\
 			$(LIBODIR)$(SLASH)sortdir.$(OFILE)\
 			$(LIBODIR)$(SLASH)str.$(OFILE)\
+			$(LIBODIR)$(SLASH)str_list.$(OFILE)\
 			$(LIBODIR)$(SLASH)str_util.$(OFILE)\
 			$(LIBODIR)$(SLASH)telgate.$(OFILE)\
 			$(LIBODIR)$(SLASH)telnet.$(OFILE)\
diff --git a/src/xpdev/ini_file.c b/src/xpdev/ini_file.c
index 0321fc4a595e5c9b68346c5eff1fe22d19647126..d8a78050925450eb367f420e927e7928712b4581 100644
--- a/src/xpdev/ini_file.c
+++ b/src/xpdev/ini_file.c
@@ -8,7 +8,7 @@
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
  *																			*
- * Copyright 2003 Rob Swindell - http://www.synchro.net/copyright.html		*
+ * Copyright 2004 Rob Swindell - http://www.synchro.net/copyright.html		*
  *																			*
  * This library is free software; you can redistribute it and/or			*
  * modify it under the terms of the GNU Lesser General Public License		*
@@ -40,6 +40,7 @@
 #include <ctype.h>		/* isdigit */
 #include "sockwrap.h"	/* inet_addr */
 #include "ini_file.h"
+#include "str_list.h"	/* strList functions */
 
 #define INI_MAX_LINE_LEN	256		/* Maximum length of entire line, includes '\0' */
 
@@ -137,7 +138,6 @@ char** iniGetStringList(FILE* fp, const char* section, const char* key
 	char*	value;
 	char	buf[INI_MAX_VALUE_LEN];
 	char**	lp;
-	char**	np;
 	char*	token;
 	char	list[INI_MAX_VALUE_LEN];
 	ulong	items=0;
@@ -153,32 +153,18 @@ char** iniGetStringList(FILE* fp, const char* section, const char* key
 	token=strtok(list,sep);
 	while(token!=NULL) {
 		truncsp(token);
-		if((np=realloc(lp,sizeof(char*)*(items+2)))==NULL)
+		if(strListAdd(&lp,token,items++)==NULL)
 			break;
-		lp=np;
-		if((lp[items]=malloc(strlen(token)+1))==NULL)
-			break;
-		strcpy(lp[items++],token);
 		token=strtok(NULL,sep);
 	}
 
-	lp[items]=NULL;	/* terminate list */
-
 	return(lp);
 }
 
 void* iniFreeStringList(char** list)
 {
-	ulong	i;
-
-	if(list==NULL)
-		return(NULL);
-
-	for(i=0;list[i]!=NULL;i++)
-		free(list[i]);
-
-	free(list);
-	return(NULL);
+	strListFree(&list);
+	return(list);
 }
 
 void* iniFreeNamedStringList(named_string_t** list)
@@ -205,7 +191,6 @@ char** iniGetSectionList(FILE* fp, const char* prefix)
 	char*	p;
 	char*	tp;
 	char**	lp;
-	char**	np;
 	char	str[INI_MAX_LINE_LEN];
 	ulong	items=0;
 
@@ -234,16 +219,10 @@ char** iniGetSectionList(FILE* fp, const char* prefix)
 		if(prefix!=NULL)
 			if(strnicmp(p,prefix,strlen(prefix))!=0)
 				continue;
-		if((np=realloc(lp,sizeof(char*)*(items+2)))==NULL)
+		if(strListAdd(&lp,p,items++)==NULL)
 			break;
-		lp=np;
-		if((lp[items]=malloc(strlen(p)+1))==NULL)
-			break;
-		strcpy(lp[items++],p);
 	}
 
-	lp[items]=NULL;	/* terminate list */
-
 	return(lp);
 }
 
@@ -252,7 +231,6 @@ char** iniGetKeyList(FILE* fp, const char* section)
 	char*	p;
 	char*	tp;
 	char**	lp;
-	char**	np;
 	char	str[INI_MAX_LINE_LEN];
 	ulong	items=0;
 
@@ -283,16 +261,10 @@ char** iniGetKeyList(FILE* fp, const char* section)
 			continue;
 		*tp=0;
 		truncsp(p);
-		if((np=realloc(lp,sizeof(char*)*(items+2)))==NULL)
-			break;
-		lp=np;
-		if((lp[items]=malloc(strlen(p)+1))==NULL)
+		if(strListAdd(&lp,p,items++)==NULL)
 			break;
-		strcpy(lp[items++],p);
 	}
 
-	lp[items]=NULL;	/* terminate list */
-
 	return(lp);
 }
 
diff --git a/src/xpdev/str_list.c b/src/xpdev/str_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..1299f9af564e613cb63c34ff1859d5e8f81f250d
--- /dev/null
+++ b/src/xpdev/str_list.c
@@ -0,0 +1,85 @@
+/* str_list.c */
+
+/* Functions to deal with NULL-terminated string lists */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2004 Rob Swindell - http://www.synchro.net/copyright.html		*
+ *																			*
+ * This library is free software; you can redistribute it and/or			*
+ * modify it under the terms of the GNU Lesser General Public License		*
+ * as published by the Free Software Foundation; either version 2			*
+ * of the License, or (at your option) any later version.					*
+ * See the GNU Lesser General Public License for more details: lgpl.txt or	*
+ * http://www.fsf.org/copyleft/lesser.html									*
+ *																			*
+ * Anonymous FTP access to the most recent released source is available at	*
+ * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
+ *																			*
+ * Anonymous CVS access to the development source and modification history	*
+ * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
+ * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
+ *     (just hit return, no password is necessary)							*
+ * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
+ *																			*
+ * For Synchronet coding style and modification guidelines, see				*
+ * http://www.synchro.net/source.html										*
+ *																			*
+ * You are encouraged to submit any modifications (preferably in Unix diff	*
+ * format) via e-mail to mods@synchro.net									*
+ *																			*
+ * Note: If this box doesn't appear square, then you need to fix your tabs.	*
+ ****************************************************************************/
+
+#include <stdlib.h>		/* malloc */
+#include "str_list.h"
+
+size_t strListCount(char** list)
+{
+	size_t i;
+
+	if(list==NULL)
+		return(0);
+
+	for(i=0;list[i]!=NULL;i++)
+		;
+
+	return(i);
+}
+
+char** strListAdd(char*** list, char* str, size_t count)
+{
+	char**	lp;
+
+	if(!count)
+		count=strListCount(*list);
+
+	if((lp=realloc(*list,sizeof(char*)*(count+2)))==NULL)
+		return(NULL);
+
+	*list=lp;
+	if((lp[count]=malloc(strlen(str)+1))==NULL)
+		return(NULL);
+
+	strcpy(lp[count++],str);
+	lp[count]=NULL;	/* terminate list */
+
+	return(lp);
+}
+
+void strListFree(char*** list)
+{
+	size_t i;
+
+	if(*list!=NULL) {
+
+		for(i=0;*list[i]!=NULL;i++)
+			free(*list[i]);
+
+		free(*list);
+	}
+}
diff --git a/src/xpdev/str_list.h b/src/xpdev/str_list.h
new file mode 100644
index 0000000000000000000000000000000000000000..351aa78d1d5253c3c1709c79fdb3c08b6a048dd9
--- /dev/null
+++ b/src/xpdev/str_list.h
@@ -0,0 +1,62 @@
+/* str_list.h */
+
+/* Functions to deal with NULL-terminated string lists */
+
+/* $Id$ */
+
+/****************************************************************************
+ * @format.tab-size 4		(Plain Text/Source Code File Header)			*
+ * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
+ *																			*
+ * Copyright 2004 Rob Swindell - http://www.synchro.net/copyright.html		*
+ *																			*
+ * This library is free software; you can redistribute it and/or			*
+ * modify it under the terms of the GNU Lesser General Public License		*
+ * as published by the Free Software Foundation; either version 2			*
+ * of the License, or (at your option) any later version.					*
+ * See the GNU Lesser General Public License for more details: lgpl.txt or	*
+ * http://www.fsf.org/copyleft/lesser.html									*
+ *																			*
+ * Anonymous FTP access to the most recent released source is available at	*
+ * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
+ *																			*
+ * Anonymous CVS access to the development source and modification history	*
+ * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
+ * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
+ *     (just hit return, no password is necessary)							*
+ * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
+ *																			*
+ * For Synchronet coding style and modification guidelines, see				*
+ * http://www.synchro.net/source.html										*
+ *																			*
+ * You are encouraged to submit any modifications (preferably in Unix diff	*
+ * format) via e-mail to mods@synchro.net									*
+ *																			*
+ * Note: If this box doesn't appear square, then you need to fix your tabs.	*
+ ****************************************************************************/
+
+#ifndef _STR_LIST_H
+#define _STR_LIST_H
+
+#include "gen_defs.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/* Pass a pointer to a string list, the string to add, and the current list count if known */
+/* (or 0 if unknown) */
+/* Returns the updated list or NULL on error */
+char**	strListAdd(char*** list, char* str, size_t count);
+
+/* Count the number of strings in the list and returns the count */
+size_t	strListCount(char** list);
+
+/* Frees the strings in the list (and the list itself) */
+void	strListFree(char*** list);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif	/* Don't add anything after this line */