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