diff --git a/src/sbbs3/file.cpp b/src/sbbs3/file.cpp
index 29c719603d175b46c1734eb60d47fe75fda5406f..efb1b0d0abe17b1cddfd9044b3491991218a3d93 100644
--- a/src/sbbs3/file.cpp
+++ b/src/sbbs3/file.cpp
@@ -145,7 +145,7 @@ bool sbbs_t::checkfname(const char *fname)
 		hacklog("Filename", fname);
 		return false;
 	}
-	return allowed_filename(fname);
+	return allowed_filename(&cfg, fname);
 }
 
 long sbbs_t::delfiles(const char *inpath, const char *spec, size_t keep)
diff --git a/src/sbbs3/filedat.c b/src/sbbs3/filedat.c
index aa1732e55216b562801f2a37d32385f6f37c0fab..e9427fec7dac8774769886c5ea9c7d0eaab44349 100644
--- a/src/sbbs3/filedat.c
+++ b/src/sbbs3/filedat.c
@@ -1250,31 +1250,49 @@ bool illegal_filename(const char *fname)
 {
 	size_t len = strlen(fname);
 
-	if(*fname == '-')
+	if(len < 1)
 		return true;
 	if(strcspn(fname, ILLEGAL_FILENAME_CHARS) != len)
 		return true;
 	if(strstr(fname, "..") != NULL)
 		return true;
+	if(*fname == '-') // leading dash is a problem for argument parsing
+		return true;
+	if(*fname == '.') // leading dot hides files on *nix
+		return true;
+	if(*fname == ' ') // leading space is a problem for argument parsing (shells)
+		return true;
+	if(fname[len - 1] == '.') // a trailing dot is a problem for Windows
+		return true;
+	if(fname[len - 1] == ' ') // a trailing space is a problem for argument parsing (shells)
+		return true;
+	for(size_t i = 0; i < len; i++) {
+		if(IS_CONTROL(fname[i])) // control characters in filenames are evil
+			return true;
+	}
+
 	return false;
 }
 
-/*****************************************************************************/
-/* Checks the filename 'fname' for invalid symbol or character sequences     */
-/*****************************************************************************/
-bool allowed_filename(const char *fname)
+/****************************************************************************/
+/* Checks if the filename chars meet the system requirements for upload		*/
+/* Assumes the filename has already been checked with illegal_filename()	*/
+/****************************************************************************/
+bool allowed_filename(scfg_t* cfg, const char *fname)
 {
 	size_t len = strlen(fname);
-
 	if(len < 1)
 		return false;
 
+	if(cfg->file_misc & FM_SAFEST)
+		return safest_filename(fname);
+
+	uchar min = (cfg->file_misc & FM_SPACES) ? ' ' : '!';
+	uchar max = (cfg->file_misc & FM_EXASCII) ? 0xff : 0x7f;
+
 	for(size_t i = 0; i < len; i++) {
-		if(fname[i] <= ' ')
+		if((uchar)fname[i] < min || (uchar)fname[i] > max)
 			return false;
 	}
-	if(*fname == '.' // leading dot hides files on *nix
-		|| fname[len - 1] == '.') // a trailing dot is a problem for Win32
-		return false;
 	return true;
 }
diff --git a/src/sbbs3/filedat.h b/src/sbbs3/filedat.h
index 6b2ec95816d90b9d9e9b2f5830265928d153897e..f3ac6278c00498ffa6e413f55ad751d439b69eaa 100644
--- a/src/sbbs3/filedat.h
+++ b/src/sbbs3/filedat.h
@@ -59,7 +59,7 @@ DLLEXPORT bool			removefile(scfg_t*, uint dirnum, const char* filename);
 DLLEXPORT char*			format_filename(const char* fname, char* buf, size_t, bool pad);
 DLLEXPORT bool			safest_filename(const char* fname);
 DLLEXPORT bool			illegal_filename(const char* fname);
