diff --git a/xtrn/DDMsgReader/DDMsgReader.cfg b/xtrn/DDMsgReader/DDMsgReader.cfg
index 22a9bbbd399df9970d38bf7819c247332f804671..82cdb1a3dc372bc4c545bf43cfae02633b11107c 100644
--- a/xtrn/DDMsgReader/DDMsgReader.cfg
+++ b/xtrn/DDMsgReader/DDMsgReader.cfg
@@ -52,5 +52,16 @@ convertYStyleMCIAttrsToSync=true
 ; Whether or not to prepend the subject for forwarded messages with "Fwd: "
 prependFowardMsgSubject=true
 
+; For indexed reader mode, whether or not to enable caching the message
+; header lists for performance
+enableIndexedModeMsgListCache=true
+
+; An index of a quick-validation set from SCFG > System > Security Options > Quick-Validation Values
+; to be used by the sysop to quick-validate a local user who has posted a message while reading the
+; message.  This index is 0-based, as they appear in SCFG. Normally there are 10 quick-validation
+; values, so valid values for this index are 0 through 9. If you would rather DDMsgReader display a
+; menu of quick-validation sets, you can set this to an invalid index (such as -1).
+quickUserValSetIndex=-1
+
 ; The theme file name (for colors, strings, etc.)
 themeFilename=DefaultTheme.cfg
diff --git a/xtrn/DDMsgReader/DDMsgReader.js b/xtrn/DDMsgReader/DDMsgReader.js
index 597fefa406ab2ccea915a46f41df0ab2e892f5fe..e1548af76b621a38e983acc40dad27c1d6e5fb60 100644
--- a/xtrn/DDMsgReader/DDMsgReader.js
+++ b/xtrn/DDMsgReader/DDMsgReader.js
@@ -120,6 +120,10 @@
  * 2023-04-07 Eric Oulashin     Version 1.71
  *                              Ctrl-C is now supported for message searches to abort the search. A
  *                              new configurable string was added for this situation: msgSearchAbortedText
+ * 2023-04-16 Eric Oulashin     Version 1.72
+ *                              Added a quick-validation hotkey, Ctrl-Q, for sysops to use to apply a
+ *                              quick-validation set to a user when reading their message. Quick-Validation
+ *                              sets are configured in SCFG > System > Security Options > Quick-Validation Values.
  */
 
 "use strict";
@@ -225,8 +229,8 @@ var ansiterm = require("ansiterm_lib.js", 'expand_ctrl_a');
 
 
 // Reader version information
-var READER_VERSION = "1.71";
-var READER_DATE = "2023-04-07";
+var READER_VERSION = "1.72";
+var READER_DATE = "2023-04-16";
 
 // Keyboard key codes for displaying on the screen
 var UP_ARROW = ascii(24);
@@ -1101,10 +1105,12 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 		closePoll: "!",
 		bypassSubBoardInNewScan: "B",
 		userSettings: CTRL_U,
+		validateMsg: "A", // Only if the user is a sysop
+		quickValUser: CTRL_Q,
 		threadView: "*" // TODO: Implement this
 	};
