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  * @fileoverview
 18  * This file defines the Collector class, which represents the Collector object itself in ESM.
 19  * In the context of the Collector script itself, it also represents the execution environment
 20  * and stores variables and parameters that control the Collector.
 21  * @name Collector Class
 22  */
 23 
 24 
 25 /**
 26  * This class represents the Collector ESM object.
 27  * The Collector class provides methods used to manipulate Collectors in ESM such as starting and stopping,
 28  * and is also used to contain the execution environment for a running Collector script.
 29  * @class
 30  * The Collector template creates an instance of this class (called <code>instance</code>) to contain 
 31  * a number of utility data objects and methods. These data objects control operation of the Collector,
 32  * define the environment, and help locate files on disk.
 33  * The template handles most things to do with the Collector object, but if you need to reference
 34  * parameters to affect parsing, or use the Session multi-line record handling routines, you will
 35  * use this class.
 36  * @author   Novell Engineering
 37  * @version  6.1
 38  * @param {String} uuid  ID of the Collector you want to access.
 39  * @constructor
 40  */
 41 
 42 function Collector(uuid) {
 43 	
 44 	if (typeof uuid != "undefined" && typeof ESM != "undefined") {
 45 		/**
 46 		 * The UUID of this Collector.
 47 		 * @type UUID
 48 		 */
 49 		this.UUID = uuid;
 50 		this.collectorObject = ESM.collectorForId(this.UUID);
 51 	}
 52 	
 53 	if ( typeof scriptEnv == "undefined" ) { // this is a Collector
 54 		
 55 		// importPackage( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util );
 56 		// Bug in importPackage - import classes separately
 57 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.AgentInfo );
 58 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.BaseEngine );
 59 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.BSFEngine );
 60 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.CollectorConfiguration );
 61 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.Constants );
 62 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.ContextInfoSender );
 63 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.DBCollectorHelper );
 64 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.EventData );
 65 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.DebugInfoSender );
 66 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.EventDataDebugger );
 67 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.FormatDate );
 68 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.InfoBlockSession );
 69 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.InfoSender );
 70 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.MatchResult );
 71 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.OSEnv );
 72 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.ScriptEngineContext );
 73 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.ScriptEngineUtil );
 74 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.TagValue );
 75 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.collector.util.FileUtil );
 76 
 77 		// importPackage( Packages.esecurity.ccs.comp.evtsrcmgt.connectorapi );
 78 		// Bug in importPackage - import classes separately
 79 		importClass( Packages.esecurity.ccs.comp.evtsrcmgt.connectorapi.ConnectorData );	
 80 
 81 		importClass( java.util.logging.Level );
 82 		importClass( java.util.Map );
 83 		importClass( java.util.HashMap );
 84 		
 85 		/**
 86 		 * An object that contains various configuration information about this Collector 
 87 		 * like parameters, maps, etc.
 88 		 * <p>Here are the usual contents:
 89 		 * <ul>
 90 		 * <li><code>instance.CONFIG.params.<parameter></code>: An object that contains the parameters 
 91  	   * set during configuration of this Collector.
 92  	   * <li><code>instance.CONFIG.scriptContext</code>: An instance of the script context, which is 
 93  	   * the Java representation of the running script.
 94  	   * <li><code>instance.CONFIG.collDir</code>: The directory in which this Collector is located. 
 95  	   * Can be used to find external files to load.
 96  	   * <li><code>instance.CONFIG.commonDir</code>: The directory used to hold files that can be 
 97  	   * referenced by all Collectors.
 98  	   * @type Object
 99 		 */
