1 /*
  2  * Copyright © [2008-2009] Novell, Inc.  All Rights Reserved.
  3  * 
  4  * USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE DEVELOPER LICENSE AGREEMENT
  5  * OR OTHER AGREEMENT THROUGH WHICH NOVELL, INC. MAKES THE WORK AVAILABLE.  THIS WORK 
  6  * MAY NOT BE ADAPTED WITHOUT NOVELL'S PRIOR WRITTEN CONSENT.
  7  * 
  8  * NOVELL PROVIDES THE WORK "AS IS," WITHOUT ANY EXPRESS OR IMPLIED WARRANTY, 
  9  * INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR 
 10  * A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  NOVELL, THE AUTHORS OF THE WORK, AND THE 
 11  * OWNERS OF COPYRIGHT IN THE WORK ARE NOT LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER 
 12  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, 
 13  * OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS IN THE WORK.
 14  */
 15 
 16 
 17 /**
 18  * @fileoverview
 19  * This file contains a variety of utility functions used by Collectors
 20  * including commonly-used extensions to existing object methods, and 
 21  * additional SDK object template functions.
 22  */
 23 /**
 24  * Extensions to the String object methods
 25  */
 26 
 27 /**
 28  * Removes whitespace from the beginning and end of the string.
 29  * @addon
 30  * @return {String} The trimmed string
 31  */
 32 String.prototype.trim = function() {
 33 	return this.replace(/^\s+|\s+$/g,"");
 34 };
 35 
 36 /**
 37  * Inserts a string at a specified character position, replacing that character
 38  * @example
 39  * var str = "this is a string";
 40  * var newstr = str.insert(8, 1, "my");
 41  * 
 42  * @addon
 43  * @param {Number} start  Character position in the string at which the inserted string should be placed.
 44  * @param {Number} num  Number of characters to consume while inserting string
 45  * @param {String} ins  String to insert into this
 46  * @return {String} 
 47  */
 48 String.prototype.insert = function(start, num, ins) {
 49 	if ( !(typeof start == "number" && typeof num == "number") ||
 50 				start > this.length || num > this.length ) { return this; }
 51 	var newstring = this.substr(0,start) + ins.toString() + this.substr(start+num);
 52 	return newstring;
 53 };
 54 
 55 
 56 /**
 57  * Splits a string into substrings based on a delimiter character.
 58  * The safesplit() method works like the split() method, except that it protects 
 59  * quoted text sections from being split at the delimiter. It will strip quotes from 
 60  * any token that ends up being an entire output token (and will de-escape embedded
 61  * quotes); otherwise it will leave quotes and escapes intact.
 62  * <p>Note: does not support the "howmany" argument from split().
 63  * <p>Example:
 64  * <pre>
 65  * var input1='token1,token2,"token3,including this",token4,token5\\"embedded quote'
 66  * var output1=input1.safesplit();
 67  * var input2="this (string) used (parentheses) as quote (chars) \(but not this one\)"
 68  * var output2=input2.safesplit(" ","(");
 69  * </pre>
 70  * @addon
 71  * @param {char} delim  The delimiter (n char substring) to split the string on - does NOT support empty string
 72  * @param {char} quote  The quote character; if '[', '(', or '{' are used
 73  * this function will look for the natural closing bracket to end a quoted section.
 74  * @return {String[]}  An output array of token strings
 75  */
 76 String.prototype.safesplit = function(delim, quote) {
 77 	var tokens=[];
 78 	tokens[0]="";
 79 	if (typeof quote == 'undefined') { quote="\""; }
 80 	var endquote=quote;
 81 	if (quote=="(") { endquote=")"; }
 82 	if (quote=="[") { endquote="]"; }
 83 	if (quote=="{") { endquote="}"; }
 84 	if (typeof delim == 'undefined') { delim=","; }
 85 	var escape="\\";
 86 	
 87 	var tokNum=0;
 88 	var quoting=false;
 89 	var fullyEnclosed=false;
 90 	var len=this.length;
 91 	for (var i = 0; i < len; i++) {
 92   	if (!quoting) {
 93   		if (this[i] == quote) {  // starting a quote
 94   			if (tokens[tokNum].length === 0) {  // if this is at the beginning of a token, assume it's enclosed
 95   				quoting = true;
 96   				fullyEnclosed = true;
 97   			}
 98   			else {  // otherwise just turn quoting on  			
 99   				quoting = true;
100 					tokens[tokNum] += this[i];
101   			}
102   		}
103 			else if (this[i] == escape) {  // escape the next char
104 			  tokens[tokNum] += this[i];
105 				tokens[tokNum] += this[++i];
106 			}
107   		else if (this.substr(i,delim.length) == delim) { // delimiter found; create next token
108 				tokNum++;
109 				tokens[tokNum] = "";
110 				i=i+(delim.length-1);
111 			}
112 			else { // normal char
113 				tokens[tokNum] += this[i];
114 			}
115 		}
116 		else { // quoting
117 			if (this[i] != endquote && this[i] != escape ) { // normal char (delim not used)
118 				tokens[tokNum] += this[i];
119 			}
120 			else if (this[i] == escape && typeof this[i+1] != 'undefined' && this[i+1] != endquote ) { // not escaping a quote
121 				tokens[tokNum] += this[i];
122 			}
123 			else if (!fullyEnclosed && this[i] == endquote) {  // end of embedded quote
124 				tokens[tokNum] += this[i];
125 				quoting=false;
126 			}
127 			else if (fullyEnclosed && this[i+1] == endquote) { // unescape quotes
128 				tokens[tokNum] += this[++i];
129 			}
130 			else if (fullyEnclosed && (this.substr(i+1,delim.length) == delim || typeof this[i+1] == 'undefined')) { // we are eliminating end quotes
131 				quoting=false;
132 				fullyEnclosed=false;
133 				i=i+(delim.length-1);
134 			}
135 			else {                    // oops, we thought this was enclosed but it was not
136 				quoting=false;
137 				fullyEnclosed=false;
138 				tokens[tokNum] = quote + tokens[tokNum] + endquote;
139 			}
140 		}
141 	}
142 	return tokens;
143 };
144 
145 
146 /**
147  * The parseNVP() function converts a string in name-value pair format into
148  * a hash where each value can be retrieved by its name. For example, the
149  * string "name=value" would be converted to obj["name"] = value.
150  * <p>
151  * Note that in some cases you may need to pre-process your string to avoid confusion.
152  * Quoting in particular can be thorny, and you'll need to make sure there are no
153  * unquoted characters that look like separators but really aren't.
154  * <p>Example:
155  * <pre>
156  * var input='a=1 b=2 c=hello d="one token"'
157  * input.parseNVP(" ","=",'"')
158  * </pre>
159  * @addon
160  * @param {char} pairSep  Character(s) that separate one entry pair from another
161  * @param {char} NVSep  Character(s) that separate names from values in a single pair
162  * @param {char} quote  Quote character used for values; if '[', '(', or '{' are used
163  * this function will look for the natural closing bracket to end a quoted section.
164  * @return {Hash} Map of names (attributes) to values
165  */
166 String.prototype.parseNVP = function(pairSep, NVSep, quote) {
167   
168 	if (typeof NVSep=='undefined') { NVSep="="; }
169 	if (typeof pairSep=='undefined') { pairSep=" "; }
170 	if (typeof quote=='undefined') { quote="\""; }
171 	var ret= {};
172 	var tmparr=[];
173 	
174 	var entrysplit=this.safesplit(pairSep,quote);
175 	for (var i = entrysplit.length-1; i > -1; i--) {
176 		tmparr=entrysplit[i].safesplit(NVSep,quote);
177 		ret[tmparr[0]]=tmparr[1];
178 	}
179   return ret;
180 };
181 
182 /**
183  * Parse a Syslog string.
184  * Note that this method assumes strict RFC3164 compliance, namely:
185  * Mon DD HH:MM:SS (host) (message)
186  * Also: Syslog messages lack a year, so this method assumes that events were generated
187  * in the current year *except* if the month is December and the current month is January.
188  * @addon
189  * @return {Hash} Three-attribute object with date, host, and message from Syslog string 
190  */
191 String.prototype.parseSyslog = function() {
192 	var msg = {};
193 	
194 	
195 	
196 	if (this.search(/^(\w+\s+\d+\s+\d+:\d+:\d+)\s+(\S+)\s+(.*)$/) != -1) {
197 		var date_str = RegExp.$1;
198 		var sysDate = new Date();
199 		var date_year = sysDate.getFullYear();
200 		
201 		msg.reporterip = RegExp.$2;
202   	    msg.message = RegExp.$3;		
203 		msg.date = Date.parseExact(date_str + " " + date_year, "MMM dd HH:mm:ss yyyy");
204 		// Handle old data from last year
205 		if (msg.date.isAfter(sysDate.add({ hours: 2 }))) {
206 			msg.date.setFullYear(date_year - 1);
207 		}
208 		
209 		// Check for observer's hostname, which will be shifted rightwards
210 		if ( msg.message.search(/([a-zA-Z0-9][-a-zA-Z0-9]+[a-zA-Z0-9])\s+(\w+(\[\d+\])?:.*)/) != -1 ) {
211 			msg.observerhostname = RegExp.$1;
212 			msg.message = RegExp.$2;
213 		}
214   	}
215 	return msg;
216 };
217 
218 /**
219  * Converts a standard path into a normalized form.
220  * This method will take standard Unix and Windows paths and transform them into a normalized form.
221  * It returns this normalized form as an object containing two elements:
222  * this.object : the terminal element in the path, e.g. the name of the actual object
223  * this.context : the slash-separated path which defines the namespace in which that object is contained.
224  * Often these elements will ultimately be assigned to TargetDataName and TargetDataContainer in the output event.
225  * @return {Object}  Two-element object that contains an 'object' attribute and a 'context' attribute.
226  */
227 String.prototype.parsePath = function() {
228 	// Types: 1=Unix, 0=Windows
229 	
230 	var path = {};
231 	var type;
232 	// If there are no path separators, return just the object name
233 	if (this.search(/\//) == -1 && this.search(/\\/) == -1) {
234 		path.object = this;
235 		return path;
236 	}
237 	
238 	/* Next, try to determine if this is Unix or Windows.  This is tricky because Unix paths can contain '\' chars
239 	 * (as escape characters).
240 	 */ 
241 	if ( this[0] == "/" || this.search(/\\/) == -1) {
242 		type = 1;
243 	} else {
244 		type = 0;
245 	}
246 	
247 	if (type == 1) {
248 		var idx = this.lastIndexOf("/"); 
249 		if (idx > -1) {
250 			path.object = this.substr(idx+1);
251 			path.context = this.substr(0,idx);
252 		}
253 	} else {
254 		var idx = this.lastIndexOf("\\"); 
255 		if (idx > -1) {
256 			path.object = this.substr(idx + 1);
257 			path.context = this.substr(0, idx);
258 			path.context = path.context.replace(/\\/, "/");
259 		}
260 	}
261 	
262 	return path;
263 };
264 
265 /**
266  * Converts an LDAP-formatted string into an object with separate attributes to contain the 
267  * CN and the rest of the directory structure.
268  * This method will take a normal LDAP string (CN=user1.OU=users.OU=engineering.O=novell) and
269  * convert it into an object with separate attributes for the base name and domain (in Sentinel
270  * parlance. This object has two fields:
271  * <ul>
272  * <li>basename: The base common name of the object (ex. "user1")
273  * <li>domain: the rest of the path for the object, in slash notation (ex. "\novell\engineering\users")
274  * </ul>
275  * <p>Example:
276  * <pre>
277  * var ldapuser = "CN=user1.OU=users.OU=engineering.O=novell";
278  * var ldapobj = ldapuser.parseLDAP();
279  * // In Rec2Evt.map, modify the lines to say:
280  * InitUserName,ldapobj.basename
281  * InitUserDomain,ldapobj.domain
282  * </pre>
283  * @addon
284  * @return {Hash} Two-attribute object with basename and domain from LDAP string 
285  */
286 String.prototype.parseLDAP = function() {
287 	var i = 0;
288 	var dn = {};
289 	var splitDN = this.split(",");
290 	
291 	if ( (i = splitDN[0].indexOf("=")) != -1 ) {
292 		dn.basename = splitDN[0].substr(i+1);
293 	}
294 	
295 	dn.domain = "";
296 	for ( var j = splitDN.length-1; j > 0; j--) {
297 		if ( (i = splitDN[j].indexOf("=")) != -1 ) {
298 			dn.domain = dn.domain + "\\" + splitDN[j].substr(i+1);
299 		}
300 	}
301 	return dn;
302 };
303 
304 /**
305  * This method converts the string from Base64 encoding to a regular string.
306  * Note that in general you should only attempt to decode strings; decoding binary data
307  * may cause issues.
308  * @example
309  * var string="SGVsbG8sIHdvcmxk";
310  * var outstring=string.parseBase64();
311  * @addon
312  */
313 String.prototype.parseBase64 = function() {
314 	
315 	if (typeof instance.MAPS.base64Map == "undefined") {
316 		// define a map for fast lookups
317 		instance.MAPS.base64Map = { A:0,B:1,C:2,D:3,E:4,F:5,G:6,H:7,I:8,J:9,K:10,L:11,M:12,
318 		N:13,O:14,P:15,Q:16,R:17,S:18,T:19,U:20,V:21,W:22,X:23,Y:24,Z:25,
319 		a:26,b:27,c:28,d:29,e:30,f:31,g:32,h:33,i:34,j:35,k:36,l:37,m:38,
320 		n:39,o:40,p:41,q:42,r:43,s:44,t:45,u:46,v:47,w:48,x:49,y:50,z:51,
321 		"0":52,"1":53,"2":54,"3":55,"4":56,"5":57,"6":58,"7":59,"8":60,"9":61,"+":62,"/":63}
322 	}
323 	var outStr = "";
324 	var incode, outcode, tmpbuf;
325 	var count = -1;
326 	
327 	for ( var i=0; i < this.length; i++ ) {
328 		incode = instance.MAPS.base64Map[this.charAt(i)];
329 		if(typeof incode != "undefined") { count++; } else { continue; } // skip over whitespace and invalid chars
330 		// Break the input into blocks of 4 chars; convert each 6-bit input into 8-bit charcodes
331 		switch (count % 4) {
332 			case 0:
333 				tmpbuf = incode;
334 				continue;
335 			case 1:
336 				outcode = (tmpbuf << 2) | (incode >> 4);
337 				tmpbuf = incode & 0x0F;
338 				break;
339 			case 2:
340 				outcode = (tmpbuf << 4) | (incode >> 2);
341 				tmpbuf = incode & 0x03;
342 				break;
343 			case 3:
344 				outcode = (tmpbuf << 6) | (incode >> 0);
345 				tmpbuf = 0;
346 				break;
347 		}
348 		outStr = outStr + String.fromCharCode(outcode);
349 	}
350 	
351 	// Fixup of Finnish chars
352 	outStr = outStr.replace(/ä/g, "ä");
353 	outStr = outStr.replace(/ö/g, "ö");
354 	outStr = outStr.replace(/Ã¥/g, "å");
355 	outStr = outStr.replace(/Ä/g, "Ä");
356 	outStr = outStr.replace(/Ö/g, "Ö");
357 	outStr = outStr.replace(/Ã…/g, "Å");
358 	
359 	return outStr;
360 }
361 
362 /**
363  * Extensions to the Date object methods
364  */
365 	
366 /**
367  * Converts a JavaScript Date object into a Java Date object
368  * @return {Java Date} Java Date object
369  */
370 Date.prototype.toJava = function() {
371         return new java.util.Date(this.getTime());
372 };
373 
374 /**
375  * Extensions to the Array object methods
376  */
377 	
378 /**
379  * Converts a JavaScript Array object into a Java array of the named type.
380  * This routine converts a JavaScript Array into a particular type of Java array. For example,
381  * this is the only way to create a byte[] array commonly used for e.g. IP addresses.
382  * <p>Example:
383  * <pre>
384  * var JSArray = [ 012, 000, 000, 0147 ];
385  * var JavaArray = JSArray.toJava(Byte);
386  * var hostname = java.net.InetAddress.getByAddress(JavaArray);
387  * </pre>
388  * @param {Object[]} type The type of Java array to create
389  */
390 Array.prototype.toJava = function(type) {
391 	var jarr = new java.lang.reflect.Array.newInstance(type ? type : java.lang.Object, this.length);
392 	for (var i = 0; i < this.length; ++i) {
393 		if (typeof this[i] != "undefined" && this[i] != null) {
394 			java.lang.reflect.Array.set(jarr, i, (this[i].toJava ? this[i].toJava(type) : this[i]));
395 		}
396 	}
397 	return jarr;
398 };
399 
400 
401 
402 /**
403  * log() is a global utility function that allows you to record messages to the log and to events.
404  * @param {String} msg  Message to record (required)
405  * @param {Number} sev  Numeric severity, use 0-5 (optional)
406  * @param {Flag} dest  Destination flag, use instance.LOG, instance.EVENT, or instance.LOG|instance.EVENT  (optional, defaults to both)
407  */
408 log = function(msg, sev, dest) {
409 	
410 	if (typeof dest == "undefined") { dest = 0x3; }
411 	var level = Level.FINEST;
412 	if( typeof sev == "undefined" ) {
413 		if ( typeof instance.CONFIG.params.Error_Message_Severity != 'undefined' ) {
414 		 sev = instance.CONFIG.params.Error_Message_Severity;
415 	 } else {
416 	 	sev = 5;
417 	 }
418 	}
419 	
420 	
421 	if (dest & instance.LOG) {
422 		switch (sev) {
423 			case 5:
424 			  level = Level.SEVERE;
425 					break;
426 			case 4:
427 			case 3:
428 			  level = Level.WARNING;
429 					break;
430 			case 2:
431 			case 1:
432 			  level = Level.INFO;
433 					break;
434 			case 0:
435 			  level = Level.FINE;
436 					break;
437 		}
438 		ScriptEngineUtil.log( level, msg );
439 	}
440 	
441 	// Send an audit event to Sentinel indicating an error in the Collector
442 	if (dest & instance.EVENT) {
443 		var errEvt = new Event(instance.protoEvt);
444 		errEvt.Severity = sev;
445 		errEvt.EventName = "Collector Internal Message";
446 		errEvt.SensorType = "A";
447 		errEvt.Message = msg;
448 		errEvt.send(instance.MAPS.UnsupEvt);
449 	}
450 		
451 	return true;
452 };
453 
454 
455 /**
456  * Creates a DataMap object by loading the map from a CSV file on disk.
457  * <p>Example:
458  * <pre>
459  * // First, define the conversion in an external file (CSV format)
460  * Rec2Evt.map:
461  * Message,msg
462  * InitUserName,iun
463  * ...
464  * // Next, load the DataMap:
465  * this.MAPS.Rec2Evt = new DataMap(this.CONFIG.collDir + "/Rec2Evt.map");
466  * // And set your values in the Record object (typically in Record.parse())
467  * rec.msg = rec.s_RXBufferString.substr(0,20);
468  * rec.iun = rec.s_RXBufferString.substr(21,25);
469  * // Finally, run the conversion:
470  * rec.convert(this, instance.MAPS.Rec2Evt);
471  * </pre>
472  * @class
473  * A DataMap specifies a conversion from a Record object into some other type of object.
474  * An external file is used to specify how the conversion is accomplished. This external file is
475  * in CSV format, with the first column being the name of the attribute in the target object.
476  * The second column is then the attribute in the Record object that will be plugged into the
477  * target object. The Record attribute references can be complex, for example they can specify
478  * array elements, nested attributes, and so forth.
479  * <p>
480  * In most cases, the conversion will be between a Record object and an Event object. The template
481  * will load the default maps automatically; you will only need to use this class if you are
482  * creating your own output objects or providing multiple conversions.
483  * @param {String} fileName  The file to load the DataMap from
484  * @constructor
485  */
486 function DataMap(fileName){
487 	var mapFile=new File(fileName);
488 	var elements=[];
489 	
490 	if(mapFile.isOpen()) {
491 		for (var inString = mapFile.readLine(); typeof inString != 'undefined'; inString = mapFile.readLine()) {
492 			elements = inString.safesplit(",");
493 			if( typeof elements[0] != "undefined" && typeof elements[1] != "undefined") {
494 				elements[0] = elements[0].trim();
495 				elements[1] = elements[1].trim();
496 				if( elements[0] !== "" && elements[0].charAt(0) != "~" && elements[1] !== "" ) {
497 					this[elements[0]] = elements[1];
498 				}
499 			}
500 		}
501 		mapFile.close();
502 	}
503 	
504 	/**
505 	 * Most DataMaps are simple translations between one flat object and another, but in some cases
506 	 * the source object may be a more complex object. The test here is if the RHS of your DataMap
507 	 * input file includes any complex object/attribute references, and/or references to "illegal"
508 	 * attribute names that should be quoted, e.g.:
509 	 * winevt.evt.myname
510 	 * My Attribute
511 	 * obj.Full Name
512 	 * If you have any of these, you should run the makesafe() method on your DataMap before
513 	 * attempting to use it.
514 	 */
515 	this.makesafe = function() {
516 		for (var tag in this) {
517 			if (typeof this[tag] != "function") {
518 				// Escape the various elements of the path
519 				var arr = this[tag].safesplit(".");
520 				var path = "";
521 				for (var i = 0; i < arr.length; i++) {
522 					if ( arr[i].indexOf("[") < 0) {
523 						path = path + "[\"" + arr[i] + "\"]";
524 					} else {
525 						var tmparr = arr[i].split("[");
526 						path = path + "[\"" + tmparr[0] + "\"]"; // root element
527 						for (var j = 1; j < tmparr.length; j++) {
528 							path = path + "[" + tmparr[j];  // array references
529 						}
530 					}
531 				}
532 				this[tag] = path;
533 			}
534 		}
535 	}
536 	
537 	/**
538 	 * DataMap objects are normally used to convert one object type into another, and the convert()
539 	 * routine typically uses an eval() to accomplish this. In high-datarate code, however, the 
540 	 * eval() method causes an unacceptable performance hit.
541 	 * To circumvent this, the compile() method will create a dynamic function which will be used
542 	 * to perform the conversion instead of the eval(). The normal template will use this for 
543 	 * converting the input Record object into the output Event object.
544 	 */
545 	this.compile = function() {
546 		var body = "";
547 		for (var tag in this) {
548 			if (typeof this[tag] != "function") {
549 				if (tag == "Severity") {
550 					body = body + "try{ typeof input" + this[tag] + " != \"undefined\" ? output." + tag + " = input" + this[tag] + " : true; } catch(err) {};"
551 				} else {
552 					body = body + "try{input" + this[tag] + " ? output." + tag + " = input" + this[tag] + " : true; } catch(err) {};"
553 				}
554 			}
555 		}
556 		var func = "this.preconvert = function(input, output) {" + body + "};";
557 		eval(func);	
558 	}
559 	
560 	return true;
561 }
562 
563 
564 /**
565  * Creates a KeyMap object that loads the map from a CSV file on disk.
566  * <p>Example:
567  * <pre>
568  * // First, define your KeyMap input file in CSV format, with the key in the first column
569  * usermap.map:
570  * user1,user1@novell.com,801-861-1000
571  * user2,user2@novell.com,801-861-2000
572  * // Next, load the KeyMap
573  * instance.MAPS.userMap = new KeyMap( instance.CONFIG.collDir + "/usermap.map");
574  * // And then do your lookups:
575  * var userinfo = instance.MAPS.userMap.lookup(rec.username);
576  * rec.useremail = userinfo[0];
577  * rec.userphone = userinfo[1];
578  * // (or you could just put the array references directly in Rec2Evt.map)
579  * </pre>
580  * @class
581  * A KeyMap defines a relationship between a key and a set of strings related to that key.
582  * Each KeyMap is loaded from an input file (CSV format) and can then be used as a reference source
583  * to enhance data gathered by the Collector. For example, you could use the user's name to pull
584  * back the phone number, e-mail address, and other details relating to that user.
585  * The second parameter enable optional header-row parsing; you pass in an ID to tell the 
586  * constructor how to find the header row. If you pass in:
587  * <ul>
588  * <li>String: The parser will search for this string at the beginning of each line and treat
589  * that line as the header.</li>
590  * <li>Number: The parser will use this line number as the header row.</li>
591  * </ul>
592  * Note that lines before the header row will be ignored.
593  * @param {String} fileName  The name of the file that contains the KeyMap definition.
594  * @param {String or Number} hdrID  Tells the constructor how to identify the header row.
595  * @constructor
596  */
597 function KeyMap(fileName, hdrID){
598     var mapFile = new File(fileName);
599     this.SourceFiles = [];
600     this.SourceFiles[0] = {
601         "file": fileName,
602         "time": new Date(),
603         "hdrID": hdrID
604     };
605     if (!mapFile.isOpen()) {
606         return false;
607     }
608     var elements = [];
609     var hdrString = "";
610     var hdrArr = [];
611     if (typeof hdrID != "undefined" && hdrID != "") {
612         if (typeof hdrID == "number" && hdrID > 1) {
613             for (var i = 1; i < hdrID; i++) {
614                 mapFile.readLine(); // discard any lines before header
615             }
616             hdrString = mapFile.readLine();
617         }
618         else {
619             do {
620                 hdrString = mapFile.readLine();
621             }
622             while (hdrString.indexOf(hdrID) !== 0);
623                     }
624         hdrArr = hdrString.split(",").slice(1);
625         this.KeyMapHeader = {};
626         for (i = hdrArr.length - 1; i > -1; i--) {
627             this.KeyMapHeader[hdrArr[i].trim()] = i;
628         }
629     }
630     for (var inString = mapFile.readLine(); typeof inString != 'undefined'; inString = mapFile.readLine()) {
631         elements = inString.safesplit(",");
632         if (typeof elements[1] != 'undefined') {
633             elements[1] = elements[1].trim();
634             if (elements[1] !== "" && elements[0].charAt(0) != "~" && elements[0].charAt(0) != "#") {
635                 this[elements[0]] = elements.slice(1);
636             }
637         } else if (elements[0] !== "" && elements[0].charAt(0) != "~" && elements[0].charAt(0) != "#") {  // single column
638         	this[elements[0]] = true;
639         }
640     }
641     mapFile.close();
642     
643     /**
644      * This method extends an existing KeyMap by adding additional keys and values.
645      * The source file must be in the standard CSV format of the original KeyMap file.
646      * A hdrId argument is provided as with the constructor, however this header is just
647      * skipped and the original header will be used to determine column placement (as a
648      * result, the columns must be in the same order).
649      * Note also that if identical keys are specified in the new input map, they will
650      * replace existing entries.
651      * @param {String} fileName  The name of the file that contains the KeyMap extension definition.
652      * @param {String or Number} hdrID  Tells the constructor how to identify the header row.
653      */
654     this.extend = function(fileName, hdrID){
655         var mapFile = new File(fileName);
656         var existingFile;
657         for (i = 0; i < this.SourceFiles.length; i++) {
658             if (this.SourceFiles[i].file == fileName) {
659                 existingFile = true;
660             }
661         }
662         if (!existingFile) {
663             this.SourceFiles.push({
664                 "file": fileName,
665                 "time": new Date().getTime(),
666                 "hdrID": hdrID
667             });
668         }
669         if (!mapFile.isOpen()) {
670             return false;
671         }
672         var elements = [];
673         var hdrString = "";
674         var hdrArr = [];
675         if (typeof hdrID != "undefined") {
676             // Read up to header and discard
677             if (hdrID instanceof Number && hdrID > 1) {
678                 for (i = 0; i < hdrID; i++) {
679                     mapFile.readLine();
680                 }
681                 hdrString = mapFile.readLine();
682             }
683             else {
684                 do {
685                     hdrString = mapFile.readLine();
686                 }
687                 while (hdrString.indexOf(hdrID) !== 0);
688                             }
689         }
690         
691         for (var inString = mapFile.readLine(); typeof inString != 'undefined'; inString = mapFile.readLine()) {
692             elements = inString.safesplit(",");
693             if (typeof elements[1] != 'undefined') {
694                 elements[1] = elements[1].trim();
695                 if (elements[1] !== "" && elements[0].charAt(0) != "~" && elements[0].charAt(0) != "#") {
696                     this[elements[0]] = elements.slice(1);
697                 }
698             } else if (elements[0] !== "" && elements[0].charAt(0) != "~" && elements[0].charAt(0) != "#") {  // single column
699             	this[elements[0]] = true;
700             }
701         }
702         mapFile.close();
703     };
704     
705     /**
706      * Looks up a set of values in a KeyMap with this string as the key.
707      * A KeyMap is used to associate a set of values with a lookup key, sort of like a hash map, but
708      * with multiple possible return strings. You can either fetch the entire array of results,
709      * or select a single column to return with the "col" parameter. If you pass in "col" as:
710      * <ul>
711      * <li>String: Assumes that the KeyMap includes a header row with text column names. Returns the string value of the column with that name.</li>
712      * <li>Number: Returns the string value of of the selected column.</li>
713      * </ul>
714      * Note that the return type depends on whether you use "col" or not; either you get a String
715      * back or an Array of Strings.
716      * @param {String} key  The string key to use for the lookup, it must match a string in the first column of the KeyMap
717      * @param {Number or String} col  Optional, specifies a single column to return (as array 0).
718      * @return {String[]} An array of strings that were associated with this key in the KeyMap. Null if no match.
719      * @return {String}
720      */
721     this.lookup = function(key, col){
722         if (typeof key == "undefined") {
723             return null;
724         }
725         if (typeof col != "undefined") {
726             if (typeof col == 'number') {
727             	if (typeof this[key] != "undefined") {
728             		return this[key][col];
729             	} else {
730             		return null;
731             	}
732             } else {
733                 return this[key][this.KeyMapHeader[String(col)]];
734             }
735         }
736         return this[key];
737     };
738     
739     /**
740      * Checks the KeyMap source file to see if it has been modified, and reloads it if so.
741      */
742     this.refresh = function(){
743         // Check last mod time, refresh if needed
744         for (i = 0; i < this.SourceFiles.length; i++) {
745             if (this.SourceFiles[i].time < File.modTime(this.SourceFiles[i].file)) {
746                 this.extend(this.SourceFiles[i].file, this.SourceFiles[i].hdrID);
747             }
748         }
749     };
750     
751 };
752 
753 
754 /**
755  * Creates a DNS resolver class object.
756  * @class
757  * The DNS class is used to perform hostname and IP address resolution.
758  * <p><strong>Experimental</strong>
759  * @param {String} server The IP address or name of the server to use for lookups. 
760  * @constructor
761  */
762 function DNS(server) {
763 	this.server = server;
764 	return true;
765 };
766 
767 /**
768  * Resolve a hostname or IP address.
769  * This method will autodetect which format is passed in (host or IP) and return the other.
770  * <p><strong>Experimental</strong>
771  * @param {String} host
772  * @return {String} IP address or hostname that the passed in hostname or IP resolved to.
773  */
774 DNS.resolve = function(host) {
775 	
776 	var matchIP = new RegExp("\d+\.\d+\.\d+\.\d+");
777 	
778 	if ( matchIP.test(host) ) { // this is an IP
779 	  var octets = host.split(".");
780 			eval( "var octetArray = [ 0" + octets[0] + ",0" + octets[2] + ",0" + octets[3] + ",0" + octets[4]);
781 			var byteArray = octetArray.toJava(Byte);
782 	  try {
783      // Get hostname by textual representation of IP address
784      var addr = java.net.InetAddress.getByAddress(byteArray);
785 				    
786      // Get canonical host name
787      var hostname = addr.getCanonicalHostName();
788 			
789 			  return hostname;
790     } catch (e) {
791 			  return "unknown.unknown";
792     }
793 	} else { // we have a hostname
794 	  try {
795      var addr = java.net.InetAddress.getByName(host);
796         
797 			  var ip = addr;
798 			  // byte[] ipAddr = addr.getAddress();
799     
800      // Convert to dot representation
801      // String ipAddrStr = "";
802      // for (int i=0; i<ipAddr.length; i++) {
803      //     if (i > 0) {
804      //         ipAddrStr += ".";
805      //    }
806      //    ipAddrStr += ipAddr[i]&0xFF;
807 			  return ip;
808        
809     } catch (err) {
810 			  return "0.0.0.0";
811     }
812 	}
813 };
814 
815 /**
816  * Adjust a localized date of the current Date object to a newly-specified timezone.
817  * By default, dates created in Javascript are created in the local
818  * timezone, e.g. if I say "create a Date object at time 3PM", what I will
819  * get is a Date that is set to 3PM for the local timezone that this
820  * machine is running in. If our event source sends us the timezone, or
821  * if it is set on the Event Source ESM object, then we want to adjust for
822  * the specified timezone.
823  * This routine uses the Java TimeZone object, so any TZ specifiers supported by that
824  * method will be supported by this routine.
825  * <strong>This method uses the timezone specified in the rec.s_TimeZone variable. This variable
826  * is set by current Connectors if configured.</strong>
827  * If the variable is not set, this code assumes that the TZ of the source device is local time,
828  * and does not change the timezone of this Date object. If your device reports in GMT, you
829  * <strong>must</strong> set the rec.s_TimeZone variable to "UTC".
830  *  
831  * @return {Boolean}  Specified whether a timezone adjustment was made.
832  */
833 Date.prototype.adjustTimezone = function() {
834 	if( typeof rec.s_TimeZone != "undefined" && rec.s_TimeZone != "") {
835 		// Create a Java TimeZone object for this timezone
836 		if(typeof instance.MAPS.TZMap[rec.s_TimeZone] == "undefined") {
837 			instance.MAPS.TZMap[rec.s_TimeZone] = java.util.TimeZone.getTimeZone(rec.s_TimeZone);
838 		}
839 		// Convert to UTC so we have the "base time" correct (the offset is calculated against UTC)
840 		this.setTimezone("UTC");
841 		this.setTime(this.getTime() - instance.MAPS.TZMap[rec.s_TimeZone].getOffset(this.getTime()));
842 		return true;
843 	}
844 	return false;
845 };
846 
847 /**
848  * Creates a hash value of a string. Message digests are secure one-way hash functions
849  * that take arbitrary-sized data and output a fixed-length hash value.
850  * @param {String} hashtype  One of 'MD2','MD5','SHA','SHA-1','SHA-2','SHA-256','SHA-384','SHA-512' specifying the hash algorithm
851  * @return {String} checksum  The computed hash value of a string or 'undefined' if it fails due to any reason.
852  */
853 String.prototype.getHashValue = function(hashtype){
854     switch (hashtype) {
855         case "MD2":
856         case "MD5":
857         case "SHA-1":        
858         case "SHA-384":
859         case "SHA-512":
860             break;
861         case "SHA":            
862         case "SHA-2":
863 				case "SHA-256":
864             hashtype = "SHA-256";
865             break;
866         default:
867             return undefined;
868     }
869     try {
870 		// Create a Java MessageDigest object for this hashtype message digest algorithm 
871         var md5 = new java.security.MessageDigest.getInstance(hashtype);
872 		// Create a Java String object for the specified string in 'this'javascript object
873         var str = new java.lang.String(this);
874 		// Updates the digest using the specified array of bytes
875 		// Use [].concat(<java array object>) to convert java array object to javascript array object 
876         md5.update([].concat(str.getBytes()));
877         var checksum = new java.math.BigInteger(1, [].concat(md5.digest())).toString(16);
878         return checksum;
879     } 
880     catch (err) {
881         log("Could not generate the hash value of the string " + err, 4, this.LOG);
882     }
883 };
884 
885 
886 /**
887  * Converts an IP address in a number of input forms into the standard dotted-quad notation used by Sentinel.
888  * @param {Number} radix  The radix of the input; 16 for hex, 8 for octal, etc. Default is 10 for decimal.
889  * @return {String} The converted IP address in normalized, dotted-quad form (network order)
890  */
891 String.prototype.convertIP = function (radix) {
892 	if (typeof radix == "undefined") { var radix = 10; }
893 	var inp = parseInt(this, radix);
894 	if (inp < 4294967295 ) {	
895 		var o1 = inp >> 24 & 255;
896 		var o2 = inp >> 16 & 255;
897 		var o3 = inp >> 8 & 255;
898 		var o4 = inp & 255;
899 		return o1 + "." + o2 + "." + o3 + "." + o4;
900 	} else {
901 		return "0.0.0.0";
902 	}
903 };
904