-	if (user.is_sysop)
-		this.enhReaderKeys.validateMsg = "A";
+	//if (user.is_sysop)
+	//	this.enhReaderKeys.validateMsg = "A";
 	// Some key bindings for the message list (not necessarily all of them)
 	this.msgListKeys = {
 		deleteMessage: KEY_DEL,
@@ -1131,6 +1137,10 @@ function DigDistMsgReader(pSubBoardCode, pScriptArgs)
 	// Whether or not to prepend the subject for forwarded messages with "Fwd: "
 	this.prependFowardMsgSubject = true;
 
+	// An index of a quick-validation set for the sysop to apply to a user when reading their message, or
+	// an invalid index (such as -1) to show a menu of quick-validation values
+	this.quickUserValSetIndex = -1;
+
 	this.cfgFilename = "DDMsgReader.cfg";
 	// Check the command-line arguments for a custom configuration file name
 	// before reading the configuration file.  Defaults to the current user
@@ -3450,7 +3460,7 @@ function DigDistMsgReader_ListMessages_Traditional(pAllowChgSubBoard)
 			else if (retvalObj.userInput == "?")
 			{
 				console.clear("\x01n");
-				this.DisplayMsgListHelp(allowChgSubBoard, true);
+				this.DisplayMsgListHelp(!this.readingPersonalEmail && allowChgSubBoard, true);
 				console.clear("\x01n");
 				this.WriteMsgListScreenTopHeader();
 			}
@@ -3991,7 +4001,7 @@ function DigDistMsgReader_ListMessages_Lightbar(pAllowChgSubBoard)
 		else if (lastUserInputUpper == this.msgListKeys.showHelp) // Show help
 		{
 			console.clear("\x01n");
-			this.DisplayMsgListHelp(allowChgSubBoard, true);
+			this.DisplayMsgListHelp(!this.readingPersonalEmail && allowChgSubBoard, true);
 			// Re-draw the message list header & help line before
 			// the menu is re-drawn
 			this.WriteMsgListScreenTopHeader();
@@ -5954,6 +5964,33 @@ function DigDistMsgReader_ReadMessageEnhanced_Scrollable(msgHeader, allowChgMsgA
 				else
 					writeMessage = false;
 				break;
+			case this.enhReaderKeys.quickValUser: // Quick-validate the user
+				if (user.is_sysop)
+				{
+					var valRetObj = quickValidateLocalUser(msgHeader.from, this.scrollingReaderInterface && console.term_supports(USER_ANSI), this.quickUserValSetIndex);
+					if (valRetObj.needWholeScreenRefresh)
+					{
+						this.DisplayEnhancedMsgHdr(msgHeader, pOffset+1, 1);
+						if (this.userSettings.useEnhReaderScrollbar)
+							this.DisplayEnhancedReaderWholeScrollbar(solidBlockStartRow, numSolidScrollBlocks);
+						else
+						{
+							// TODO
+						}
+						this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
+					}
+					else
+					{
+						writeMessage = false; // Don't refresh the whole message
+						if (valRetObj.refreshBottomLine)
+							this.DisplayEnhancedMsgReadHelpLine(console.screen_rows, allowChgMsgArea);
+						if (valRetObj.optionBoxTopLeftX > 0 && valRetObj.optionBoxTopLeftY > 0 && valRetObj.optionBoxWidth > 0 && valRetObj.optionBoxHeight > 0)
+							this.RefreshMsgAreaRectangle(msgInfo.messageLines, topMsgLineIdx, valRetObj.optionBoxTopLeftX, valRetObj.optionBoxTopLeftY, valRetObj.optionBoxWidth, valRetObj.optionBoxHeight);
+					}
+				}
+				else
+					writeMessage = false;
+				break;
 			case this.enhReaderKeys.bypassSubBoardInNewScan:
 				writeMessage = false; // TODO: Finish
 				/*
@@ -7006,6 +7043,12 @@ function DigDistMsgReader_ReadMessageEnhanced_Traditional(msgHeader, allowChgMsg
 				else
 					writeMessage = false;
 				break;
+			case this.enhReaderKeys.quickValUser: // Quick-validate the user
+				if (user.is_sysop)
+					quickValidateLocalUser(msgHeader.from, this.scrollingReaderInterface && console.term_supports(USER_ANSI), this.quickUserValSetIndex);
+				else
+					writeMessage = false;
+				break;
 			case this.enhReaderKeys.bypassSubBoardInNewScan:
 				// TODO: Finish
 				writeMessage = false;
@@ -8360,9 +8403,9 @@ function DigDistMsgReader_ReadConfigFile()
 				}
 				else if (settingUpper == "TABSPACES")
 				{
-					var numSpaces = +value;
+					var numSpaces = parseInt(value);
 					// If greater than 0, then set this.numTabSpaces
-					if (numSpaces > 0)
+					if (!isNaN(numSpaces) && numSpaces > 0)
 						this.numTabSpaces = numSpaces;
 				}
 				else if (settingUpper == "PAUSEAFTERNEWMSGSCAN")
@@ -8373,8 +8416,8 @@ function DigDistMsgReader_ReadConfigFile()
 					this.areaChooserHdrFilenameBase = value;
 				else if (settingUpper == "AREACHOOSERHDRMAXLINES")
 				{
-					var maxNumLines = +value;
-					if (maxNumLines > 0)
+					var maxNumLines = parseInt(value);
+					if (!isNaN(maxNumLines) && maxNumLines > 0)
 						this.areaChooserHdrMaxLines = maxNumLines;
 				}
 				else if (settingUpper == "THEMEFILENAME")
@@ -8403,6 +8446,12 @@ function DigDistMsgReader_ReadConfigFile()
 					this.prependFowardMsgSubject = (valueUpper == "TRUE");
 				else if (settingUpper == "ENABLEINDEXEDMODEMSGLISTCACHE")
 					this.enableIndexedModeMsgListCache = (valueUpper == "TRUE");
+				else if (settingUpper == "QUICKUSERVALSETINDEX")
+				{
+					var numberVal = parseInt(value);
+					if (!isNaN(numberVal) && numberVal > -1)
+						this.quickUserValSetIndex = numberVal;
+				}
 			}
 		}
 
@@ -8433,7 +8482,7 @@ function DigDistMsgReader_ReadConfigFile()
 			var setting = null;      // A setting name (string)
 			var value = null;        // To store a value for a setting (string)
 			// A regex for attribute settings - Note that this doesn't contain some of the control codes
-			var onlySyncAttrsRegexWholeWord = new RegExp("^[\x01krgybmcw01234567hinq,;\.dtlasz]+$", 'i');;
+			var onlySyncAttrsRegexWholeWord = new RegExp("^[\x01krgybmcw01234567hinq,;\.dtlasz]+$", 'i');
 			while (!themeFile.eof)
 			{
 				// Read the next line from the config file.
@@ -10444,6 +10493,10 @@ function DigDistMsgReader_DisplayEnhancedReaderHelp(pDisplayChgAreaOpt, pDisplay
 		keyHelpLines.push("\x01h\x01cDEL              \x01g: \x01n\x01cDelete the current message");
 		keyHelpLines.push("\x01h\x01cCtrl-S           \x01g: \x01n\x01cSave the message (to the BBS machine)");
 		keyHelpLines.push("\x01h\x01c" + this.enhReaderKeys.validateMsg + "                \x01g: \x01n\x01cValidate the message");
+		var quickValUserLine = "\x01h\x01cCtrl-Q           \x01g: \x01n\x01cQuick-validate user (must be local)";
+		if (this.quickUserValSetIndex >= 0 && this.quickUserValSetIndex < 10)
+			quickValUserLine += "; Set index: " + this.quickUserValSetIndex;
+		keyHelpLines.push(quickValUserLine);
 	}
 	else if (this.CanDelete() || this.CanDeleteLastMsg())
 		keyHelpLines.push("\x01h\x01cDEL              \x01g: \x01n\x01cDelete the current message (if it's yours)");
@@ -12924,6 +12977,17 @@ function DigDistMsgReader_GetMsgInfoForEnhancedReader(pMsgHdr, pWordWrap, pDeter
 		//msgTextAltered = msgTextAltered.substr(strIdx);
 	}
 
+	// ASCII 0x8D (141 decimal) is considered a soft-CR character in FidoNet.  In
+	// echocfg, under Global Settings, the setting "Strip Incoming Soft-CRs" can
+	// be enabled to remove those characters (that are not UTF-8
+	// encoded).
+	//msgTextAltered = msgTextAltered.replace(new RegExp("\x8D", "g"), ""); // 'i' with accent; hard word wrap character
+	//msgTextAltered = msgTextAltered.replace(new RegExp("\x8D", "g"), "\r\n"); // 'i' with accent; hard word wrap character
+	// This PDF lists some characters without any description - Are these normally non-printable?
+	// https://www.utm.edu/staff/lholder/csci201/ascii_table.pdf
+	// 0x8D (141) is 'i' with accent; hard word wrap character
+	//msgTextAltered = msgTextAltered.replace(new RegExp("[\x81\x8D\x8F\x90\x9D]", "g"), "");
+
 	var wordWrapTheMsgText = true;
 	if (typeof(pWordWrap) == "boolean")
 		wordWrapTheMsgText = pWordWrap;
@@ -21222,6 +21286,404 @@ function isValidScanScopeVal(pScanScope)
 	return (typeof(pScanScope) === "number" && (pScanScope == SCAN_SCOPE_SUB_BOARD || pScanScope == SCAN_SCOPE_GROUP || pScanScope == SCAN_SCOPE_ALL));
 }
 
+// Lets the user (if they're a sysop) apply a quick-validation value (from SCFG > System > Security Options > Quick-Validation Values)
+// to a user by username
+//
+// Parameters:
+//  pUsername: The name of the user to apply the quick-validation set to
+//  pUseANSI: Optional boolean - Whether or not to use ANSI
+//  pQuickValSetIdx: Optional - The index of the quick validation set to apply (0-9, as they
+//                   appear in SCFG). If this is omitted, a menu will be displayed to allow
+//                   choosing one of them
+//
+// Return value: An object containing the following properties:
+//               needWholeScreenRefresh: Boolean - Whether or not the whole screen needs to be
+//                                       refreshed (i.e., when the user has edited their twitlist)
+//               refreshBottomLine: Boolean - Whether or not the bottom line on the screen needs to be refreshed
+//               optionBoxTopLeftX: The top-left screen column of the option box (0 if none was used)
+//               optionBoxTopLeftY: The top-left screen row of the option box (0 if none was used)
+//               optionBoxWidth: The width of the option box (0 if none was used)
+//               optionBoxHeight: The height of the option box (0 if none was used)
+function quickValidateLocalUser(pUsername, pUseANSI, pQuickValSetIdx)
+{
+	var retObj = {
+		needWholeScreenRefresh: false,
+		refreshBottomLine: false,
+		optionBoxTopLeftX: 0,
+		optionBoxTopLeftY: 0,
+		optionBoxWidth: 0,
+		optionBoxHeight: 0
+	};
+
+	if (!user.is_sysop)
+		return retObj;
+	if (typeof(pUsername) !== "string" || pUsername == "")
+		return retObj;
+
+	var useANSI = typeof(pUseANSI) === "boolean" ? pUseANSI : console.term_supports(USER_ANSI);
+	//useANSI = false; // Temporary
+
+	var userNum = system.matchuser(pUsername);
+	if (userNum == 0)
+	{
+		var msgText = bbs.text(UnknownUser).replace(/\r|\n/g, ""); // Or UNKNOWN_USER
+		msgText += "\x01."; // Delay for 2 seconds
+		if (useANSI)
+		{
+			retObj.refreshBottomLine = true;
+			console.gotoxy(1, console.screen_columns);
+			console.cleartoeol("\x01n");
+			console.gotoxy(1, console.screen_columns);
+			console.attributes = msgAttrs;
+			console.putmsg(msgText);
+		}
+		else
+		{
+			console.attributes = "N";
+			console.crlf();
+			console.putmsg(msgText);
+		}
+		return retObj;
+	}
+
+
+	// Get an array of the quick-validation values from SCFG
+	var quickValidationVals = getQuickValidationVals();
+	// If pQuickValSetIdx is a number specifying a valid index, then use it; otherwise, display
+	// a menu of the quick-validation values to choose from
+	var quickValidationValSet = null;
+	var displayedMenu = false;
+	if (typeof(pQuickValSetIdx) === "number" && pQuickValSetIdx >= 0 && pQuickValSetIdx < quickValidationVals.length)
+		quickValidationValSet = quickValidationVals[pQuickValSetIdx];
+	else
+	{
+		// No valid validation set index given; display the menu
+		var menuX = 2;
+		var menuY = 3;
+		var valHdrLineWithUsername = "Quick validation for " + pUsername;
+		var menuHdrStr = " Level E 1 2 3 4 C E R";
+		console.attributes = "N";
+		if (useANSI)
+		{
+			menuX = 25;
+			menuY = 12;
+		}
+		else
+		{
+			menuX = 2;
+			menuY = 3;
+			menuHdrStr = "   " + menuHdrStr;
+			console.clear("\x01n");
+			console.print(valHdrLineWithUsername);
+			console.crlf();
+			console.print("Quick-Validation sets:");
+			console.crlf();
+			console.print(menuHdrStr);
+			console.crlf();
+		}
+		// Create the menu of quick-validation sets
+		var valSetMenu = makeQuickValidationValLightbarMenu(useANSI, menuX, menuY, quickValidationVals);
+		// If using ANSI, draw some border characters at the left, bottom, and right sides of the menu
+		if (useANSI)
+		{
+			// Screen refresh values for returning from this function
+			retObj.needWholeScreenRefresh = false;
+			retObj.optionBoxTopLeftX = valSetMenu.pos.x-1;
+			retObj.optionBoxTopLeftY = valSetMenu.pos.y-4;
+			retObj.optionBoxWidth = valSetMenu.size.width+2;
+			retObj.optionBoxHeight = valSetMenu.size.height+5;
+			retObj.refreshBottomLine = false;
+
+			// Display the menu
+			console.attributes = "GH";
+			// Top border
+			var screenRow = valSetMenu.pos.y - 4;
+			console.gotoxy(valSetMenu.pos.x-1, screenRow);
+			console.print(UPPER_LEFT_DOUBLE);
+			for (var i = 0; i < valSetMenu.size.width; ++i)
+				console.print(HORIZONTAL_DOUBLE);
+			console.print(UPPER_RIGHT_DOUBLE);
+			// Side border characters
+			screenRow = valSetMenu.pos.y - 3;
+			var height = valSetMenu.size.height + 3;
+			for (var i = 0; i < height; ++i)
+			{
+				console.gotoxy(valSetMenu.pos.x-1, screenRow);
+				console.print(VERTICAL_DOUBLE);
+				console.gotoxy(valSetMenu.pos.x+valSetMenu.size.width, screenRow);
+				console.print(VERTICAL_DOUBLE);
+				++screenRow;
+			}
+			// Bottom border characters
+			screenRow = valSetMenu.pos.y+valSetMenu.size.height;
+			console.gotoxy(valSetMenu.pos.x-1, screenRow);
+			console.print(LOWER_LEFT_DOUBLE);
+			for (var i = 0; i < valSetMenu.size.width; ++i)
+				console.print(HORIZONTAL_DOUBLE);
+			console.print(LOWER_RIGHT_DOUBLE);
+			console.attributes = "N";
+
+			console.gotoxy(menuX, menuY-3);
+			//printf("%-*s", valSetMenu.size.width, "Quick validation for:");
+			printf("%-*s", valSetMenu.size.width, "Quick validation sets:");
+			console.gotoxy(menuX, menuY-2);
+			printf("%-*s", valSetMenu.size.width, pUsername.substr(0, valSetMenu.size.width));
+			console.gotoxy(menuX, menuY-1);
+			console.print(menuHdrStr);
+		}
+		else
+			retObj.needWholeScreenRefresh = true;
+		quickValidationValSet = valSetMenu.GetVal();
+		displayedMenu = true;
+		console.attributes = "N";
+	}
+	var statusMsg = "";
+	var msgAttrs = "N";
+	if (quickValidationValSet != null && typeof(quickValidationValSet) === "object")
+	{
+		var userToEdit = new User(userNum);
+		/*
+		user.security properties
+		Name			Type	Ver		Description
+		password		string	3.10	password
+		password_date	number	3.10	date password last modified (time_t format)
+		level			number	3.10	security level (0-99)
+		flags1			number	3.10	flag set #1 (bitfield) can use +/-[A-?] notation
+		flags2			number	3.10	flag set #2 (bitfield) can use +/-[A-?] notation
+		flags3			number	3.10	flag set #3 (bitfield) can use +/-[A-?] notation
+		flags4			number	3.10	flag set #4 (bitfield) can use +/-[A-?] notation
+		exemptions		number	3.10	exemption flags (bitfield) can use +/-[A-?] notation
+		restrictions	number	3.10	restriction flags (bitfield) can use +/-[A-?] notation
+		credits			number	3.10	credits
+		free_credits	number	3.10	free credits (for today only)
+		minutes			number	3.10	extra minutes (time bank)
+		extra_time		number	3.10	extra minutes (for today only)
+		expiration_date	number	3.10	expiration date/time (time_t format)
+		*/
+		// Each object in the returned array will have the following properties:
+		//  level (numeric)
+		//  expire
+		//  flags1
+		//  flags2
+		//  flags3
+		//  flags4
+		//  credits
+		//  exemptions
+		//  restrictions
+		userToEdit.security.level = quickValidationValSet.level;
+		userToEdit.security.flags1 |= quickValidationValSet.flags1;
+		userToEdit.security.flags2 |= quickValidationValSet.flags2;
+		userToEdit.security.flags3 |= quickValidationValSet.flags3;
+		userToEdit.security.flags4 |= quickValidationValSet.flags4;
+		userToEdit.security.exemptions |= quickValidationValSet.exemptions;
+		userToEdit.security.restrictions |= quickValidationValSet.restrictions;
+		userToEdit.security.credits = quickValidationValSet.credits;
+		statusMsg = "Validation set applied";
+		msgAttrs = "CH";
+	}
+	else
+	{
+		statusMsg = "Aborted";
+		msgAttrs = "YH";
+	}
+
+	// Display the final status message
+	if (useANSI)
+	{
+		if (displayedMenu)
+		{
+			// Clear the box on the screen and write that the validation set was applied to the user
+			var topBoxScreenRow = valSetMenu.pos.y - 3;
+			clearScreenRectangle(valSetMenu.pos.x, topBoxScreenRow, valSetMenu.size.width, valSetMenu.size.height+3);
+			console.gotoxy(valSetMenu.pos.x, topBoxScreenRow);
+			console.attributes = msgAttrs;
+			console.print(statusMsg + "\x01;\x01;");
+		}
+		else
+		{
+			retObj.refreshBottomLine = true;
+			console.gotoxy(1, console.screen_columns);
+			console.cleartoeol("\x01n");
+			console.gotoxy(1, console.screen_columns);
+			console.attributes = msgAttrs;
+			console.print(statusMsg + "\x01;\x01;");
+		}
+	}
+	else
+	{
+		console.crlf();
+		console.attributes = msgAttrs;
+		console.print(statusMsg + "\x01n\r\n\x01p");
+	}
+
+	console.attributes = "N";
+
+	return retObj;
+}
+
+// Creates a DDLightbarMenu object to use for the quick-validation values.
+//
+// Parameters:
+//  pUseANSI: Boolean - Whether or not to enable the use of ANSI
+//  pMenuX: The top-left X coordinate for the menu
+//  pMenuY: The top-left Y coordinate for the menu
+//  pQuickValidationVals: An array of the quick-validation values from SCFG > System > Security Settings > Quick-Validation Values
+//
+// Return value: A DDLightbarMenu object to use for the quick-validation values menu
+function makeQuickValidationValLightbarMenu(pUseANSI, pMenuX, pMenuY, pQuickValidationVals)
+{
+	var useANSI = (typeof(pUseANSI) === "boolean" ? pUseANSI : true);
+
+	//  Level E 1 2 3 4 C E R
+	//     60 Y Y Y Y Y Y Y Y
+	var quickValsMenuWidth = 22; //console.screen_columns - 4;
+	if (!console.term_supports(USER_ANSI))
+		quickValsMenuWidth += 3;
+	var quickValsMenuHeight = pQuickValidationVals.length;
+	var quickValsMenu = new DDLightbarMenu(pMenuX, pMenuY, quickValsMenuWidth, quickValsMenuHeight);
+	quickValsMenu.AddAdditionalQuitKeys("qQ");
+	quickValsMenu.scrollbarEnabled = true;
+	quickValsMenu.borderEnabled = false;
+	quickValsMenu.allowANSI = useANSI;
+
+	//SetBorderChars(pBorderChars)
+	//"upperLeft", "upperRight", "lowerLeft", "lowerRight", "top", "bottom", "left", "right"
+
+	var colors = {
+		level: "\x01n\x01w",
+		levelHi: "\x01n\x01w\x014",
+		YN: "\x01n\x01w",
+		YNHi: "\x01n\x01w\x014"
+	};
+	var itemTextIdxes = {
+		levelStart: 0,
+		levelEnd: 7,
+		YN1Start: 7,
+		YN1End: 9,
+		YN2Start: 9,
+		YN2End: 11,
+		YN3Start: 11,
+		YN3End: 13,
+		YN4Start: 13,
+		YN4End: 15,
+		YN5Start: 15,
+		YN5End: 17,
+		YN6Start: 17,
+		YN6End: 19,
+		YN7Start: 19,
+		YN7End: 21,
+		YN8Start: 21,
+		YN8End: 23
+	};
+
+	quickValsMenu.SetColors({
+		itemColor: [{start: itemTextIdxes.levelStart, end: itemTextIdxes.levelEnd, attrs: colors.level},
+		            {start: itemTextIdxes.YN1Start, end: itemTextIdxes.YN1End, attrs: colors.YN},
+					{start: itemTextIdxes.YN2Start, end: itemTextIdxes.YN2End, attrs: colors.YN},
+					{start: itemTextIdxes.YN3Start, end: itemTextIdxes.YN3End, attrs: colors.YN},
+					{start: itemTextIdxes.YN4Start, end: itemTextIdxes.YN4End, attrs: colors.YN},
+					{start: itemTextIdxes.YN5Start, end: itemTextIdxes.YN5End, attrs: colors.YN},
+					{start: itemTextIdxes.YN6Start, end: itemTextIdxes.YN6End, attrs: colors.YN},
+					{start: itemTextIdxes.YN7Start, end: itemTextIdxes.YN7End, attrs: colors.YN},
+					{start: itemTextIdxes.YN8Start, end: itemTextIdxes.YN8End, attrs: colors.YN}],
+		selectedItemColor: [{start: itemTextIdxes.levelStart, end: itemTextIdxes.levelEnd, attrs: colors.levelHi},
+		                    {start: itemTextIdxes.YN1Start, end: itemTextIdxes.YN1End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN2Start, end: itemTextIdxes.YN2End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN3Start, end: itemTextIdxes.YN3End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN4Start, end: itemTextIdxes.YN4End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN5Start, end: itemTextIdxes.YN5End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN6Start, end: itemTextIdxes.YN6End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN7Start, end: itemTextIdxes.YN7End, attrs: colors.YNHi},
+		                    {start: itemTextIdxes.YN8Start, end: itemTextIdxes.YN8End, attrs: colors.YNHi}]
+	});
+
+	quickValsMenu.quickValidationVals = pQuickValidationVals;
+	//format("%*s", this.numTabSpaces, "")
+	quickValsMenu.itemFormatStr = "%6d %s %s %s %s %s %s %s %s";
+	quickValsMenu.NumItems = function() {
+		return this.quickValidationVals.length;
+	};
+	quickValsMenu.GetItem = function(pItemIndex) {
+		var valYNStrs = [
+			this.quickValidationVals[pItemIndex].expire > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].flags1 > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].flags2 > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].flags3 > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].flags4 > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].credits > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].exemptions > 0 ? CHECK_CHAR : " ",
+			this.quickValidationVals[pItemIndex].restrictions > 0 ? CHECK_CHAR : " "
+		];
+
+		var menuItemObj = this.MakeItemWithRetval(-1);
+		menuItemObj.retval = this.quickValidationVals[pItemIndex];
+		menuItemObj.text = format("%6d", this.quickValidationVals[pItemIndex].level);
+		for (var i = 0; i < valYNStrs.length; ++i)
+			menuItemObj.text += " " + valYNStrs[i];
+		return menuItemObj;
+	};
+
+	return quickValsMenu;
+}
+
+// Returns an array of the quick-validation sets configured in
+// SCFG > System > Security > Quick-Validation Values.  This reads
+// from main.ini, which exists with Synchronet 3.20 and newer.
+// In SCFG:
+//
+// Level                 60     |
+// Flag Set #1                  |
+// Flag Set #2                  |
+// Flag Set #3                  |
+// Flag Set #4                  |
+// Exemptions                   |
+// Restrictions                 |
+// Extend Expiration     0 days |
+// Additional Credits    0      |
+//
+// Each object in the returned array will have the following properties:
+//  level (numeric)
+//  expire
+//  flags1
+//  flags2
+//  flags3
+//  flags4
+//  credits
+//  exemptions
+//  restrictions
+function getQuickValidationVals()
+{
+	var validationValSets = [];
+	// In SCFG > System > Security > Quick-Validation Values, there are 10 sets of
+	// validation values.  These are in main.ini as [valset:0] through [valset:9]
+	// This reads from main.ini, which exists with Synchronet 3.20 and newer.
+	//system.version_num >= 32000
+	var mainIniFile = new File(system.ctrl_dir + "main.ini");
+	if (mainIniFile.open("r"))
+	{
+		for (var i = 0; i < 10; ++i)
+		{
+			var valSection = mainIniFile.iniGetObject(format("valset:%d", i));
+			if (valSection != null)
+				validationValSets.push(valSection);
+		}
+		mainIniFile.close();
+	}
+	return validationValSets;
+}
+
+// Clears a rectangle on the screen
+function clearScreenRectangle(pX, pY, pWidth, pHeight)
+{
+	console.attributes = "N";
+	var lastScreenRow = pY + pHeight - 1;
+	for (var screenRow = pY; screenRow <= lastScreenRow; ++screenRow)
+	{
+		console.gotoxy(pX, screenRow);
+		printf("%-*s", pWidth, "");
+	}
+}
+
 ///////////////////////////////////////////////////////////////////////////////////
 
 // For debugging: Writes some text on the screen at a given location with a given pause.