100 		this.CONFIG = {};
101 	
102 		/**
103 		 * This object contains the runtime configuration parameters for this Collector, as
104 		 * defined via the ESM panel.
105 		 * Look up the rutime parameters by using:
106 		 * <code>instance.CONFIG.params.<parameter></code>
107  	   * @type Object
108 		 */
109 		this.CONFIG.params = {};
110 
111 		/**
112 		 * Object representing the context in which the script is running.
113 		 * @type ScriptEngineContext (Java)
114 		 */
115 		this.CONFIG.scriptContext = ScriptEngineUtil.getContext();
116 	
117 		ScriptEngineUtil.log( Level.FINEST, "Initializing collector");
118 		
119 		/**
120 		 * The Sentinel schema version in use on the platform this Collector is running on
121 		 * @type Number
122 		 */
123 		if (ScriptEngineUtil.hasFeature("Schema/7.0.0")) {
124 			this.CONFIG.params.schemaVersion = 700;
125 		} else {
126 			this.CONFIG.params.schemaVersion = 600;
127 		}
128 		
129 		/**
130 		 * The UUID of this Collector.
131 		 * @type UUID
132 		 */
133 		this.UUID=String(this.CONFIG.scriptContext.getCollectorID());
134 		
135 		/**
136 	 	 * Flag used to set the destination of audit output to a log
137 	 	 * @type Flag
138 	 	 */
139 		this.LOG = 0x1;
140 		/**
141 		 * Flag used to set the destination of audit output to an event.
142 		 * @type Flag
143 		 */
144 		this.EVENT = 0x2;
145 
146 		/**
147 		 * The directory in which the unpacked plugin files reside.
148 		 * @type String
149 		 */	
150 		this.CONFIG.collDir=String(this.CONFIG.scriptContext.getLocalDir()) + "/";
151 		/**
152 		 * The directory containing the Sentinel program
153 		 * @type String
154 		 */
155 		this.CONFIG.esecDir=this.CONFIG.collDir.slice(0,this.CONFIG.collDir.indexOf("data"));
156 		/**
157 		 * The directory in which common files (custom code, common maps) reside.
158 		 * @type String
159 		 */
160 		this.CONFIG.commonDir=this.CONFIG.esecDir + "data/collector_common/";
161 		/**
162 		 * The ESEC_HOME/log directory.
163 		 * @type String
164 		 */
165 		this.CONFIG.logDir=this.CONFIG.esecDir + "log/";
166 		
167 	} 
168 	
169 	/**
170 	 * Starts this Collector in ESM.
171 	 */
172 	this.start = function() {
173 		if( typeof this.collectorObject != "undefined" ) {
174 			this.collectorObject.start();
175 		}
176 	};
177 	
178 	/**
179 	 * Stops this Collector in ESM.
180 	 */
181 	this.stop = function() {
182 		if( typeof this.collectorObject != "undefined" ) {
183 			this.collectorObject.stop();
184 		} else {
185 			ScriptEngineUtil.setStopFlag(this.CONFIG.scriptContext.getCollectorID());
186 		}
187 	};
188 	
189 	
190 }
191 
192 /**
193  * The loadMaps method loads in a standard set of parameters and
194  * maps that will be used by this Collector to control operations and
195  * to transform objects.
196  * @return {Boolean} Result
197  * @see DataMap
198  * @see KeyMap
199  */
200 Collector.prototype.scriptInit = function() {
201 	
202 	/**
203 	 * A prototype Event object that is used to construct a new output event each time through
204 	 * the processing loop. 
205 	 * Note that you can pre-set static attributes on this prototype object by editing the protoEvt.map 
206 	 * file or by adding code to set attributes directly. Make sure you use the standard field labels.
207 	 * @type Event
208 	 */
209 	this.protoEvt = new Event();
210 	
211 	/**
212 	 * Flag which indicates whether the current event should be sent or not. Your code should set this
213 	 * to "true" once you've determined if the input record is a valid event and you have completed
214 	 * parsing.
215 	 * @type Boolean
216 	 */
217 	this.SEND_EVENT=false;
218 	
219 	/**
220 	 * Storage area for partial event sessions. 
221 	 * Put partial records in sessions stored here and attach expiration timers and parsers to them.
222 	 * @type Hash
223 	 * @see Session
224 	 */
225 	this.STORE = {};
226 
227 	/**
228 	 * Storage area for anonymous parsing routines - can then be attached to sessions.
229 	 * @type Hash
230 	 * @see Session
231 	 * @see SQLQuery
232 	 */
233 	this.PARSER = {};
234 	
235 	/**
236 	 * Area where SQLQuery objects are stored.
237 	 * The SQLQuery objects stored here should be named based on the event source UUIDs with which they are associated;
238 	 * ordinarily this will be handled automatically by the Connector when it detects new DB event sources, but you
239 	 * can manipulate these manually if necessary.
240 	 * @type Hash
241 	 * @see SQLQuery
242 	 */
243 	this.QUERIES = {};
244 	
245 	// Load in package.xml to set the standard stuff
246 	var packageFile=new File(this.CONFIG.collDir + "/package.xml");
247 	var packageText=packageFile.readFile();
248 	packageFile.close();
249 	var packageXML=new XML(packageText.substr(packageText.indexOf("<",1)));
250 	this.CONFIG.collectorScript=String(packageXML.DisplayName);
251 	this.CONFIG.collectorScriptSafe=String(packageXML.Name);
252 	this.protoEvt.CollectorPluginName=this.CONFIG.collectorScript;
253 	if (this.CONFIG.collectorScript == "Generic Event") {
254 		this.CONFIG.params.UseConnectorName = true;
255 	}
256 	this.CONFIG.collectorPluginId=String(packageXML.ID);
257 	this.protoEvt.CollectorPluginID=this.CONFIG.collectorPluginId;
258 	var protoMap = new KeyMap(this.CONFIG.collDir + "/protoEvt.map");
259 	for (var field in protoMap) {
260 		if ( field.hasOwnProperty && typeof protoMap[field] != "function" && field != "SourceFiles" ) {
261 			this.protoEvt[field] = protoMap.lookup(field)[0];
262 		}
263 	}
264 	
265 	// Store the parameters in the global CONFIG object
266 	var paramFile=new File(this.CONFIG.collDir + "/parameters.csv");
267 	var linePair=[];
268 	for (var inputLine = paramFile.readLine(); typeof inputLine != 'undefined'; inputLine=paramFile.readLine()) {
269 		linePair=inputLine.safesplit(",","\"");
270 		// True Boolean parameters are not supported, but convert any parameters set to 'yes/no' or 'true/false' to a real Boolean
271 		if (linePair[1] == "yes" || linePair[1] == "true") {
272 			linePair[1] = true;
273 		} else if (linePair[1] == "no" || linePair[1] == "false") {
274 			linePair[1] = false;
275 		}
276 		this.CONFIG.params[linePair[0]]=linePair[1];
277 	}
278 	paramFile.close();
279 	
280 	// Default Connector retry behavior
281 	if ( typeof this.CONFIG.params.Conn_Retry == "undefined" ) {
282 		this.CONFIG.params.Conn_Retry = "falloff";
283 	}
284 	
285 	// read params and inject into prototype event obj
286 	this.protoEvt.TenantName=this.CONFIG.params.TenantName;
287 	this.protoEvt.Severity=this.CONFIG.params.Default_Severity;
288 
289 	/**
290 	 * Default for database suboffset behavior; false 
291 	 * @see SQLQuery
292 	 */
293 	this.CONFIG.params.SubOffsets = false;
294 	this.CONFIG.params.batchLimit = 5000;
295 	
296 	// Load record-to-event conversion map
297 	/**
298 	 * This object stores the maps that will be used to control record conversion and for looking
299 	 * up external business-relevance data. For example, the instance.MAPS.Rec2Evt map defines how normal
300 	 * records will be converted to output events.
301 	 * <p>Example:
302 	 * <pre>
303 	 * // in instance.initialize() method:
304 	 * this.MAPS.SeverityMap = new KeyMap(this.CONFIG.collDir + "/Severity.map");
305 	 * // When you need to apply it in rec.normalize():
306 	 * var srcSev = this.s_RXBufferString.substr(x,y);
307 	 * this.sentinelSev = instance.MAPS.SeverityMap.lookup(srcSev);
308 	 * </pre>
309 	 * @type Object
310 	 * 
311 	 */
312 	this.MAPS = {};
313 	this.MAPS.Rec2Evt = new DataMap(this.CONFIG.collDir + "/Rec2Evt.map");
314 	this.MAPS.UnsupEvt = new DataMap(); // Blank map
315 	
316 	// Create map to convert public JS map to Java EventData map
317 	this.MAPS.Evt2EvtData = new DataMap(this.CONFIG.collDir + "/Evt2EvtData.map", true);
318 	
319 	// Load taxonomy KeyMaps
320 	this.MAPS.tax = new KeyMap(this.CONFIG.collDir + "/taxonomy.map");
321 	this.MAPS.taxcode = new KeyMap(this.CONFIG.collDir + "/xdas_tax.map");
322 	this.MAPS.taxout = new KeyMap(this.CONFIG.collDir + "/xdas_out.map");
323 	
324 	// Load IP-to-hostname map
325 	if (this.CONFIG.params.Resolve_IP_and_Hostname) {
326 		this.MAPS.ip2n = new KeyMap(this.CONFIG.commonDir + "/ip2n.map");
327 		this.MAPS.n2ip = new KeyMap(this.CONFIG.commonDir + "/n2ip.map");
328 	}
329 	
330 	// Load IP-to-user map
331 	if (this.CONFIG.params.Resolve_IP_To_User) {
332 		this.MAPS.ip2u = new KeyMap(this.CONFIG.commonDir + "/ip2u.map");
333 	}
334 	
335 	// Load privileged user map
336 	this.CONFIG.params.lookupPriv = false;
337 	this.MAPS.privLevel = new KeyMap(this.CONFIG.collDir + "/privlevel.map");
338 	if (this.MAPS.privLevel.length > 0) {
339 		this.CONFIG.params.lookupPriv = true;
340 	}
341 	
342 	// Load data sensitivity map
343 	this.CONFIG.params.lookupSens = false;
344 	this.MAPS.sensitivity = new KeyMap(this.CONFIG.collDir + "/datasensitivity.map");
345 	if (this.MAPS.sensitivity.length > 0) {
346 		this.CONFIG.params.lookupSens = true;
347 	}
348 	
349 	/**
350 	 * Cache for Timezone calculations
351 	 * @type Array
352 	 */
353 	this.MAPS.TZMap=[];
354 	
355 	// Load custom parsing code
356 	if (this.CONFIG.params.ExecutionMode == "custom") {
357 		var customFile=new File(this.CONFIG.collDir + "custom.js");
358 		if (customFile.isOpen()) {
359 			try {
360 				eval(customFile.readFile()); // will overwrite stubs from above
361 				customFile.close();
362 			} catch(err) {
363 				log("Could not read from custom parsing file:" + err, 4, this.EVT);
364 			}
365 		}
366 		if (typeof this.customInit == "undefined" || typeof Record.prototype.customPreparse == "undefined" || typeof Record.prototype.customParse == "undefined" ) {
367 			this.CONFIG.params.ExecutionMode = "release";
368 			log("Custom parsing file not found or not complete; reverting to release mode", 4, this.EVENT & this.LOG);
369 		}
370 	}
371 	if ( this.CONFIG.params.ExecutionMode != "custom") {  // can't be an 'else'; this is here to provide the API docs
372 	
373 		/**
374 		 * This method is used to provide locally-defined custom initialization of the Collector.
375 		 * <p>Useful variables:
376 		 * <dl>
377 		 * <dt>instance.CONFIG.collDir</dt><dd>The directory that will contain all the Collector plugin files</dd>
378 		 * <dt>this.CONFIG.commonDir</dt><dd>The directory where this custom code resides - you can add additional files as necessary</dd>
379 		 * </dl>
380 		 * @return {Boolean} Result
381 		 */
382 		Collector.prototype.customInit = function() {	return true; }
383 		
384 		/**
385 		 * This method is used to provide locally-defined custom pre-parsing of the input record.
386 		 * You might use this method if something in your environment modifies the normal input format,
387 		 * for example if the event is tunneled through some other protocol, you might strip off
388 		 * any additional headers that were added.
389 		 * @param {Object} e  The output event
390 		 */
391 		Record.prototype.customPreparse = function(e) {	return true; }
392 		
393 		/**
394 		 * This method is used to provide locally-defined custom parsing of the input record.
395 		 * NOTE: There are two types of modifications that are typically peformed:
396 		 * <ul>
397 		 * <li>Modifications to the output of existing event fields: in this case, you may need to
398 		 * debug the Collector to determine which Record attribute is used to hold the data, and then
399 		 * perform your custom transformation on that data. For example:<br/>
400 		 * <pre>
401 		 * // Main code sets rec.evt to raw event name from device, but these overlap with
402 		 * // event names from other devices in our environment so are hard to distinguish.
403 		 * // We will add a prefix to help identify the events
404 		 * this.evt = "FW: " + this.evt;
405 		 * </pre></li>
406 		 * <li>Additional custom parsing that pulls more specific pieces of information out of the event,
407 		 * for example if a free-text field contains some info that you want to use to categorize events
408 		 * in your environment. In this scenario, you should:
409 		 * <ul>
410 		 * <li>Only use CustomerVars to hold the parsed-out data</li>
411 		 * <li>You will need to manipulate the Event object directly, as new additions to the Record object
412 		 * will be lost. The 'e' variable is used to access the Event object.</li>
413 		 * </ul>
414 		 * Use the 'CustomerVar1' through 'CustomerVar300' to hold your data. Refer to the documentation
415 		 * for the datatypes of those variables.
416 		 * <pre>
417 		 * // Want to extract the Department name that is injected into the "message" field
418 		 * e.CustomerVar21 = rec.message.substr(12,34);
419 		 * </pre>
420 		 * @param {Object} e  The output event
421 		 */
422 		Record.prototype.customParse = function(e) { return true; }
423 	}
424 	
425 	// Open debug file
426 	if (this.CONFIG.params.ExecutionMode == "debug") {
427 		this.CONFIG.debugFile=new File(this.CONFIG.logDir + this.UUID + "-" + new Date().getTime() + ".json", "w");
428 	}
429 
430 	return true;
431 };
432 
433 /**
434  * The sleep method simply puts the Collector to sleep for the specified number of seconds.
435  * @param {Number} sec  Number of seconds to sleep
436  * @return {Boolean} Result
437  */	
438 Collector.prototype.sleep = function(sec) {
439 	var msec = sec * 1000;
440 	try {
441     java.lang.Thread.sleep(msec);
442   } catch (e) {
443 		log(e);
444   }
445 		return true;
446 };
447 
448 /**
449  * The reset method resets the Collector after processing is completed for the previous
450  * record. It will create a new, empty record to receive the next input, and also a new
451  * Event object that is a clone of the prototype event (so that static fields can be preset).
452  * <p>
453  * Side effects:
454  * <ul>
455  * <li><code>instance.SEND_EVENT</code> is set to false
456  * <li><code>rec</code> is created as an empty Record object
457  * <li><code>curEvt</code> is created as a clone of the prototype Event object
458  * <li>Host/IP resolution maps are refreshed
459  * <li>IP/User resolution maps are refreshed
460  * <li>Any stored Session objects that are ready for processing are processed
461  * </ul> 
462  * @return {Boolean} Result
463  */
464 Collector.prototype.reset = function() {
465 	
466 	// Default to sending an event only if the code explicitly sets SEND_EVENT to true 
467 	this.SEND_EVENT=false;
468 	
469 	if (this.CONFIG.params.Resolve_IP_and_Hostname) {
470 	  this.MAPS.ip2n.refresh();
471 	  this.MAPS.n2ip.refresh();
472 	}
473 	if (instance.CONFIG.params.Resolve_IP_To_User) {
474 		this.MAPS.ip2u.refresh();
475 	}
476 	
477 	// Before getting the next record, check if there are any waiting DB queries to send
478 	for each ( var query in this.QUERIES ) {
479 		if (query.needQuery && new Date().isAfter(query.queryScheduled)) {
480 			conn.send(query);
481 			query.needQuery = false;
482 		}
483 	}
484 	
485 	// Before getting the next record, check if there are any waiting sessions to be processed
486 	for each ( var key in this.STORE ) {
487 		if ( key.retrieve().length >= key.MaxRecs || new Date().getTime() > key.WallTimer ) {
488 		try {
489 			key.Parser();
490 		} catch(err) {
491 			log("Parsing failed in Session", 5, instance.LOG|instance.EVENT);
492 		}
493 		key.clear();
494 	 }
495 	}
496 	
497 	return true;
498 };
499