diff --git a/xtrn/DDUploadProcessor/DDUP.cfg b/xtrn/DDUploadProcessor/DDUP.cfg new file mode 100644 index 0000000000000000000000000000000000000000..f027306af23dfa843bdfacd4958eb260b11b4a19 --- /dev/null +++ b/xtrn/DDUploadProcessor/DDUP.cfg @@ -0,0 +1,24 @@ +; This is the general configuration file for Digital Distortion +; Upload Processor. This contains options for the processor +; itself. + +; In the scan command, the following specifiers can be used: +; Specifier Replaced with +; %FILESPEC% The name of the file/directory to scan +; +; The virus scanner should be able to take just a directory name (to +; scan that entire directory) so that archive files can be unpacked +; into a directory and scanned. +; +; This scan command is for AVG 9; you may need to change the path for your computer. +scanCmd="C:\Program Files\AVG\AVG9\avgscanx.exe" /SCAN=%FILESPEC% + +; The following option sets whether or not to pause for the user to +; enter a key after a file is scanned. Valid values are yes and +; no. +pauseAtEnd=no + +; If the following setting is set to yes, then all uploads by +; sysops will be automatically approved. Valid values are yes +; and no. +skipScanIfSysop=no \ No newline at end of file diff --git a/xtrn/DDUploadProcessor/DDUP.js b/xtrn/DDUploadProcessor/DDUP.js new file mode 100644 index 0000000000000000000000000000000000000000..96d1fd63371e2b1a2f6615820536441e27f401d2 --- /dev/null +++ b/xtrn/DDUploadProcessor/DDUP.js @@ -0,0 +1,1015 @@ +/* Name: Digital Distortion Upload Processor + * + * Description: This is a script for Synchronet that scans + * uploaded files with a virus scanner. Compressed archives are + * unpacked so that the files inside can be scanned by the virus + * scanner. + * + * Author: Eric Oulashin (AKA Nightfox) + * BBS: Digital Distortion + * BBS address: digdist.bbsindex.com + * + * Date User Description + * 2009-12-25- + * 2009-12-28 Eric Oulashin Initial development + * 2009-12-29 Eric Oulashin Version 1.00 + * Initial public release + */ + +/* Command-line arguments: + 1 (argv[0]): The name of the file to scan +*/ + +load("sbbsdefs.js"); + +// Determine the script's execution directory. +// This code is a trick that was created by Deuce, suggested by Rob +// Swindell as a way to detect which directory the script was executed +// in. I've shortened the code a little. +// Note: gStartupPath will include the trailing slash. +var gStartupPath = '.'; +try { throw dig.dist(dist); } catch(e) { gStartupPath = e.fileName; } +gStartupPath = backslash(gStartupPath.replace(/[\/\\][^\/\\]*$/,'')); + +load(gStartupPath + "DDUP_Cleanup.js"); + +// Version information +var gDDUPVersion = "1.00"; +var gDDUPVerDate = "2009-12-29"; + + +// If the filename was specified on the command line, then use that +// for the filename. Otherwise, read the name of the file to view +// from DDArcViewerFilename.txt in the node directory. +var gFileToScan = ""; +if (argv.length > 0) +{ + if (typeof(argv[0]) == "string") + { + // Make sure the arguments are correct (in case they have spaces), + // then use the first one. + var fixedArgs = fixArgs(argv); + if ((typeof(fixedArgs[0]) == "string") && (fixedArgs[0].length > 0)) + gFileToScan = fixedArgs[0]; + else + { + console.print("nyhError: ncBlank filename argument given.\r\np"); + exit(-2); + } + } + else + { + console.print("nyhError: ncUnknown command-line argument specified.\r\np"); + exit(-1); + } +} +else +{ + // Read the filename from DDArcViewerFilename.txt in the node directory. + // This is a workaround for file/directory names with spaces in + // them, which would get separated into separate command-line + // arguments for JavaScript scripts. + var filenameFileFilename = system.node_dir + "DDArcViewerFilename.txt"; + var filenameFile = new File(filenameFileFilename); + if (filenameFile.open("r")) + { + if (!filenameFile.eof) + gFileToScan = filenameFile.readln(2048); + filenameFile.close(); + } +} + +// Make sure the slashes in the filename are correct for the platform. +if (gFileToScan.length > 0) + gFileToScan = fixPathSlashes(gFileToScan); + +// A filename must be specified as the first argument, so give an error and return +// if not. +if (gFileToScan.length == 0) +{ + console.print("nyhError: ncNo filename specified to process.\r\np"); + exit(1); +} + +// Create the global configuration objects. +var gGenCfg = new Object(); +gGenCfg.scanCmd = ""; +gGenCfg.skipScanIfSysop = false; +gGenCfg.pauseAtEnd = false; +var gFileTypeCfg = new Object(); + +// Read the configuration files to populate the global configuration object. +var configFileRead = ReadConfigFile(gStartupPath); +// If the configuration files weren't read, then output an error and exit. +if (!configFileRead) +{ + console.print("nyhError: ncUpload processor is unable to read its\r\n"); + console.print("configuration files.\r\np"); + exit(2); +} +// Exit if there is no scan command. +if (gGenCfg.scanCmd.length == 0) +{ + console.print("nyhWarning: ncNo scan command configured for the upload processor.\r\n"); + exit(0); +} + +// Global variables +// Strings for the OK and failure symbols +var gOKStr = "nkh[ng�kh]n"; +var gOKStrWithNewline = gOKStr + "\r\n"; +var gFailStr = "nkh[rXk]n"; +var gFailStrWithNewline = gFailStr + "\r\n"; +// Stuff for the printf formatting string for the status messages +var gStatusTextLen = 79 - console.strlen(gOKStr); // gOKStr and gFailStr should have the same length +var gStatusPrintfStr = "n%s%-" + gStatusTextLen + "sn"; // For a color and the status text + +// Now, scan the file and return the appropriate return code. +exit(main()); +// End of script execution. + + +// This is the "main" function that contains the main code +// for the script. +// +// Return value: An integer to return upon script exit. +function main() +{ + // Output the program name & version information + console.print("n\r\nchDncigital hDncistortion hUncpload hPncrocessor whvng" + + gDDUPVersion); + // Originally I had this script output the version date, but now I'm not sure + // if I want to do that.. + //console.print(" wh(b" + gDDUPVerDate + "w)"); + console.print("n"); + console.crlf(); + + // Process the file + var exitCode = processFile(gFileToScan); + // Depending on the exit code, display a success or failure message. + console.crlf(); + if (exitCode == 0) + console.print(gOKStr + " nbhScan successful - The file passed.\r\n"); + else + console.print(gFailStr + " nyhScan failed!\r\n"); + + // If the option to pause at the end is enabled, then prompt the user for + // a keypress. + if (gGenCfg.pauseAtEnd) + { + console.print("nwhPress any key to continue:n"); + console.getkey(K_NOECHO); + } + + return exitCode; +} + + +////////////////////////////////////////////////////////////////////////////////// +// Object stuff + +// Constructor for the ScannableFile object, which contains information +// about a viewable file. +// +// Parameters: +// pExtension: The filename extension +// pViewCmd: The OS command to view it +// +// The ScannableFile object contains the following properties: +// extension: The filename extension +// pExtractCmd: The OS command to extract it (if applicable) +// pScanOption: A string containing a scan option. The following are valid: +// "scan": Always scan the file using the scan commdn +// "always pass": Don't scan the file, and assume it's good +// "always fail": Don't scan the file, and assume it's bad +function ScannableFile(pExtension, pExtractCmd, pScanOption) +{ + this.extension = ""; // The archive filename extension + this.extractCmd = ""; // The command to extract the archive (if applicable) + this.scanOption = "scan"; // The scan option ("scan", "always pass", "always fail") + + // If the parameters are valid, then use them to set the object properties. + if ((pExtension != null) && (pExtension != undefined) && (typeof(pExtension) == "string")) + this.extension = pExtension; + if ((pExtractCmd != null) && (pExtractCmd != undefined) && (typeof(pExtractCmd) == "string")) + this.extractCmd = pExtractCmd; + if ((pScanOption != null) && (pScanOption != undefined) && (typeof(pScanOption) == "string")) + this.scanOption = pScanOption; +} + + +///////////////////////////////////////////////////////////////////////////////////////////// +// Functions + +// This function fixes an array of command-line arguments so that +// arguments with spaces in them are a single argument. This function +// was written by Tracker1 of The Roughnecks BBS - He posted the code +// in the DOVE-Net Synchronet Discussion sub-board on December 20, 2009. +function fixArgs(input) +{ + var patt1 = /\"[^\"]*\"|\S+/g; + var patt2 = /^\"?([^\"]*)\"?$/; + return input.join(' ').match(patt1).map(function(item) + { + return item.replace(patt2, "$1") + }); +} + +// Scans a file. +// +// Parameters: +// pFilename: The name of the file to scan +// +// Return value: A return code from scanning the file. 0 means success; +// non-zero means failure. +function processFile(pFilename) +{ + // Display the program header stuff - The name of the file being scanned + // and the status header line + var justFilename = getFilenameFromPath(pFilename); + console.print("nwhScanning b" + justFilename.substr(0, 70)); + console.print("n\r\nb7 File Scan Status n\r\n"); + + // If the skipScanIfSysop option is enabled and the user is a sysop, + // then assume the file is good. + if (gGenCfg.skipScanIfSysop && user.compare_ars("SYSOP")) + { + printf(gStatusPrintfStr, "gh", "Auto-approving the file (you're a sysop)"); + console.print(gOKStrWithNewline); + return 0; + } + + var retval = 0; + + // Look for the file extension in gFileTypeCfg to get the file scan settings. + // If the file extension is not there, then go ahead and scan it (to be on the + // safe side). + var filenameExtension = getFilenameExtension(pFilename); + if (typeof(gFileTypeCfg[filenameExtension]) != "undefined") + { + if (gFileTypeCfg[filenameExtension].scanOption == "scan") + { + // - If the file has an extract command, then: + // Extract the file to a temporary directory in the node dir + // For each file in the directory: + // If it's a subdir + // Recurse into it + // else + // Scan it for viruses + // If non-zero retval + // Return with error code + var filespec = pFilename; + if (gFileTypeCfg[filenameExtension].extractCmd.length > 0) + { + // Create the base work directory for this script in the node dir. + // And just in case that dir already exists, remove it before + // creating it. + var baseWorkDir = system.node_dir + "DDUploadProcessor_Temp"; + deltree(baseWorkDir + "/"); + if (!mkdir(baseWorkDir)) + { + console.print("nyhWarning: nwh Unable to create the work dir.n\r\n"); + retval = -1; + } + + // If all is okay, then create the directory in the temporary work dir. + var workDir = baseWorkDir + "/" + justFilename + "_temp"; + if (retval == 0) + { + deltree(workDir + "/"); + if (!mkdir(workDir)) + { + console.print("nyhWarning: nwh Unable to create a dir in the temporary work dir.n\r\n"); + retval = -1; + } + } + + // If all is okay, we can now process the file. + if (retval == 0) + { + // Extract the file to the work directory + printf(gStatusPrintfStr, "mh", "Extracting the file..."); + var errorStr = extractFileToDir(pFilename, workDir); + if (errorStr.length == 0) + { + console.print(gOKStrWithNewline); + // Scan the files in the work directory. + printf(gStatusPrintfStr, "r", "Scanning files inside the archive for viruses..."); + var retObj = scanFilesInDir(workDir); + retval = retObj.returnCode; + if (retObj.returnCode == 0) + console.print(gOKStrWithNewline); + else + { + console.print(gFailStrWithNewline); + console.print("nyhVirus scan failed. Scan output:n\r\n"); + for (var index = 0; index < retObj.cmdOutput.length; ++index) + { + console.print(retObj.cmdOutput[index]); + console.crlf(); + } + } + } + else + { + console.print(gFailStrWithNewline); + // Scan the files in the work directory. + console.print("nyhWarning: nwh Unable to extract to work dir.n\r\n"); + retval = -2; + } + } + // Remove the work directory. + deltree(baseWorkDir + "/"); + } + else + { + // The file has no extract command, so just scan it. + printf(gStatusPrintfStr, "bh", "Scanning..."); + var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pFilename) + "\""); + // Run the scan command and capture its output, in case the scan fails. + var retObj = runExternalCmdWithOutput(scanCmd); + retval = retObj.returnCode; + if (retObj.returnCode == 0) + console.print(gOKStrWithNewline); + else + { + console.print(gFailStrWithNewline); + console.print("nyhVirus scan failed. Scan output:n\r\n"); + for (var index = 0; index < retObj.cmdOutput.length; ++index) + { + console.print(retObj.cmdOutput[index]); + console.crlf(); + } + } + } + } + else if (gFileTypeCfg[filenameExtension].scanOption == "always fail") + exitCode = 10; + } + else + { + // There's nothing configured for the file's extension, so just scan it. + printf(gStatusPrintfStr, "r", "Scanning..."); + var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pFilename) + "\""); + var retObj = runExternalCmdWithOutput(scanCmd); + retval = retObj.returnCode; + if (retObj.returnCode == 0) + console.print(gOKStrWithNewline); + else + { + console.print(gFailStrWithNewline); + console.print("nyhVirus scan failed. Scan output:n\r\n"); + for (var index = 0; index < retObj.cmdOutput.length; ++index) + { + console.print(retObj.cmdOutput[index]); + console.crlf(); + } + } + } + + return retval; +} + +// Recursively scans the files in a directory using the scan command in +// gGencfg. +// +// Parameters: +// pDir: The directory to scan +// +// Return value: 0 on success, or non-zero on error. +// Return value: An object containing the following properties: +// returnCode: The return code of the last scan command called in the +// OS (0 is good, non-zero is failure). +// cmdOutput: An array of strings containing the output from the last +// file scan. +function scanFilesInDir(pDir) +{ + // If pDir is unspecified, then just return. + if (typeof(pDir) != "string") + { + var retObj = new Object(); + retObj.cmdOutput = new Array(); + retObj.returnCode = -1; + return retObj; + } + if (pDir.length == 0) + { + var retObj = new Object(); + retObj.cmdOutput = new Array(); + retObj.returnCode = -2; + return retObj; + } + // Also, just return if gGenCfg.scanCmd is blank. + if (gGenCfg.scanCmd.length == 0) + { + var retObj = new Object(); + retObj.cmdOutput = new Array(); + retObj.returnCode = -3; + return retObj; + } + + // If the filename has a trailing slash, remove it. + if ((/\/$/.test(pDir)) || (/\\$/.test(pDir))) + pDir = pDir.substr(0, pDir.length-1); + + var retObj = null; // Will be used to capture the return from the scan commands + + // If the virus scan command contains %FILESPEC%, then + // replace %FILESPEC% with pDir and run the scan command. + if (gGenCfg.scanCmd.indexOf("%FILESPEC%") > -1) + { + var scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(pDir) + "\""); + retObj = runExternalCmdWithOutput(scanCmd); + + // This is old code, for scanning each file individually (slow): + /* + // Get a list of the files, and scan them. + var files = directory(pDir + "/*"); + if (files.length > 0) + { + var scanCmd = null; // Will be used for the scan commands (string) + var counter = 0; // Loop variable + for (var i in files) + { + // If the file is a directory, then recurse into it. Otherwise, + // scan the file using the configured scan command. + if (file_isdir(files[i])) + retObj = scanFilesInDir(files[i]); + else + { + scanCmd = gGenCfg.scanCmd.replace("%FILESPEC%", "\"" + fixPathSlashes(files[i]) + "\""); + // Run the scan command and capture its output, in case the scan fails. + retObj = runExternalCmdWithOutput(scanCmd); + } + + // If there's a problem, then stop going through the list of files. + if (retObj.returnCode != 0) + break; + } + } + else + { + // There are no files. So create retObj with default settings + // for a good result. + retObj = new Object(); + retObj.returnCode = 0; + retObj.cmdOutput = new Array(); + } + */ + } + else + { + // gGenCfg.scanCmd doesn't contain %FILESPEC%, so set up + // retObj with a non-zero return code (for failure) + retObj = new Object(); + retObj.returnCode = -4; + retObj.cmdOutput = new Array(); + retObj.cmdOutput.push("The virus scanner is not set up correctly."); + } + + return retObj; +} + +// Reads the configuration file and returns an object containing the +// configuration settings. +// +// Parameters: +// pCfgFilePath: The path from which to load the configuration file. +// +// Return value: Boolean - Whether or not the configuration was read. +function ReadConfigFile(pCfgFilePath) +{ + // Read the file type settings. + var fileTypeSettingsRead = false; + var fileTypeCfgFile = new File(pCfgFilePath + "DDUPFileTypes.cfg"); + if (fileTypeCfgFile.open("r")) + { + if (fileTypeCfgFile.length > 0) + { + fileTypeSettingsRead = true; + // Read each line from the config file and set the + // various options. + var pos = 0; // Index of = in the file lines + var fileLine = ""; + var filenameExt = ""; // Archive filename extension + var option = ""; // Configuration option + var optionValue = ""; // Configuration option value + var optionValueUpper; // Upper-cased configuration option value + var scannableFile = null; // Will be used to create & store scannable file options + while (!fileTypeCfgFile.eof) + { + // Read the line from the config file, look for a =, and + // if found, read the option & value and set them + // in cfgObj. + fileLine = fileTypeCfgFile.readln(1024); + + // fileLine should be a string, but I've seen some cases + // where it isn't, so check its type. + if (typeof(fileLine) != "string") + continue; + + // If the line is blank or starts with with a semicolon + // (the comment character), then skip it. + if ((fileLine.length == 0) || (fileLine.substr(0, 1) == ";")) + continue; + + // Look for a file extension in square brackets ([ and ]). + // If found, then set filenameExt and continue onto the next line. + // Note: This regular expression allows whitespace around the [...]. + if (/^\s*\[.*\]\s*$/.test(fileLine)) + { + var startIndex = fileLine.indexOf("[") + 1; + var endIndex = fileLine.lastIndexOf("]"); + var ext = fileLine.substr(startIndex, endIndex-startIndex).toUpperCase(); + // If the filename extension is different than the last one + // we've seen, then: + // 1. If scannableFile is not null, then add it to gScannableFileTypes. + // 2. Create a new one (referenced as scannableFile). + if (ext != filenameExt) + { + if ((scannableFile != null) && (scannableFile != undefined) && + (filenameExt.length > 0)) + { + gFileTypeCfg[filenameExt] = scannableFile; + } + filenameExt = ext; + scannableFile = new ScannableFile(ext, "", "scan"); + } + continue; + } + + // If filenameExt is blank, then continue onto the next line. + if (filenameExt.length == 0) + continue; + + // If we're here, then filenameExt is set, and this is a valid + // line to process. + // Look for an = in the line, and if found, split into + // option & value. + pos = fileLine.indexOf("="); + if (pos > -1) + { + // Extract the option & value, trimming leading & trailing spaces. + option = trimSpaces(fileLine.substr(0, pos), true, false, true).toUpperCase(); + optionValue = trimSpaces(fileLine.substr(pos+1), true, false, true); + + if (option == "EXTRACT") + scannableFile.extractCmd = optionValue; + else if (option == "SCANOPTION") + scannableFile.scanOption = optionValue; + } + } + } + + fileTypeCfgFile.close(); + } + + // Read the general program configuration + var genSettingsRead = false; + var genCfgFile = new File(pCfgFilePath + "DDUP.cfg"); + if (genCfgFile.open("r")) + { + if (genCfgFile.length > 0) + { + genSettingsRead = true; + var fileLine = null; // A line read from the file + var equalsPos = 0; // Position of a = in the line + var commentPos = 0; // Position of the start of a comment + var setting = null; // A setting name (string) + var settingUpper = null; // Upper-case setting name + var value = null; // A value for a setting (string) + while (!genCfgFile.eof) + { + // Read the next line from the config file. + fileLine = genCfgFile.readln(1024); + + // fileLine should be a string, but I've seen some cases + // where it isn't, so check its type. + if (typeof(fileLine) != "string") + continue; + + // If the line starts with with a semicolon (the comment + // character) or is blank, then skip it. + if ((fileLine.substr(0, 1) == ";") || (fileLine.length == 0)) + continue; + + // If the line has a semicolon anywhere in it, then remove + // everything from the semicolon onward. + commentPos = fileLine.indexOf(";"); + if (commentPos > -1) + fileLine = fileLine.substr(0, commentPos); + + // Look for an equals sign, and if found, separate the line + // into the setting name (before the =) and the value (after the + // equals sign). + equalsPos = fileLine.indexOf("="); + if (equalsPos > 0) + { + // Read the setting & value, and trim leading & trailing spaces. + setting = trimSpaces(fileLine.substr(0, equalsPos), true, false, true); + settingUpper = setting.toUpperCase(); + value = trimSpaces(fileLine.substr(equalsPos+1), true, false, true); + + // Skip this one if the value is blank. + if (value.length == 0) + continue; + + // Set the appropriate value in the settings object. + if (settingUpper == "SCANCMD") + gGenCfg.scanCmd = value; + else if (settingUpper == "SKIPSCANIFSYSOP") + gGenCfg.skipScanIfSysop = (value.toUpperCase() == "YES"); + else if (settingUpper == "PAUSEATEND") + gGenCfg.pauseAtEnd = (value.toUpperCase() == "YES"); + } + } + + genCfgFile.close(); + } + } + + return (fileTypeSettingsRead && genSettingsRead); +} + +// Removes multiple, leading, and/or trailing spaces +// The search & replace regular expressions used in this +// function came from the following URL: +// http://qodo.co.uk/blog/javascript-trim-leading-and-trailing-spaces +// +// Parameters: +// pString: The string to trim +// pLeading: Whether or not to trim leading spaces (optional, defaults to true) +// pMultiple: Whether or not to trim multiple spaces (optional, defaults to true) +// pTrailing: Whether or not to trim trailing spaces (optional, defaults to true) +function trimSpaces(pString, pLeading, pMultiple, pTrailing) +{ + var leading = true; + var multiple = true; + var trailing = true; + if (typeof(pLeading) != "undefined") + leading = pLeading; + if (typeof(pMultiple) != "undefined") + multiple = pMultiple; + if (typeof(pTrailing) != "undefined") + trailing = pTrailing; + + // To remove both leading & trailing spaces: + //pString = pString.replace(/(^\s*)|(\s*$)/gi,""); + + if (leading) + pString = pString.replace(/(^\s*)/gi,""); + if (multiple) + pString = pString.replace(/[ ]{2,}/gi," "); + if (trailing) + pString = pString.replace(/(\s*$)/gi,""); + + return pString; +} + +// Returns a filename's extension. Always returns a string. +// +// Parameters: +// pFilename: The name of a file +// +// Return value: The filename's extension, or blank if there is none. +function getFilenameExtension(pFilename) +{ + const filenameUpper = pFilename.toUpperCase(); + var filenameExt = ""; + // Special case for .tar.gz - Report tar.gz as an extension + // rather than just gz + if (/.TAR.GZ$/.test(filenameUpper)) + filenameExt = "TAR.GZ"; + else + { + // Look for the last period in filenameUpper + var dotIndex = filenameUpper.lastIndexOf("."); + if (dotIndex > -1) + filenameExt = filenameUpper.substr(dotIndex+1); + } + return filenameExt; +} + +// This function returns just the filename from the end of a full path, regardless +// of whether it has a trailing slash. +// +// Parameters: +// pFilename: The full path & filename +// +// Return value: Just the filename from the end of the path +function getFilenameFromPath(pFilename) +{ + var filename = pFilename; + if (filename.length > 0) + { + // If the filename has a trailing slash, remove it. Then, + // use file_getname() to get the filename from the end. + if ((/\/$/.test(filename)) || (/\\$/.test(filename))) + filename = filename.substr(0, filename.length-1); + filename = file_getname(filename); + } + return filename; +} + +// Given a full path & filename, this function returns just the path portion, +// with a trailing slash. +// +// Parameters: +// pFilename: A filename with the full path +// +// Return value: Just the path portion of the filename. +function getPathFromFilename(pFilename) +{ + // Make sure pFilename is valid + if ((pFilename == null) || (pFilename == undefined)) + return ""; + if (typeof(pFilename) != "string") + return ""; + if (pFilename.length == 0) + return ""; + + // Determine which slash character to use for paths, depending + // on the OS. + if (getPathFromFilename.inWin == undefined) + getPathFromFilename.inWin = /^WIN/.test(system.platform.toUpperCase()); + var pathSlash = (getPathFromFilename.inWin ? "\\" : "/"); + + // Make sure the filename has the correct slashes for + // the platform. + var filename = fixPathSlashes(pFilename); + + // If pFilename is actually a directory, then just return it. + if (file_isdir(filename)) + { + // Make sure it has a trailing slash that's appropriate + // for the OS. + var lastChar = filename.charAt(filename.length-1); + if ((lastChar != "/") || (lastChar == "\\")) + filename += pathSlash; + return filename; + } + + // Find the index of the last slash and use that to extract the path. + var path = ""; + var lastSlashIndex = filename.lastIndexOf(pathSlash); + if (lastSlashIndex > 0) + path = filename.substr(0, lastSlashIndex); + + // If we extracted the path, make sure it ends with a slash. + if (path.length > 0) + { + var lastChar = path.charAt(path.length-1); + if (lastChar != pathSlash) + path += pathSlash; + } + + return path; +} + +// Fixes all slashes in a given path to be the appropriate slash +// character for the OS. Returns a new string with the fixed version. +// +// Parameters: +// pPath: A path to fix +// +// Return value: The fixed version of pPath +function fixPathSlashes(pPath) +{ + // Make sure pPath is valid. + if ((pPath == null) || (pPath == undefined)) + return ""; + if (typeof(pPath) != "string") + return ""; + if (pPath.length == 0) + return ""; + + // Create a variable to store whether or not we're in Windows, + // but only once (for speed). + if (fixPathSlashes.inWin == undefined) + fixPathSlashes.inWin = /^WIN/.test(system.platform.toUpperCase()); + + // Fix the slashes and return the fixed version. + //return(fixPathSlashes.inWin ? pPath.replace("/", "\\") : pPath.replace("\\", "/")); + var path = pPath; + if (fixPathSlashes.inWin) // Windows + { + while (path.indexOf("/") > -1) + path = path.replace("/", "\\"); + } + else // *nix + { + while (path.indexOf("\\") > -1) + path = path.replace("\\", "/"); + } + return path; +} + +// This function extracts a file to a directory. +// +// Parameters: +// pFilename: The name of the file to extract +// pWorkDir: The directory to extract the file into. This directory must +// exist before calling this function. +// +// Return value: A blank string on success, or an error message on failure. +function extractFileToDir(pFilename, pWorkDir) +{ + // If pFilename doesn't exist, then return with an error. + if (typeof(pFilename) != "string") + return ("Invalid filename specified."); + if (pFilename.length == 0) + return ("No filename specified."); + if (!file_exists(pFilename)) + return ("The specified file does not exist."); + + // If pWorkDir is blank, then return with an error. + if (typeof(pWorkDir) != "string") + return ("Unknown argument specified for the work directory."); + if (pWorkDir.length == 0) + return ("No work directory specified."); + + // If pWorkDir ends with a slash, remove it. + if ((/\/$/.test(pWorkDir)) || (/\\$/.test(pWorkDir))) + pWorkDir = pWorkDir.substr(0, pWorkDir.length-1); + + // If the work directory doesn't exist, then return with + // an error. + // Note: file_exists() doesn't seem to work properly with directories. + //if (!file_exists(pWorkDir)) + // return ("The work directory doesn't exist."); + + var filenameExt = getFilenameExtension(pFilename); + // Return with errors if there are problems. + if (filenameExt.length == 0) + return ("Can't extract (no file extension)."); + if (typeof(gFileTypeCfg[filenameExt]) == "undefined") + return ("Can't extract " + getFilenameFromPath(pFilename) + " (I don't know how)."); + if (gFileTypeCfg[filenameExt].extractCmd == "") + return ("Can't extract " + getFilenameFromPath(pFilename) + " (I don't know how)."); + + var retval = ""; + + // Extract the file to the work directory. + var extractCmd = gFileTypeCfg[filenameExt].extractCmd.replace("%FILENAME%", "\"" + fixPathSlashes(pFilename) + "\""); + extractCmd = extractCmd.replace("%FILESPEC% ", ""); + extractCmd = extractCmd.replace("%TO_DIR%", "\"" + fixPathSlashes(pWorkDir) + "\""); + var retCode = system.exec(extractCmd); + if (retCode != 0) + return ("Extract failed with exit code " + retCode); + // For each file in the work directory: + // If the file has an extract command + // Extract it to a subdir in the temp dir + // Delete the archive + var files = directory(pWorkDir + "/*"); + for (var i in files) + { + // If the file has an extract command, then extract it to a + // temp directory in the work directory. + filenameExt = getFilenameExtension(files[i]); + if ((typeof(gFileTypeCfg[filenameExt]) != "undefined") && + ((gFileTypeCfg[filenameExt].extractCmd != ""))) + { + // Create the temp directory and extract the file there. + var workDir = pWorkDir + "/" + getFilenameFromPath(files[i] + "_temp"); + if (mkdir(workDir)) + retval = extractFileToDir(files[i], workDir); + else + retval = "Unable to create a temporary directory."; + + // If there was no problem, then delete the archive file. Otherwise, + // stop going through the list of files. + if (retval.length == 0) + file_remove(files[i]); + else + break; + } + } + + return retval; +} + +// This function executes an OS command and returns its output as an +// array of strings. The reason this function was written is that +// system.popen() is only functional in UNIX. +// +// Parameters: +// pCommand: The command to execute +// +// Return value: An object containing the following properties: +// returnCode: The return code of the OS command +// cmdOutput: An array of strings containing the program's output. +function execCmdWithOutput(pCommand) +{ + var retObj = new Object(); + retObj.returnCode = 0; + retObj.cmdOutput = new Array(); + + if ((pCommand == undefined) || (pCommand == null) || (typeof(pCommand) != "string")) + return retObj; + + // Execute the command and redirect the output to a file in the + // node's directory. system.exec() returns the return code that the + // command returns; generally, 0 means success and non-zero means + // failure (or an error of some sort). + const tempFilename = system.node_dir + "DDUPCommandOutput_temp.txt"; + retObj.returnCode = system.exec(pCommand + " >" + tempFilename + " 2>&1"); + // Read the temporary file and populate retObj.cmdOutput with its + // contents. + var tempFile = new File(tempFilename); + if (tempFile.open("r")) + { + if (tempFile.length > 0) + { + var fileLine = null; + while (!tempFile.eof) + { + fileLine = tempFile.readln(2048); + + // fileLine should be a string, but I've seen some cases + // where it isn't, so check its type. + if (typeof(fileLine) != "string") + continue; + + retObj.cmdOutput.push(fileLine); + } + } + + tempFile.close(); + } + + // Remove the temporary file, if it exists. + if (file_exists(tempFilename)) + file_remove(tempFilename); + + return retObj; +} + +// Runs an external command. This function was written because +// I want to be able to handle executable files with spaces in +// their name/path (system.exec() doesn't handle said spaces). +// +// Parameters: +// pCommand: The command to execute +// +// Return value: An object containing the following properties: +// returnCode: The return code of the OS command +// cmdOutput: An array of strings containing the program's output. +function runExternalCmdWithOutput(pCommand) +{ + // Determine whether or not we're in Windows. + if (runExternalCmdWithOutput.inWin == undefined) + runExternalCmdWithOutput.inWin = /^WIN/.test(system.platform.toUpperCase()); + + var retObj = null; // The return object + var wroteScriptFile = false; // Whether or not we were able to write the script file + + // In the node directory, write a batch file (if in Windows) or a *nix shell + // script (if not in Windows) containing the command to run. + var scriptFilename = ""; + if (runExternalCmdWithOutput.inWin) + { + // Write a Windows batch file to run the command + scriptFilename = fixPathSlashes(system.node_dir + "DDUP_ScanCmd.bat"); + //console.print(":" + scriptFilename + ":\r\n\1p"); // Temporary (for debugging) + var scriptFile = new File(scriptFilename); + if (scriptFile.open("w")) + { + scriptFile.writeln("@echo off"); + scriptFile.writeln(pCommand); + scriptFile.close(); + wroteScriptFile = true; + retObj = execCmdWithOutput(scriptFilename); + } + } + else + { + // Write a *nix shell script to run the command + scriptFilename = system.node_dir + "DDUP_ScanCmd.sh"; + var scriptFile = new File(scriptFilename); + if (scriptFile.open("w")) + { + scriptFile.writeln("#!/bin/bash"); // Hopefully /bin/bash is valid on the system! + scriptFile.writeln(pCommand); + scriptFile.close(); + wroteScriptFile = true; + system.exec("chmod ugo+x " + scriptFilename); + retObj = execCmdWithOutput("bash " + scriptFilename); + } + } + + // Remove the script file, if it exists + if (file_exists(scriptFilename)) + file_remove(scriptFilename); + + // If we were unable to write the script file, then create retObj with + // a returnCode indicating failure. + if (!wroteScriptFile) + { + // Could not open the script file for writing + retObj = new Object(); + retObj.cmdOutput = new Array(); + retObj.returnCode = -1; + } + + return retObj; +} \ No newline at end of file diff --git a/xtrn/DDUploadProcessor/DDUPFileTypes.cfg b/xtrn/DDUploadProcessor/DDUPFileTypes.cfg new file mode 100644 index 0000000000000000000000000000000000000000..bb73a127f6f93757b5a5ecc82b343d928eb13997 --- /dev/null +++ b/xtrn/DDUploadProcessor/DDUPFileTypes.cfg @@ -0,0 +1,201 @@ +; This is the file type configuration for Digital Distortion Upload Processor. + +; Compressed archive file extensions +[ZIP] +scanOption=scan +; PKZip for Win32 +;EXTRACT=\BBS\COMPRESS\pkzip25.exe -extract -NoZipExtension -Directories %FILENAME% %FILESPEC% %TO_DIR% +; Info-ZIP for Win32 console (comes with Synchronet) +EXTRACT=\BBS\sbbs\exec\unzip.exe -qq -o %FILENAME% %FILESPEC% -d %TO_DIR% +; Info-ZIP, *nix +;EXTRACT=unzip -qq -o %FILENAME% %FILESPEC% -d %TO_DIR% +; 7-Zip for Win32 console +;EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +[7Z] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +[RAR] +scanOption=scan +; RAR for Win32 console +EXTRACT=\BBS\COMPRESS\Rar.exe x -p- -y %FILENAME% %FILESPEC% %TO_DIR% +; Alexander Roshal's unrar for *nix +;EXTRACT=unrar x %FILENAME% %FILESPEC% %TO_DIR% + +[ARJ] +scanOption=scan +; ARJ for Win32 console +EXTRACT=\BBS\COMPRESS\ARJ32.EXE x -y %FILENAME% %FILESPEC% -ht%TO_DIR% +; Open-Source ARJ for *nix +;EXTRACT=arj x %FILENAME% %FILESPEC% -ht%TO_DIR% + +[ISO] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +[TAR] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +[GZ] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +[TGZ] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +[TAR.GZ] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; tar for *nix +;EXTRACT=cd %TO_DIR% && tar zxvf %FILENAME% + +; Microsoft Installer file +[MSI] +scanOption=scan +; 7-Zip for Win32 console +EXTRACT=\BBS\COMPRESS\7za.exe x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% +; 7-Zip for *nix +;EXTRACT=7za x -y -pxyz %FILENAME% %FILESPEC% -o%TO_DIR% + +; Text file extensions +[TXT] +scanOption=always pass + +[DIZ] +scanOption=always pass + +[DOC] +scanOption=scan + +[ANS] +scanOption=always pass + +[ASC] +scanOption=always pass + +[ASCII] +scanOption=always pass + +[RIP] +scanOption=always pass + +[NFO] +scanOption=always pass + +; Chinese text file +[ZW] +scanOption=always pass + +[NEW] +scanOption=always pass + +[BBS] +scanOption=always pass + +[ICE] +scanOption=always pass + +[LOG] +scanOption=always pass + +; Guitar tab file +[TAB] +scanOption=always pass + +; Guitar tab file +[CRD] +scanOption=always pass + +; Guitar tab file +[CHORD] +scanOption=always pass + +; Readme file (i.e., READ.ME) +[ME] +scanOption=always pass + +[FAQ] +scanOption=always pass + +[NOW] +scanOption=always pass + +[HTM] +scanOption=always pass + +[HTML] +scanOption=always pass + +[REG] +scanOption=scan + +; A readme file, as in README.1ST +[1ST] +scanOption=always pass + +[CFG] +scanOption=always pass + +[INI] +scanOption=always pass + +; Batch file +[BAT] +scanOption=always pass + +; Command list file (similar to a DOS batch file) +[CMD] +scanOption=always pass + +; REXX script (a programming language developed by IBM) +[REXX] +scanOption=always pass + +; *nix shell script +[SH] +scanOption=always pass + +; Outlook Express email message +[EMAIL] +scanOption=scan + +; Mail message +[EMLX] +scanOption=scan + +; EditPad Pro +[EPP] +scanOption=always pass + +; Error log +[ERR] +scanOption=always pass + +; Outlook Express mailbox index file +[IDX] +scanOption=scan + +; Data list +[LST] +scanOption=always pass \ No newline at end of file diff --git a/xtrn/DDUploadProcessor/DDUP_Cleanup.js b/xtrn/DDUploadProcessor/DDUP_Cleanup.js new file mode 100644 index 0000000000000000000000000000000000000000..7a8bf12b24febdc01e6a95ede7a5801da7424dea --- /dev/null +++ b/xtrn/DDUploadProcessor/DDUP_Cleanup.js @@ -0,0 +1,119 @@ +/* This is a cleanup script for Digital Distortion Upload Processor. + * This script cleans up temporary files & directories from the + * node directory used by Digital Distortion Upload Processor. + * + * Author: Eric Oulashin (AKA Nightfox) + * BBS: Digital Distortion + * BBS address: digdist.bbsindex.com + * + * Date User Description + * 2009-12-26 Eric Oulashin Created + * 2009-12-28 Eric Oulashin Added removal of DDUPCommandOutput_temp.txt + * and DDUP_ScanCmd.* + */ + +load("sbbsdefs.js"); + +// Remove the temporary work directory used by Digital Distortion +// Upload Processor. +deltree(system.node_dir + "DDUploadProcessor_Temp"); +// Remove DDUPCommandOutput_temp.txt from the node directory +file_remove(system.node_dir + "DDUPCommandOutput_temp.txt"); +// Remove the command script from the node directory +file_remove(system.node_dir + "DDUP_ScanCmd.*"); + + +///////////////////////////////////////////////////////////////////////////////// +// Functions + +// This function recursively removes a directory and all of its contents. Returns +// whether or not the directory was removed. +// +// Parameters: +// pDir: The directory to remove (with trailing slash). +// +// Return value: Boolean - Whether or not the directory was removed. +function deltree(pDir) +{ + if ((pDir == null) || (pDir == undefined)) + return false; + if (typeof(pDir) != "string") + return false; + if (pDir.length == 0) + return false; + // Make sure pDir actually specifies a directory. + if (!file_isdir(pDir)) + return false; + // Don't wipe out a root directory. + if ((pDir == "/") || (pDir == "\\") || (/:\\$/.test(pDir)) || (/:\/$/.test(pDir)) || (/:$/.test(pDir))) + return false; + + // If we're on Windows, then use the "RD /S /Q" command to delete + // the directory. Otherwise, assume *nix and use "rm -rf" to + // delete the directory. + if (deltree.inWindows == undefined) + deltree.inWindows = (/^WIN/.test(system.platform.toUpperCase())); + if (deltree.inWindows) + system.exec("RD " + withoutTrailingSlash(pDir) + " /s /q"); + else + system.exec("rm -rf " + withoutTrailingSlash(pDir)); + // The directory should be gone, so we should return true. I'd like to verify that the + // directory really is gone, but file_exists() seems to return false for directories, + // even if the directory does exist. So I test to make sure no files are seen in the dir. + return (directory(pDir + "*").length == 0); + + /* + // Recursively deleting each file & dir using JavaScript: + var retval = true; + + // Open the directory and delete each entry. + var files = directory(pDir + "*"); + for (var i = 0; i < files.length; ++i) + { + // If the entry is a directory, then deltree it (Note: The entry + // should have a trailing slash). Otherwise, delete the file. + // If the directory/file couldn't be removed, then break out + // of the loop. + if (file_isdir(files[i])) + { + retval = deltree(files[i]); + if (!retval) + break; + } + else + { + retval = file_remove(files[i]); + if (!retval) + break; + } + } + + // Delete the directory specified by pDir. + if (retval) + retval = rmdir(pDir); + + return retval; + */ +} + +// Removes a trailing (back)slash from a path. +// +// Parameters: +// pPath: A directory path +// +// Return value: The path without a trailing (back)slash. +function withoutTrailingSlash(pPath) +{ + if ((pPath == null) || (pPath == undefined)) + return ""; + + var retval = pPath; + if (retval.length > 0) + { + var lastIndex = retval.length - 1; + var lastChar = retval.charAt(lastIndex); + if ((lastChar == "\\") || (lastChar == "/")) + retval = retval.substr(0, lastIndex); + } + return retval; +} \ No newline at end of file diff --git a/xtrn/DDUploadProcessor/FILE_ID.DIZ b/xtrn/DDUploadProcessor/FILE_ID.DIZ new file mode 100644 index 0000000000000000000000000000000000000000..3f8d0408fd33b8e1a0189362687be72d8227f801 --- /dev/null +++ b/xtrn/DDUploadProcessor/FILE_ID.DIZ @@ -0,0 +1,9 @@ +nhc Digital Distortion Upload Processor yvw1.00 +hy For Synchronet 3.14+ +k��������������������������������������������� +ncThis is an upload processor for Synchronet +that allows for performing a virus scan on +files inside of archives. File extraction +commands and the virus scan command are +configurable. +bhRelease date: 2009-12-29 \ No newline at end of file diff --git a/xtrn/DDUploadProcessor/Read Me.txt b/xtrn/DDUploadProcessor/Read Me.txt new file mode 100644 index 0000000000000000000000000000000000000000..7ae3b2cce93b2e4d466068f75b8795ec1a15590c --- /dev/null +++ b/xtrn/DDUploadProcessor/Read Me.txt @@ -0,0 +1,343 @@ + Digital Distortion Upload Processor + Version 1.00 + Release date: 2009-12-29 + + by + + Eric Oulashin + Sysop of Digital Distortion BBS + BBS internet address: digitaldistortionbbs.com + digdist.bbsindex.com + Email: eric.oulashin@gmail.com + + + +This file describes the Digital Distortion Upload Processor. + +Contents +======== +1. Disclaimer +2. Introduction +3. Archive File Software +4. Installation and Setup +5. Main configuration file +6. Archive file type configuration file + + +1. Disclaimer +============= +The only guarantee that I can make about Digital Distortion Upload Processor +is that it will take up space on your computer. I have tested this with +the Windows verison of Synchronet and with the Windows version of AVG Free +(virus scanner) on my BBS, running in Windows 2000; this script has not +been tested with Linux Synchronet platforms. I created this script because +I felt that it would be useful and am providing it to the Synchronet BBS +community in case other Synchronet sysops might find it useful. + + +2. Introduction +=============== +Digital Distortion Upload Processor is a script makes use of a virus scanner +to scan uploaded files, with the added ability to extract compressed files +in order to scan the files inside the compressed file. + +File formats can be specified and configured via a configuration file, +including extraction commands for compressed files. In addition, the +virus scan command can be configured in the main configuration file, +which should allow for the use of any virus scanner, as long as it is a +command-line scanner (no GUI) and is able to take a subdirectory as a +command-line parameter. + +Compressed (archive) files will be extracted to a temporary directory in the +node directory after they are uploaded. Furthermore, compressed files +within the compressed file will be extracted to subdirectories within that +subdirectory, and any compressed files inside those compressed files will +be extracted, etc.. This way, all files inside of the archive can be +scanned by the virus scanner. + +The temporary directory created in the node directory has the following +name: +DDUploadProcessor_Temp +Additionally, the following temporary files are created in the node +directory: +DDUPCommandOutput_temp.txt +DDUP_ScanCmd.bat (for Win32 systems) or DDUP_ScanCmd.sh (for *nix systems) + +The temporary files and temporary directory will be removed when the +script finishes; however, in cases where they aren't removed (i.e., if the +user disconnects during the scan), a cleanup script is also included, which +can be executed in your logon and logoff scripts to ensure that the node +directory does not contain the temporary files. + +Detection of viruses will reject the uploaded file. Also, failure to +extract an archive (and thus, inability to scan for viruses) will cause the +uploaded file to be rejected. + + +3. Archive File Software +======================== +Digital Distortion Upload Processor comes with configuration settings to +handle extraction of ZIP, 7Z (7-Zip), RAR, ARJ, MSI, TAR, GZ, TGZ, and +TAR.GZ archives. + +The file format configuration file included with this script includes +extraction command lines (specified by an EXTRACT setting) for various +archivers for both Windows and Linux. In order for this script to work +properly, you will need to make sure you have the appropriate archiver +software installed on your system, and you will need to edit the +DDUPFileTypes.cfg file (using a text editor) and make sure you have +EXTRACT command lines set properly for your system. For information on +that configuration file, see section 6: Archive file type configuration +file. + +The following archive contains Win32 command-line archivers for popular +archive file formats: +http://digdist.bbsindex.com/miscFilesForDL/Win32CmdLineCompressionTools.zip +The archivers included in that archive handle the most popular file formats +(ZIP, 7Z (7-Zip), RAR, ARJ, TAR, GZ, TGZ, and TAR.GZ), and they are set up in +DDUPFileTypes.cfg to extract popular file formats (ZIP, 7Z (7-Zip), RAR, ARJ, +MSI, TAR, GZ, TGZ, and TAR.GZ). Note that you will need to edit that .cfg +file and change the path to the .exe file according to where you copied them +on your system. If your BBS is running in Windows, the included configuration +file should work for you (although it does also have the Linux command lines +as comments). If you copy the archivers to a directory that is not in your +system path, you will need to edit the DDUPFileTypes.cfg file to include the +full paths with the archive executables. + +Extractor notes: +DDUPFileTypes.cfg includes a setup for using 7-Zip to extract ISO (CD/DVD +image) files; however, in testing, it seemed that 7-Zip can only extract +or see one file in an ISO image. +DDUPFileTypes.cfg also includes a setup for extracting MSI (Microsoft +Installer) files. + +For Linux, the following is a list of Linux archivers that this +script is configured for and how you can acquire them if you don't +have them already: +------------------ +ZIP, 7Z, GZ, TGZ, TAR: +- Install p7zip using your distro's package manager, or download it via +the web. A download page is available here: +http://www.7-zip.org/download.html + +RAR: +- Install unrar using your distro's package manager, or download it via +the web. Or, download RARLab's version: +http://www.rarlab.com/download.htm + +ARJ: +- Source code for open-Source ARJ (and instructions) are available here: +http://linux.softpedia.com/get/System/Archiving/Arj-12097.shtml +This is the download link from that page: +http://linux.softpedia.com/progDownload/Arj-Download-12097.html +Download the source, and follow the page's instructions to build that on +your Linux system. After compiling, the executable file will be located +in linux-gnu/en/rs/arj . Place the executable file (arj) in a directory +in your path (i.e., /usr/local/bin or /usr/bin). +Instructions for building the ARJ source code, from the above web page, +are as follows: +cd gnu;autoconf;./configure;cd ..;make prepare;make + +Notes: + +1. GNU make must be used (on FreeBSD systems it's called "gmake"). +2. You have to run autoconf prior to running configure - as there will be different configure scripts for UNIX-like systems and OS/2 EMX. +3. On OS/2 EMX, autoconf v 2.57 is confirmed to work. +4. You can finalize the build process with "make install" (to perform a local installation) or "make package" (to create a self-extracting distribution kit). + +ARJ is a CPU-intensive program. If you wish to configure ARJ for a higher performance on a specific CPU, try the following, assuming that you have a newer version of GCC, e.g. 3.2.1: + +./configure CFLAGS="-march=i386 -mcpu=athlon-xp" + +where "-mcpu" designates the type of your CPU, run "man gcc" for a list of CPU types. + + +4. Installation and Setup +========================= +Step 1: Install a virus scanner +------------------------------- +You will need to download and install a virus scanner on your BBS system. +The one that I set up this script for is AVG Free version 9 for Windows, +which is available at the following web page: +http://free.avg.com/us-en/download?prd=afg + +Step 2: Copy the script files, configuration files, & archivers to your system +------------------------------------------------------------------------------ +Digital Distortion Upload Processor consists of the following files, which +you will need to place in your sbbs/exec directory (or another directory of +your choice): + 1. DDUP.js + 2. DDUP.cfg + 3. DDUP_Cleanup.js + 4. DDUPFileTypes.cfg + +For sysops running their BBS in Windows, the following archiver programs +in the Win32Archivers directory will need to be placed in a directory +(preferably a directory that's included in the system's path): + 1. 7za.exe + 2. ARJ32.EXE + 3. Rar.exe + 4. unzip.exe + +For sysops running the Linux version of Synchronet, you will need to acquire +the appropriate archivers as described in the previous section. + +Step 3: Edit the configuration files +------------------------------------ +You will need to edit DDUPFileTypes.cfg to make sure that the EXTRACT +command lines are correct for your system. If you're running your BBS +in Linux, you will first need to comment the Windows command lines and +uncomment the Linux command lines. See section 6: Archive file type +configuration file. + +You will also need to edit DDUP.cfg and change the scanCmd option, which +specifies the command line to use to scan files for viruses. In this +command line, the text string %FILESPEC% will be replaced with the name +of the file or directory to be scanned; thus, the virus scanner you use +should be able to take the name of an individual file or a directory as +a parameter, and it should also be a command-line scanner (capable of +taking command-line parameters). + +Special note about the scanner command line +------------------------------------------- +It should be noted that the scanner command line specified in DDUP.cfg +will be written to a temporary batch file (on Win32 systems) or a shell +script (on *nix systems), which is then run in order to scan the file(s). +The reason for this is that if there are any spaces in the file or +directory names used in the scanner command, the command line doesn't seem +to be passed to the operating system correctly by Synchronet's JavaScript +object model. + + +Step 4: Set up Digital Distortion Upload Processor for Testable Files +in Synchronet's configuration program (SCFG) +---------------------------------------------------------------------- +1. Run Synchronet's configuration program (SCFG) +2. From the main menu, choose "File Options". +3. From the menu that appears, choose "Testable Files..." +4. For each file type that you want to be able to test, you will need +an entry in this table. Some archive file types (i.e., ZIP) might +already be in there. In that case, simply scroll down to it and press +Enter to select it. If the desired file type is not in the list, then +press the INS key to insert an entry or scroll down to the first blank +line and press Enter to insert an entry there, then press Enter to +select and edit it. +The Command Line setting for the file type should look similar to +this (assuming the upload processor files were copied to your +sbbs\exec directory): +?DDUP.js %f +If you copied the upload processor files to another directory, you +will need to provide the path to DDUP.js; for example: +?/sbbs/UploadProcessor/DDUP.js %f +As an example, your Testable File Type window should look similar +to the following: ++[�][?]--------------------------------------------------------+ +� Testable File Type � +�--------------------------------------------------------------� +� �File Extension ZIP � +� �Command Line ?DDUP.js %f � +� �Working String Scanning arrchive file for viruses... � +� �Access Requirements � ++--------------------------------------------------------------+ + + +Step 5: Create/update your logout and scripts to handle temporary file cleanup +------------------------------------------------------------------------------ +If you have not already done so, you will need to create logout and login +scripts for your BBS; there, you will want to load DDUP_Cleanup.js - That will +help to ensure that temporary files created by the upload processor will be +removed to prevent extra space on your hard drive being wasted. + +If you are not already using a logout script, follow these steps: +1. Create a file in your sbbs\exec directory called logout.js (or another name +of your choosing) +2. Add this line to it (assuming that the upload processor scripts are in +sbbs\exec): +load("DDUP_Cleanup.js"); +3. Add your logout script to Synchronet's configuration: + A. Run Synchronet's configuration program (SCFG) + B. From the main menu, choose "System". + C. From that menu, choose "Loadable Modules..." + D. Arrow down to highlight "Logout Event", and press Enter. When it + prompts you, type in "logout" (without the quotes) and press Enter. + E. Escape back to the main menu, saving changes when it asks you to do + so. Then, exit out of SCFG. +If you already have a logout script (using JavaScript), you just need to add the +following line to it: +load("DDUP_Cleanup.js"); + +It is recommended that you do the same thing with your logout script. + + +5. Main configuration file +========================== +The file DDUP.cfg contains general settings for the upload processor. This +file can be edited with a text editor. The syntax for each setting is as +folows: +setting=value + +where "setting" is the setting name, "value" is the corresponding value for +the setting. +Also, comments are allowed in the configuration file. Comments begin with a +semicolon (;). + +The following are the settings used in this configuration file: +Setting Description +------- ----------- +scanCmd The command line to use for the virus scanner. + In this command line, the text string + %FILESPEC% will be replaced with the + file/directory to be scanned. + +pauseAtEnd Specifies whether or not to pause for user + input when the scan is done. Valid values + are yes and no. + +skipScanIfSysop Specifies whether or not to skip scanning + for the sysop(s). Valid values are yes + and no. + + + +6. Archive file type configuration file +======================================= +The configuration file DDUPFileTypes.cfg defines options for various file +types, including the extract command (for archive files) and whether or not +you want the upload processor to scan it. File types are specified by their +filename extension in square brackets. Extractable files must have an +EXTRACT option, which specifies the command line for extracting the file. +Another option that can be specified in this file is scanOption, which +specifies whether or not you want the upload processor to scan the file +(or files in the archive) with the virus scanner. +The general format for each file type is as follows: + +[EXTENSION] +EXTRACT=command +scanOption=scan (or always pass, or always fail) + +By default, the scanOption setting will be "scan", and by default, the extract +command is blank (not set). + +The valid values for the scanOption setting are as follows: +scan: Scan the file using the virus scanner +always pass: Always assume the file is good +always fail: Always assume the file is bad + +As an example, the following settings can be used for zip files in Windows: + +[ZIP] +scanOption=scan +EXTRACT=\BBS\sbbs\exec\unzip.exe -qq -o %FILENAME% %FILESPEC% -d %TO_DIR% + +Note that for the extract command, the following pseudonyms are used: +%FILENAME% : The name of the archive file or text file. +%FILESPEC% : This would specify the file(s) to be extracted from the archive. + The script actually will totally remove this from the command; it + is not used. It's currently here for possible future use. +%TO_DIR% : The directory to which the archive file will be extracted. + +Using the above example configuration for zip files, if the user (on node 1) +uploads D:\Files\someArchive.zip and your Synchronet installation is located +in D:\sbbs, the temp directory is D:\sbbs\node1\DDUploadProcessor_Temp, and +the extract command will be translated to the following: +unzip.exe -qq -o D:\Files\someArchive.zip -d D:\sbbs\node1\DDUploadProcessor_Temp \ No newline at end of file diff --git a/xtrn/DDUploadProcessor/Revision history.txt b/xtrn/DDUploadProcessor/Revision history.txt new file mode 100644 index 0000000000000000000000000000000000000000..6bb5abf43dceb394e57abb1c5bceb17fcdc0c2fd --- /dev/null +++ b/xtrn/DDUploadProcessor/Revision history.txt @@ -0,0 +1,5 @@ +Revision History for Digital Distortion Upload Processor +======================================================== +Version Date Description +------- ---- ----------- +1.00 2009-12-29 First general public release \ No newline at end of file