-DLLEXPORT bool			allowed_filename(const char* fname);
+DLLEXPORT bool			allowed_filename(scfg_t*, const char* fname);
 DLLEXPORT bool			extract_diz(scfg_t*, file_t*, str_list_t diz_fname, char* path, size_t);
 DLLEXPORT char*			read_diz(const char* path, struct sauce_charinfo*);
 DLLEXPORT char*			format_diz(const char* src, char* dest, size_t maxlen, int width, bool ice_color);
diff --git a/src/sbbs3/ftpsrvr.c b/src/sbbs3/ftpsrvr.c
index 62c228ced1e0d68251107d2ceec6687f2305234b..c7b64237402b9ba544ac5247b122aea0c3f9c97c 100644
--- a/src/sbbs3/ftpsrvr.c
+++ b/src/sbbs3/ftpsrvr.c
@@ -4573,7 +4573,7 @@ static void ctrl_thread(void* arg)
 					ftp_hacklog("FTP FILENAME", user.alias, cmd, host_name, &ftp.client_addr);
 					continue;
 				}
-				if(!allowed_filename(p)) {
+				if(!allowed_filename(&scfg, p)) {
 					lprintf(LOG_WARNING,"%04d <%s> !UNALLOWED FILENAME ATTEMPT by %s [%s]: %s"
 						,sock, user.alias, host_name, host_ip, p);
 					sockprintf(sock,sess,"553 Unallowed filename attempt");
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index 07e027f804fa490b2e87cf8f51c835c4badabe41..881862bb70c9c0c8d3669ecdc433a99de613dfc5 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -261,6 +261,10 @@
 #define DIR_NOHASH		(1<<22)		/* Don't auto calculate/store file content hashes */
 #define DIR_FILETAGS	(1<<23)		/* Allow files to have user-specified tags */
 
+#define FM_SAFEST		(1<<1)		/* Allow safest filenames to be uploaded only */
+#define FM_SPACES		(1<<2)		/* Allow spaces in uploaded filenames */
+#define FM_EXASCII		(1<<3)		/* Allow extended-ASCII (or UTF-8) in uploaded filenames */
+
 									/* Bit values for cfg.msg_misc (upper 16-bits default to on) */
 #define MM_REALNAME	(1<<16)			/* Allow receipt of e-mail using real names	*/
 #define MM_EMAILSIG	(1<<17)			/* Include user signatures in e-mail msgs */
diff --git a/src/sbbs3/scfg/scfgxfr1.c b/src/sbbs3/scfg/scfgxfr1.c
index f77f311af60ca0a5b0fe1d5d86b81e38e4dfeb37..2c562ef791ad1c9086f47934badbe21d459e4402 100644
--- a/src/sbbs3/scfg/scfgxfr1.c
+++ b/src/sbbs3/scfg/scfgxfr1.c
@@ -69,6 +69,13 @@ void xfer_opts()
 		else
 			strcpy(str,"Disabled");
 		sprintf(opt[i++],"%-33.33s%s","Leech Protocol Detection",str);
+		if(cfg.file_misc & FM_SAFEST)
+			SAFECOPY(str, "Safest Subset");
+		else
+			SAFEPRINTF2(str, "Most %s, %scluding Spaces"
+				,cfg.file_misc & FM_EXASCII ? "CP437" : "ASCII"
+				,cfg.file_misc & FM_SPACES ? "In" : "Ex");
+		sprintf(opt[i++], "%-33.33s%s", "Allow Filename Characters", str);
 		strcpy(opt[i++],"Viewable Files...");
 		strcpy(opt[i++],"Testable Files...");
 		strcpy(opt[i++],"Download Events...");
@@ -186,7 +193,7 @@ void xfer_opts()
 					"\n"
 					"This option allows you to adjust the sensitivity of the leech protocol\n"
 					"detection feature. This value is the minimum length of transfer time\n"
-					"(in seconds) that must elapse before an aborted tranfser will be\n"
+					"(in seconds) that must elapse before an aborted transfer will be\n"
 					"considered a possible leech attempt.\n"
 				;
 				uifc.input(WIN_MID,0,0
@@ -194,6 +201,82 @@ void xfer_opts()
 					,ultoa(cfg.leech_sec,tmp,10),3,K_EDIT|K_NUMBER);
 				cfg.leech_sec=atoi(tmp);
 				break;
+			case __COUNTER__:	/* Uploaded Filename characters allowed */
+				i = 0;
+				strcpy(opt[i++], "Safest Subset Only (A-Z, a-z, 0-9, -, _, and .)");
+				strcpy(opt[i++], "Most ASCII Characters, Excluding Spaces");
+				strcpy(opt[i++], "Most ASCII Characters, Including Spaces");
+				strcpy(opt[i++], "Most CP437 Characters, Excluding Spaces");
+				strcpy(opt[i++], "Most CP437 Characters, Including Spaces");
+				opt[i][0] = '\0';
+				if(cfg.file_misc & FM_SAFEST)
+					j = 0;
+				else {
+					j = 1;
+					if(cfg.file_misc & FM_EXASCII)
+						j = 3;
+					if(cfg.file_misc & FM_SPACES)
+						j++;
+				}
+				uifc.helpbuf=
+					"`Allowed Characters in Uploaded Filenames:`\n"
+					"\n"
+					"Here you can control which characters will be allowed in the names of\n"
+					"files uploaded by users (assuming you allow file uploads at all).\n"
+					"\n"
+					"The `Safest` (most compatible) filename characters to allow are:\n"
+					"`" SAFEST_FILENAME_CHARS "`\n"
+					"\n"
+					"`Spaces` may be allowed in filenames, but may cause issues with some file\n"
+					"transfer protocol drivers or clients (older/MS-DOS software).\n"
+					"\n"
+					"Filenames that are most often troublesome (including those in your\n"
+					"`text/file.can` file) are `always disallowed`:\n"
+					"    Filenames beginning with dash (`-`)\n"
+					"    Filenames beginning or ending in space\n"
+					"    Filenames beginning or ending in period (`.`)\n"
+					"    Filenames containing consecutive periods (`..`)\n"
+					"    Filenames containing illegal characters (`" ILLEGAL_FILENAME_CHARS "`)\n"
+					"    Filenames containing control characters (ASCII 0-31 and 127)\n"
+				;
+				i = uifc.list(0, 0, 0, 0, &j, NULL, "Allowed Characters in Uploaded Filenames", opt);
+				switch(i) {
+					case 0:
+						if(cfg.file_misc != FM_SAFEST) {
+							cfg.file_misc |= FM_SAFEST;
+							cfg.file_misc &= ~(FM_EXASCII | FM_SPACES);
+							uifc.changes = TRUE;
+						}
+						break;
+					case 1:
+						if(cfg.file_misc) {
+							cfg.file_misc &= ~(FM_SAFEST | FM_SPACES | FM_EXASCII);
+							uifc.changes = TRUE;
+						}
+						break;
+					case 2:
+						if(cfg.file_misc != FM_SPACES) {
+							cfg.file_misc &= ~(FM_SAFEST | FM_EXASCII);
+							cfg.file_misc |= FM_SPACES;
+							uifc.changes = TRUE;
+						}
+						break;
+					case 3:
+						if(cfg.file_misc != FM_EXASCII) {
+							cfg.file_misc &= ~(FM_SAFEST | FM_SPACES);
+							cfg.file_misc |= FM_EXASCII;
+							uifc.changes = TRUE;
+						}
+						break;
+					case 4:
+						if(cfg.file_misc != (FM_EXASCII | FM_SPACES)) {
+							cfg.file_misc &= ~(FM_SAFEST);
+							cfg.file_misc |= FM_EXASCII | FM_SPACES;
+							uifc.changes = TRUE;
+						}
+						break;
+				}
+				break;
 			case __COUNTER__: 	/* Viewable file types */
 				while(1) {
 					for(i=0;i<cfg.total_fviews && i<MAX_OPTS;i++)