diff --git a/xtrn/DDMsgReader/readme.txt b/xtrn/DDMsgReader/readme.txt
index e10547b7c31e3440d35421aa1ccba4c5f9c25709..375c67524dbcf7290decd5ea737a57bef92efd1f 100644
--- a/xtrn/DDMsgReader/readme.txt
+++ b/xtrn/DDMsgReader/readme.txt
@@ -1,6 +1,6 @@
                       Digital Distortion Message Reader
-                                 Version 1.71
-                           Release date: 2023-04-07
+                                 Version 1.72
+                           Release date: 2023-04-16
 
                                      by
 
@@ -31,8 +31,9 @@ Contents
    - Main configuration file (DDMsgReader.cfg)
    - Theme configuration file
 7. Indexed reader mode
-8. Drop file for replying to messages with Synchronet message editors
-9. text.dat lines used in Digital Distortion Message Reader
+8. Quick-Validating users (while reading their message)
+9. Drop file for replying to messages with Synchronet message editors
+10. text.dat lines used in Digital Distortion Message Reader
 
 
 1. Disclaimer
@@ -143,6 +144,8 @@ confirmation before deleting the messages.
 - Has an "indexed" reader mode, which lists all sub-boards configured for
   newscan by the user, with total number of messages, number of new messages,
   and last post date, allowing the user to select a sub-board to read
