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 UUID of this Collector.
121 		 * @type UUID
122 		 */
123 		this.UUID=String(this.CONFIG.scriptContext.getCollectorID());
124 		
125 		/**
126 	 	 * Flag used to set the destination of audit output to a log
127 	 	 * @type Flag
128 	 	 */
129 		this.LOG = 0x1;
130 		/**
131 		 * Flag used to set the destination of audit output to an event.
132 		 * @type Flag
133 		 */
134 		this.EVENT = 0x2;
135 
136 		/**
137 		 * The directory in which the unpacked plugin files reside.
138 		 * @type String
139 		 */	
140 		this.CONFIG.collDir=String(this.CONFIG.scriptContext.getLocalDir()) + "/";
141 		/**
142 		 * The directory containing the Sentinel program
143 		 * @type String
144 		 */
145 		this.CONFIG.esecDir=this.CONFIG.collDir.slice(0,this.CONFIG.collDir.indexOf("data"));
146 		/**
147 		 * The directory in which common files (custom code, common maps) reside.
148 		 * @type String
149 		 */
150 		this.CONFIG.commonDir=this.CONFIG.esecDir + "data/collector_common/";
151 		/**
152 		 * The ESEC_HOME/log directory.
153 		 * @type String
154 		 */
155 		this.CONFIG.logDir=this.CONFIG.esecDir + "log/";
156 		
157 	} 
158 	
159 	/**
160 	 * Starts this Collector in ESM.
161 	 */
162 	this.start = function() {
163 		if( typeof this.collectorObject != "undefined" ) {
164 			this.collectorObject.start();
165 		}
166 	};
167 	
168 	/**
169 	 * Stops this Collector in ESM.
170 	 */
171 	this.stop = function() {
172 		if( typeof this.collectorObject != "undefined" ) {
173 			this.collectorObject.stop();
174 		} else {
175 			ScriptEngineUtil.setStopFlag(this.CONFIG.scriptContext.getCollectorID());
176 		}
177 	};
178 	
179 	
180 }
181 
182 /**
183  * The loadMaps method loads in a standard set of parameters and
184  * maps that will be used by this Collector to control operations and
185  * to transform objects.
186  * @return {Boolean} Result
187  * @see DataMap
188  * @see KeyMap
189  */
190 Collector.prototype.scriptInit = function() {
191 	
192 	/**
193 	 * A prototype Event object that is used to construct a new output event each time through
194 	 * the processing loop. 
195 	 * Note that you can pre-set static attributes on this prototype object by editing the protoEvt.map 
196 	 * file or by adding code to set attributes directly. Make sure you use the standard field labels.
197 	 * @type Event
198 	 */
199 	this.protoEvt = new Event();
200 	
201 	/**
202 	 * Flag which indicates whether the current event should be sent or not. Your code should set this
203 	 * to "true" once you've determined if the input record is a valid event and you have completed
204 	 * parsing.
205 	 * @type Boolean
206 	 */
207 	this.SEND_EVENT=false;
208 	
209 	/**
210 	 * Storage area for partial event sessions. 
211 	 * Put partial records in sessions stored here and attach expiration timers and parsers to them.
212 	 * @type Hash
213 	 * @see Session
214 	 */
215 	this.STORE = {};
216 
217 	/**
218 	 * Storage area for anonymous parsing routines - can then be attached to sessions.
219 	 * @type Hash
220 	 * @see Session
221 	 * @see SQLQuery
222 	 */
223 	this.PARSER = {};
224 	
225 	/**
226 	 * Area where SQLQuery objects are stored.
227 	 * The SQLQuery objects stored here should be named based on the event source UUIDs with which they are associated;
228 	 * ordinarily this will be handled automatically by the Connector when it detects new DB event sources, but you
229 	 * can manipulate these manually if necessary.
230 	 * @type Hash
231 	 * @see SQLQuery
232 	 */
233 	this.QUERIES = {};
234 	
235 	// Load in package.xml to set the standard stuff
236 	var packageFile=new File(this.CONFIG.collDir + "/package.xml");
237 	var packageText=packageFile.readFile();
238 	packageFile.close();
239 	var packageXML=new XML(packageText.substr(packageText.indexOf("<",1)));
240 	this.CONFIG.collectorScript=String(packageXML.DisplayName);
241 	this.CONFIG.collectorScriptSafe=String(packageXML.Name);
242 	this.protoEvt.CollectorScript=this.CONFIG.collectorScript;
243 	if (this.CONFIG.collectorScript == "Generic Event") {
244 		this.CONFIG.params.UseConnectorName = true;
245 	}
246 	this.CONFIG.collectorPluginId=String(packageXML.ID);
247 	this.protoEvt.CollectorPluginId=this.CONFIG.collectorPluginId;
248 	var protoMap = new KeyMap(this.CONFIG.collDir + "/protoEvt.map");
249 	for (var field in protoMap) {
250 		if ( field.hasOwnProperty && typeof protoMap[field] != "function" && field != "SourceFiles" ) {
251 			this.protoEvt[field] = protoMap.lookup(field)[0];
252 		}
253 	}
254 	
255  // Store the parameters in the global CONFIG object
256 	var paramFile=new File(this.CONFIG.collDir + "/parameters.csv");
257 	var linePair=[];
258 	for (var inputLine = paramFile.readLine(); typeof inputLine != 'undefined'; inputLine=paramFile.readLine()) {
259 		linePair=inputLine.safesplit(",","\"");
260 		this.CONFIG.params[linePair[0]]=linePair[1];
261 	}
262 	paramFile.close();
263 	
264 	// Default Connector retry behavior
265 	if ( typeof this.CONFIG.params.Conn_Retry == "undefined" ) {
266 		this.CONFIG.params.Conn_Retry = "falloff";
267 	}
268 	
269 	// read params and inject into prototype event obj
270 	this.protoEvt.MSSPCustomerName=this.CONFIG.params.MSSP_Customer;
271 	this.protoEvt.Severity=this.CONFIG.params.Default_Severity;
272 
273 	/**
274 	 * Default for database suboffset behavior; false 
275 	 * @see SQLQuery
276 	 */
277 	this.CONFIG.params.SubOffsets = false;
278 
279 	
280 	// Load record-to-event conversion map
281 	/**
282 	 * This object stores the maps that will be used to control record conversion and for looking
283 	 * up external business-relevance data. For example, the instance.MAPS.Rec2Evt map defines how normal
284 	 * records will be converted to output events.
285 	 * <p>Example:
286 	 * <pre>
287 	 * // in instance.initialize() method:
288 	 * this.MAPS.SeverityMap = new KeyMap(this.CONFIG.collDir + "/Severity.map");
289 	 * // When you need to apply it in rec.normalize():
290 	 * var srcSev = this.s_RXBufferString.substr(x,y);
291 	 * this.sentinelSev = instance.MAPS.SeverityMap.lookup(srcSev);
292 	 * </pre>
293 	 * @type Object
294 	 * 
295 	 */
296 	this.MAPS = {};
297 	this.MAPS.Rec2Evt = new DataMap(this.CONFIG.collDir + "/Rec2Evt.map");
298 	this.MAPS.Rec2Evt.makesafe(); // Escape the rec object references
299 	this.MAPS.Rec2Evt.compile();  // Precompile for speed
300 	this.MAPS.UnsupEvt = new DataMap(); // Blank map
301 	this.MAPS.UnsupEvt.compile();  // Precompile for speed
302 	
303 	// Create map to convert public JS map to Java EventData map
304 	this.MAPS.Evt2EvtData = new DataMap(this.CONFIG.collDir + "/Evt2EvtData.map");
305 	
306 	// Load taxonomy KeyMaps
307 	this.MAPS.tax = new KeyMap(this.CONFIG.collDir + "/taxonomy.map");
308 	this.MAPS.taxcode = new KeyMap(this.CONFIG.collDir + "/xdas_tax.map");
309 	this.MAPS.taxout = new KeyMap(this.CONFIG.collDir + "/xdas_out.map");
310 	
311 	// Load IP-to-hostname map
312 	if (this.CONFIG.params.Resolve_IP_and_Hostname == "yes") {
313 		this.MAPS.ip2n = new KeyMap(this.CONFIG.commonDir + "/ip2n.map");
314 		this.MAPS.n2ip = new KeyMap(this.CONFIG.commonDir + "/n2ip.map");
315 	}
316 	
317 	// Load IP-to-user map
318 	if (this.CONFIG.params.Resolve_IP_To_User == "yes") {
319 		this.MAPS.ip2u = new KeyMap(this.CONFIG.commonDir + "/ip2u.map");
320 	}
321 	
322 	/**
323 	 * Cache for Timezone calculations
324 	 * @type Array
325 	 */
326 	this.MAPS.TZMap=[];
327 	
328 	// Load custom parsing code
329 	if (this.CONFIG.params.ExecutionMode == "custom") {
330 		var customFile=new File(this.CONFIG.collDir + "custom.js");
331 		if (customFile.isOpen()) {
332 			try {
333 				eval(customFile.readFile()); // will overwrite stubs from above
334 				customFile.close();
335 			} catch(err) {
336 				log("Could not read from custom parsing file:" + err, 4, this.EVT);
337 			}
338 		}
339 		if (typeof this.customInit == "undefined" || typeof Record.prototype.customPreparse == "undefined" || typeof Record.prototype.customParse == "undefined" ) {
340 			this.CONFIG.params.ExecutionMode = "release";
341 			log("Custom parsing file not found or not complete; reverting to release mode", 4, this.EVENT & this.LOG);
342 		}
343 		// Call the customInit method
344 		this.customInit();
345 	}
346 	if ( this.CONFIG.params.ExecutionMode != "custom") {  // can't be an 'else'; this is here to provide the API docs
347 	
348 		/**
349 		 * This method is used to provide locally-defined custom initialization of the Collector.
350 		 * <p>Useful variables:
351 		 * <dl>
352 		 * <dt>instance.CONFIG.collDir</dt><dd>The directory that will contain all the Collector plugin files</dd>
353 		 * <dt>this.CONFIG.commonDir</dt><dd>The directory where this custom code resides - you can add additional files as necessary</dd>
354 		 * </dl>
355 		 * @return {Boolean} Result
356 		 */
357 		Collector.prototype.customInit = function() {	return true; }
358 		
359 		/**
360 		 * This method is used to provide locally-defined custom pre-parsing of the input record.
361 		 * You might use this method if something in your environment modifies the normal input format,
362 		 * for example if the event is tunneled through some other protocol, you might strip off
363 		 * any additional headers that were added.
364 		 * @param {Object} e  The output event
365 		 */
366 		Record.prototype.customPreparse = function(e) {	return true; }
367 		
368 		/**
369 		 * This method is used to provide locally-defined custom parsing of the input record.
370 		 * NOTE: There are two types of modifications that are typically peformed:
371 		 * <ul>
372 		 * <li>Modifications to the output of existing event fields: in this case, you may need to
373 		 * debug the Collector to determine which Record attribute is used to hold the data, and then
374 		 * perform your custom transformation on that data. For example:<br/>
375 		 * <pre>
376 		 * // Main code sets rec.evt to raw event name from device, but these overlap with
377 		 * // event names from other devices in our environment so are hard to distinguish.
378 		 * // We will add a prefix to help identify the events
379 		 * this.evt = "FW: " + this.evt;
380 		 * </pre></li>
381 		 * <li>Additional custom parsing that pulls more specific pieces of information out of the event,
382 		 * for example if a free-text field contains some info that you want to use to categorize events
383 		 * in your environment. In this scenario, you should:
384 		 * <ul>
385 		 * <li>Only use CustomerVars to hold the parsed-out data</li>
386 		 * <li>You will need to manipulate the Event object directly, as new additions to the Record object
387 		 * will be lost. The 'e' variable is used to access the Event object.</li>
388 		 * </ul>
389 		 * Use the 'CustomerVar1' through 'CustomerVar300' to hold your data. Refer to the documentation
390 		 * for the datatypes of those variables.
391 		 * <pre>
392 		 * // Want to extract the Department name that is injected into the "message" field
393 		 * e.CustomerVar21 = rec.message.substr(12,34);
394 		 * </pre>
395 		 * @param {Object} e  The output event
396 		 */
397 		Record.prototype.customParse = function(e) { return true; }
398 	}
399 	
400 	// Open debug file
401 	if (this.CONFIG.params.ExecutionMode == "debug") {
402 		this.CONFIG.debugFile=new File(this.CONFIG.logDir + this.UUID + "-" + new Date().getTime() + ".json", "w");
403 	}
404 
405 	return true;
406 };
407 
408 /**
409  * The sleep method simply puts the Collector to sleep for the specified number of seconds.
410  * @param {Number} sec  Number of seconds to sleep
411  * @return {Boolean} Result
412  */	
413 Collector.prototype.sleep = function(sec) {
414 	var msec = sec * 1000;
415 	try {
416     java.lang.Thread.sleep(msec);
417   } catch (e) {
418 		log(e);
419   }
420 		return true;
421 };
422 
423 /**
424  * The reset method resets the Collector after processing is completed for the previous
425  * record. It will create a new, empty record to receive the next input, and also a new
426  * Event object that is a clone of the prototype event (so that static fields can be preset).
427  * <p>
428  * Side effects:
429  * <ul>
430  * <li><code>instance.SEND_EVENT</code> is set to false
431  * <li><code>rec</code> is created as an empty Record object
432  * <li><code>curEvt</code> is created as a clone of the prototype Event object
433  * <li>Host/IP resolution maps are refreshed
434  * <li>IP/User resolution maps are refreshed
435  * <li>Any stored Session objects that are ready for processing are processed
436  * </ul> 
437  * @return {Boolean} Result
438  */
439 Collector.prototype.reset = function() {
440 	
441 	// Default to sending an event only if the code explicitly sets SEND_EVENT to true 
442 	this.SEND_EVENT=false;
443 	
444 	if (this.CONFIG.params.Resolve_IP_and_Hostname == "yes") {
445 	  this.MAPS.ip2n.refresh();
446 	  this.MAPS.n2ip.refresh();
447 	}
448 	if (instance.CONFIG.params.Resolve_IP_To_User == "yes") {
449 		this.MAPS.ip2u.refresh();
450 	}
451 	
452 	// Before getting the next record, check if there are any waiting DB queries to send
453 	for each ( var query in this.QUERIES ) {
454 		if (query.needQuery && new Date().isAfter(query.queryScheduled)) {
455 			conn.send(query);
456 			query.needQuery = false;
457 		}
458 	}
459 	
460 	// Before getting the next record, check if there are any waiting sessions to be processed
461 	for each ( var key in this.STORE ) {
462 		if ( key.retrieve().length >= key.MaxRecs || new Date().getTime() > key.WallTimer ) {
463 		try {
464 			key.Parser();
465 		} catch(err) {
466 			log("Parsing failed in Session", 5, instance.LOG|instance.EVENT);
467 		}
468 		key.clear();
469 	 }
470 	}
471 	
472 	return true;
473 };
474