+- Allows the sysop to quick-validate a local user while reading one of their
+  messages. The hotkey to do so is Ctrl-Q.
 
 If a message has been marked for deletion, it will appear in the message list
 with a blinking red asterisk (*) after the message number.
@@ -730,6 +733,16 @@ enableIndexedModeMsgListCache         For indexed reader mode, whether or not to
                                       enable caching the message header lists
                                       for performance
 
+quickUserValSetIndex                  The index of the quick-validation set to
+                                      use for quick-validating a local user.
+                                      Normally, this should be 0-9, as there are
+                                      10 sets of values in SCFG). Alternately,
+                                      quickUserValSetIndex can be set to
+                                      something invalid (like -1) to have a menu
+                                      of the quick-validation sets displayed for
+                                      you to choose from
+one.
+
 themeFilename                         The name of the configuration file to
                                       use for colors & string settings
 
@@ -1185,7 +1198,43 @@ NEW FUNNY - FUNNY Jokes and Stories                          1184  3 2023-04-04
 NEW MEMORIES - NOSTALGIA                                     2434  3 2023-04-03
 
 
-8. Drop file for replying to messages with Synchronet message editors
+8. Quick-Validating users (while reading their message)
+=======================================================
+While reading messages, the sysop may apply quick-validation values to a local
+user using the Ctrl-Q hotkey. Quick-Validation sets are configured in SCFG >
+System > Security Options > Quick-Validation Values. In DDMsgReader.cfg, the
+option quickUserValSetIndex can be used to set the index of the quick-validation
+set to use (normally it would be 0-9, as there are 10 sets of values in SCFG).
+Alternately, quickUserValSetIndex can be set to something invalid (like -1) for
+DDMsgReader to display a menu of the quick-validation sets to let you choose
+one.
+
+A quick-validation set in CFG is a set that includes a security level, flag
+sets, exemptions, restrictions, and additional credits. For example:
+
+░░░░╔══════════════════════════════════════════════════════════════════════╗░░░░
+░░░░║                         System Conf╔══════════════════════════[  >]╗ ║░░░░
+░░░░╔════════════════════════════════════║     Quick-Validation Set 0    ║═╗░░░░
+░░░░║                     ╔══════════════╠═══════════════════════════════╣ ║░░░░
+░░░░╠═════════════════════║ Quick-Validat║ │Level                 5      ║═╣░░░░
+░░░░║ │System Password    ╠══════════════║ │Flag Set #1                  ║ ║░░░░
+░░░░║ │Prompt for System P║ │0  SL: 5   F║ │Flag Set #2                  ║ ║░░░░
+░░░░║ │Allow Sysop Access ║ │1  SL: 10  F║ │Flag Set #3                  ║ ║░░░░
+░░░░║ │Allow Login by Real║ │2  SL: 20  F║ │Flag Set #4                  ║ ║░░░░
+░░░░║ │Allow Login by User║ │3  SL: 30  F║ │Exemptions                   ║ ║░░░░
+░░░░║ │Users Can Choose Pa║ │4  SL: 40  F║ │Restrictions                 ║ ║░░░░
+░░░░║ │Always Prompt for P║ │5  SL: 50  F║ │Extend Expiration     0 days ║ ║░░░░
+░░░░║ │Display/Log Passwor║ │6  SL: 60  F║ │Additional Credits    0      ║ ║░░░░
+░░░░║ │Days to Preserve De║ │7  SL: 70  F╚═══════════════════════════════╝ ║░░░░
+░░░░║ │Maximum Days of Use║ │8  SL: 80  F1:         ║                      ║░░░░
+░░░░║ │Open to New Users  ║ │9  SL: 90  F1:         ║                      ║░░░░
+░░░░║ │User Expires When O╚═════════════════════════╝                      ║░░░░
+░░░░║ │Security Level Values...                                            ║░░░░
+░░░░║ │Expired Account Values...                                           ║░░░░
+░░░░║ │Quick-Validation Values...                                          ║░░░░
+░░░░╚══════════════════════════════════════════════════════════════════════╝
+
+9. Drop file for replying to messages with Synchronet message editors
 =====================================================================
 When reading a message, the message lister will write a drop file in the node
 directory, called DDML_SyncSMBInfo.txt, which contains some information about
@@ -1220,13 +1269,14 @@ Note that if you have SlyEdit installed on your BBS, this version of Digital
 Distortion Message Reader (1.00) requires version 1.27 or newer of SlyEdit in
 order for SlyEdit to properly get the correct message from the message lister.
 
-9. text.dat lines used in Digital Distortion Message Reader
+10. text.dat lines used in Digital Distortion Message Reader
 ===========================================================
 This message reader uses the following lines from Synchronet's text.dat file
 (located in the sbbs/ctrl directory):
 10 (Email)
 30 (Aborted)
 54 (DeleteMailQ)
+390 (UnknownUser)
 501 (SelectItemHdr)
 503 (SelectItemWhich)
 563 (Pause)
diff --git a/xtrn/DDMsgReader/revision_history.txt b/xtrn/DDMsgReader/revision_history.txt
index ec2733d87472c665191170e96661f63ff7ff7d8d..568831b86f320f5897a7f77b4f5fdcc68e9c19c5 100644
--- a/xtrn/DDMsgReader/revision_history.txt
+++ b/xtrn/DDMsgReader/revision_history.txt
@@ -5,6 +5,11 @@ Revision History (change log)
 =============================
 Version  Date         Description
 -------  ----         -----------
+1.72     2023-04-16   Added a quick-validation hotkey, Ctrl-Q, for sysops to
+                      use to apply a quick-validation set to a user when
+                      reading their message. Quick-Validation sets are
+                      configured in SCFG > System > Security Options >
+                      Quick-Validation Values.
 1.71     2023-04-07   Ctrl-C is now supported for message searches to abort the
                       search. A new configurable string was added for this
                       situation: msgSearchAbortedText