1 /*
2 * Use this file as a template to create a C plug-in for use with
3 * the SiLK tools rwfilter, rwcut, rwgroup, rwsort, rwstats, or
4 * rwuniq.
5 */
6
7 #include <silk/silk.h>
8 #include <silk/rwrec.h>
9 #include <silk/skipaddr.h>
10 #include <silk/skplugin.h>
11 #include <silk/utils.h>
12
13
14 /* DEFINES AND TYPEDEFS */
15
16 /*
17 * These variables specify the version of the SiLK plug-in API.
18 * They are used in the call to skpinSimpleCheckVersion() below.
19 * See the description of that function for their meaning.
20 */
21 #define PLUGIN_API_VERSION_MAJOR 1
22 #define PLUGIN_API_VERSION_MINOR 0
23
24
25 /* LOCAL FUNCTION PROTOTYPES */
26
27 /* a convenience structure to define command line options. you don't
28 * have to use this. */
29 typedef struct option_info_st {
30 /* this is mask specifying the applications for which this option
31 * should be available. use SKPLUGIN_FN_ANY for the option to be
32 * available on all applications. */
33 skplugin_fn_mask_t apps;
34 /* the name of the option (the command line switch) */
35 const char *name;
36 /* whether it requires an argument, one of REQUIRED_ARG,
37 * OPTIONAL_ARG, or NO_ARG */
38 int has_arg;
39 /* a unique value for this option */
40 int val;
41 /* the option's help string; printed when --help is given */
42 char *help;
43 } option_info_t;
44
45
46 /* LOCAL VARIABLES */
47
48 /*
49 * In this sample, we have the plug-in create two switches --one
50 * and --two, but --two is only available on rwfilter. For their
51 * IDs, we define an enumeration.
52 */
53 typedef enum options_enum_en {
54 OPT_ONE, OPT_TWO
55 } options_enum;
56
57 static const option_info_t my_options[] = {
58 {SKPLUGIN_FN_ANY, "one", REQUIRED_ARG, OPT_ONE,
59 "my first option"},
60 {SKPLUGIN_FN_FILTER, "two", REQUIRED_ARG, OPT_TWO,
61 "my second option"},
62 {0, 0, 0, 0, 0} /* sentinel */
63 };
64
65
66
67
68 /* LOCAL FUNCTION PROTOTYPES */
69
70 static skplugin_err_t
71 optionsHandler(
72 const char *opt_arg,
73 void *reg_data);
74 static skplugin_err_t
75 initialize(
76 void *reg_data);
77 static skplugin_err_t
78 cleanup(
79 void *reg_data);
80 static skplugin_err_t
81 recToText(
82 const rwRec *rwrec,
83 char *text_value,
84 size_t text_size,
85 void *reg_data,
86 void **extra);
87 static skplugin_err_t
88 recToBin(
89 const rwRec *rwrec,
90 uint8_t *bin_value,
91 void *reg_data,
92 void **extra);
93 static skplugin_err_t
94 binToText(
95 const uint8_t *bin_value,
96 char *text_value,
97 size_t text_size,
98 void *reg_data);
99 static skplugin_err_t
100 addRecToBin(
101 const rwRec *rwrec,
102 uint8_t *bin_value,
103 void *reg_data,
104 void **extra);
105 static skplugin_err_t
106 binMerge(
107 uint8_t *dst_bin_value,
108 const uint8_t *src_bin_value,
109 void *reg_data);
110 static skplugin_err_t
111 binCompare(
112 int *cmp_result,
113 const uint8_t *value_a,
114 const uint8_t *value_b,
115 void *reg_data);
116 static skplugin_err_t
117 filter(
118 const rwRec *rwrec,
119 void *reg_data,
120 void **extra);
121 static skplugin_err_t
122 transform(
123 rwRec *rwrec,
124 void *reg_data,
125 void **extra);
126
127
128
129 /* FUNCTION DEFINITIONS */
130
131 /*
132 * This is the registration function.
133 *
134 * When you provide "--plugin=my-plugin.so" on the command line to
135 * an application, the application calls this function to determine
136 * the new switches and/or fields that "my-plugin" provides.
137 *
138 * This function is called with three arguments: the first two
139 * describe the version of the plug-in API, and the third is a
140 * pointer that is currently unused.
141 */
142 skplugin_err_t
SKPLUGIN_SETUP_FN(uint16_t major_version,uint16_t minor_version,void * plug_in_data)143 SKPLUGIN_SETUP_FN(
144 uint16_t major_version,
145 uint16_t minor_version,
146 void *plug_in_data)
147 {
148 int i;
149 skplugin_field_t *field;
150 skplugin_err_t rv;
151 skplugin_callbacks_t regdata;
152
153 /* Check the plug-in API version */
154 rv = skpinSimpleCheckVersion(major_version, minor_version,
155 PLUGIN_API_VERSION_MAJOR,
156 PLUGIN_API_VERSION_MINOR,
157 skAppPrintErr);
158 if (rv != SKPLUGIN_OK) {
159 return rv;
160 }
161
162 /*
163 * Register the options. Note that we pass the option identifier
164 * in the cddata field as a void*.
165 */
166 for (i = 0; my_options[i].name; ++i) {
167 rv = skpinRegOption2(my_options[i].name,
168 my_options[i].has_arg,
169 my_options[i].help,
170 &optionsHandler, NULL,
171 (void*)&my_options[i].val,
172 1, my_options[i].apps);
173 /* it's (probably) not an error if the option was not
174 * registered. in our example, option "two" is only
175 * registered when the plug-in is loaded by rwfilter. */
176 if (SKPLUGIN_OK != rv && SKPLUGIN_ERR_DID_NOT_REGISTER != rv) {
177 return rv;
178 }
179 }
180
181
182 /*
183 * All of the skplugin registration functions take a pointer to
184 * an skplugin_callbacks_t structure. If the structure has
185 * valid values for the fields that an application requires,
186 * the application will register the field, the filter
187 * function, or the transform function. If the structure does
188 * not have valid values for fields, the application ignores
189 * the registration call.
190 *
191 * When defining multiple fields or filters within a single
192 * plug-in, there approaches:
193 *
194 * 1. You can create functions that operate on each field
195 * individually. In this approach, you may have recToText1()
196 * and recToText2().
197 *
198 * 2. You can create a function that computes the value for
199 * multiple fields. In this approach, the 'reg_data' field is
200 * often used to tell the function which field to compute.
201 *
202 * Either approach is fine. The first approach can be easier,
203 * but it can lead to a lot of duplicate code.
204 */
205
206
207
208 memset(®data, 0, sizeof(regdata));
209 /* set the fields on the skplugin_callbacks_t structure. This
210 * example shows a value for each field, but you only need to set
211 * the values that you require. */
212
213 /* when special initialization is required by the 'filter' or
214 * 'transform' functions or for the field in rwcut, rwgroup,
215 * rwsort, rwstats, or rwuniq, specify a function that should be
216 * called just before the application begins to process
217 * records. */
218 regdata.init = &initialize;
219 /* when special clean-up is required by the 'filter' or
220 * 'transform' functions or for the field in rwcut, rwgroup,
221 * rwsort, rwstats, or rwuniq, specify a function that
222 * should be during shutdown. */
223 regdata.cleanup = &cleanup;
224 /* when defining a field for rwcut, rwstats, or rwuniq, specify
225 * the desired width of the output column to use for the textual
226 * output. */
227 regdata.column_width = 0;
228 /* when defining a field for rwgroup, rwsort, rwstats, or rwuniq,
229 * specify the number of byte required to hold the binary
230 * representation of the field. */
231 regdata.bin_bytes = 0;
232 /* when defining a key field for rwcut, specify a function to
233 * convert the rwRec to a textual field. */
234 regdata.rec_to_text = &recToText;
235 /* when defining a key field for rwgroup, rwsort, rwstats, or
236 * rwuniq, specify a function to convert the rwRec to a binary
237 * value. The length of the returned value should be exactly
238 * bin_bytes. */
239 regdata.rec_to_bin = &recToBin;
240 /* when defining an aggregate value field for rwstats or rwuniq,
241 * specify a function to update a binary value with a value based
242 * on the current rwRec. */
243 regdata.add_rec_to_bin = &addRecToBin;
244 /* when defining a key field or an aggregate value field for
245 * rwstats or rwuniq, specify a function to convert the binary
246 * value created by rec_to_bin (for keys) or add_rec_to_bin (for
247 * aggregate values) to a textual field. */
248 regdata.bin_to_text = &binToText;
249 /* when defining an aggregate value field for rwstats or rwuniq,
250 * specify a function to merge two binary values created by
251 * add_rec_to_bin. */
252 regdata.bin_merge = &binMerge;
253 /* when defining an aggregate value field for rwstats, specify a
254 * function to compare two the binary values created by
255 * add_rec_to_bin. This is required to sort the output in
256 * rwstats. */
257 regdata.bin_compare = &binCompare;
258 /* when defining an aggregate value field for rwstats or rwuniq,
259 * specify the value to use to initialize the aggregate value.
260 * This value should be exactly 'bin_bytes' long. If not
261 * specified, the value is zeroed. */
262 regdata.initial = NULL;
263 /* when defining a partioning rule for use in rwfilter, specify a
264 * function that determines whether a record passes or fails. */
265 regdata.filter = &filter;
266 /* when defining a function that modifies SiLK records (for
267 * example, in rwptoflow), specify a function that transforms the
268 * SiLK record. */
269 regdata.transform = &transform;
270 /* this will only be required for complicated plug-ins and is not
271 * described here. */
272 regdata.extra = NULL;
273
274
275 rv = skpinRegField(&field, /* handle to new field */
276 "field_name", /* field name */
277 "field description", /* field description */
278 ®data, /* skplugin_callbacks_t */
279 NULL); /* reg_data */
280 if (SKPLUGIN_OK != rv) {
281 return rv;
282 }
283
284 /* return one of the following */
285 return SKPLUGIN_OK;
286 return SKPLUGIN_ERR;
287 }
288
289
290 /*
291 * status = optionsHandler(opt_arg, reg_data);
292 *
293 * Handles options for the plug-in. Note that the skpinRegOption()
294 * function call above contains a pointer to this function.
295 *
296 * This function is called when the application sees an option that
297 * the plug-in has registered. 'opt_arg' is the argument to the
298 * option (for example, if the user specifies --foo=23, then
299 * 'opt_arg' will be the character buffer "23") or NULL if no
300 * argument was given.
301 *
302 * The 'reg_data' will be the value you specified when you
303 * registered the option.
304 *
305 * Returns SKPLUGIN_OK on success, or SKPLUGIN_ERR if there was a
306 * problem.
307 */
308 static skplugin_err_t
optionsHandler(const char * opt_arg,void * reg_data)309 optionsHandler(
310 const char *opt_arg,
311 void *reg_data)
312 {
313 options_enum opt_index = *((options_enum*)reg_data);
314 skplugin_callbacks_t regdata;
315
316 switch (opt_index) {
317 case OPT_ONE:
318 /* handle option "one" */
319 break;
320
321 case OPT_TWO:
322 /* part of handling option "two" is to register a filter.
323 * This is one way to write a plug-in that allows the user to
324 * choose from among multiple filters */
325 memset(®data, 0, sizeof(regdata));
326 regdata.filter = &filter;
327 return skpinRegFilter(NULL, ®data, NULL);
328 }
329
330 /* return one of the following */
331 return SKPLUGIN_OK;
332 return SKPLUGIN_ERR;
333 }
334
335
336 /*
337 * status = initialize(reg_data);
338 *
339 * This function only needs to be specified when special
340 * initialization code is required. You do not have to have an
341 * initialize function.
342 *
343 * The 'reg_data' will be the value you specified when you
344 * registered the field. You do not have to use that value.
345 */
346 static skplugin_err_t
initialize(void * reg_data)347 initialize(
348 void *reg_data)
349 {
350 /* return one of the following */
351 return SKPLUGIN_OK;
352 return SKPLUGIN_ERR_FATAL;
353 }
354
355
356 /*
357 * status = cleanup(reg_data);
358 *
359 * This function only needs to be specified when special cleanup
360 * code is required. You do not have to have an cleanup function.
361 *
362 * The 'reg_data' will be the value you specified when you
363 * registered the field. You do not have to use that value.
364 */
365 static skplugin_err_t
cleanup(void * reg_data)366 cleanup(
367 void *reg_data)
368 {
369 /* return one of the following */
370 return SKPLUGIN_OK;
371 return SKPLUGIN_ERR_FATAL;
372 }
373
374
375 /*
376 * status = recToText(rwrec, text_value, text_size, reg_data, extra);
377 *
378 * A function similar to this is required when the plug-in will be
379 * used to create a key field for use by rwcut.
380 *
381 * The function should use the SiLK flow record 'rwrec' to create a
382 * textual field. The function should then write that textual
383 * value into the 'text_value' character buffer, writing no more
384 * than 'text_size' characters to it---including the final NUL.
385 * The value of 'text_size' will be at least one more than the
386 * 'column_width' that was specified when the field was registered.
387 *
388 * The 'reg_data' will be the value you specified when you
389 * registered the field. You do not have to use that value.
390 *
391 * You will most likely not use the 'extra' parameter.
392 */
393 static skplugin_err_t
recToText(const rwRec * rwrec,char * text_value,size_t text_size,void * reg_data,void ** extra)394 recToText(
395 const rwRec *rwrec,
396 char *text_value,
397 size_t text_size,
398 void *reg_data,
399 void **extra)
400 {
401 /* return one of the following */
402 return SKPLUGIN_OK;
403 return SKPLUGIN_ERR_FATAL;
404
405 #if 0
406 /* key example: print lower of sPort or dPort */
407 if (rwRecGetSPort(rwrec) < rwRecGetDPort(rwrec)) {
408 snprintf(text_value, text_size, rwRecGetSPort(rwrec));
409 } else {
410 snprintf(text_value, text_size, rwRecGetDPort(rwrec));
411 }
412 return SKPLUGIN_OK;
413 #endif /* 0 */
414 }
415
416
417 /*
418 * status = recToBin(rwrec, bin_value, reg_data, extra);
419 *
420 * A function similar to this is required when the plug-in will be
421 * used to create a key field for use by rwgroup, rwsort, rwstats,
422 * or rwuniq. (For rwstats and rwuniq, a binToText() function is
423 * also required.)
424 *
425 * The function should use the SiLK flow record 'rwrec' to create a
426 * binary value. The function should then write that binary value
427 * into the 'bin_value' buffer. Since this value will be used for
428 * sorting, the binary value should be written in network byte
429 * order (big endian).
430 *
431 * The function should write the same number of bytes as were
432 * specified in the 'bin_bytes' field when the field was
433 * registered.
434 *
435 * The 'reg_data' will be the value you specified when you
436 * registered the field. You do not have to use that value.
437 *
438 * You will most likely not use the 'extra' parameter.
439 */
440 static skplugin_err_t
recToBin(const rwRec * rwrec,uint8_t * bin_value,void * reg_data,void ** extra)441 recToBin(
442 const rwRec *rwrec,
443 uint8_t *bin_value,
444 void *reg_data,
445 void **extra)
446 {
447 /* return one of the following */
448 return SKPLUGIN_OK;
449 return SKPLUGIN_ERR_FATAL;
450
451 #if 0
452 /* key example: encode lower of sPort or dPort */
453 uint16_t port;
454
455 if (rwRecGetSPort(rwrec) < rwRecGetDPort(rwrec)) {
456 port = htons(rwRecGetSPort(rwrec));
457 } else {
458 port = htons(rwRecGetDPort(rwrec));
459 }
460 memcpy(bin_value, &port, sizeof(port));
461 return SKPLUGIN_OK;
462 #endif /* 0 */
463 }
464
465
466 /*
467 * status = binToText(bin_value, text_value, text_size, reg_data);
468 *
469 * A function similar to this is required when the plug-in will be
470 * used to create a key field or an aggregate value for use by
471 * rwstats or rwuniq.
472 *
473 * The function should use the 'bin_value' buffer to create a
474 * textual value. This function should write the textual value
475 * into the 'text_value' character buffer, writing no more than
476 * 'text_size' characters to it---including the final NUL. The
477 * value of 'text_size' will be at least one more than the
478 * 'column_width' that was specified when the field was registered.
479 *
480 * For key fields, the contents of 'bin_value' will be the result
481 * of a prior call to the recToBin() function.
482 *
483 * For aggregate value fields, the contents of 'bin_value' will be
484 * the result of calls to addRecToBin() and binMerge().
485 *
486 * The 'reg_data' will be the value you specified when you
487 * registered the field. You do not have to use that value.
488 */
489 static skplugin_err_t
binToText(const uint8_t * bin_value,char * text_value,size_t text_size,void * reg_data)490 binToText(
491 const uint8_t *bin_value,
492 char *text_value,
493 size_t text_size,
494 void *reg_data)
495 {
496 /* return one of the following */
497 return SKPLUGIN_OK;
498 return SKPLUGIN_ERR_FATAL;
499
500 #if 0
501 /* key example: print lower port encoded by recToBin */
502 uint16_t port;
503
504 memcpy(&port, bin_value, sizeof(port));
505 snprintf(text_value, text_size, ntohs(port));
506 return SKPLUGIN_OK;
507
508 /* value example: sum of durations */
509 uint32_t dur;
510
511 memcpy(&dur, bin_value, sizeof(dur));
512 snprintf(text_value, text_size, dur);
513 return SKPLUGIN_OK;
514 #endif /* 0 */
515 }
516
517
518 /*
519 * status = addRecToBin(rwrec, bin_value, reg_data, extra);
520 *
521 * A function similar to this is required when the plug-in will be
522 * used to create an aggregate value field for use by rwstats or or
523 * rwuniq. The binToText() and binMerge() functions are also
524 * required.
525 *
526 * The function should use the SiLK flow record 'rwrec' to create a
527 * binary value. The function should then ADD or MERGE that binary
528 * value into the value currently in the 'bin_value' buffer. For
529 * example, if your plug-in is counting bytes, it should add the
530 * bytes from the current 'rwRec' into the value in 'bin_value'.
531 *
532 * The length of 'bin_value' is determined by the 'bin_bytes' field
533 * when the field was registered.
534 *
535 * The 'reg_data' will be the value you specified when you
536 * registered the field. You do not have to use that value.
537 *
538 * You will most likely not use the 'extra' parameter.
539 */
540 static skplugin_err_t
addRecToBin(const rwRec * rwrec,uint8_t * bin_value,void * reg_data,void ** extra)541 addRecToBin(
542 const rwRec *rwrec,
543 uint8_t *bin_value,
544 void *reg_data,
545 void **extra)
546 {
547 /* return one of the following */
548 return SKPLUGIN_OK;
549 return SKPLUGIN_ERR_FATAL;
550
551 #if 0
552 /* value example: sum of duration for all flows matching key */
553 uint32_t dur;
554
555 memcpy(&dur, bin_value, sizeof(dur));
556 dur += rwRecGetElapsed(rwrec);
557 memcpy(bin_value, &dur, sizeof(dur));
558 return SKPLUGIN_OK;
559 #endif /* 0 */
560 }
561
562
563 /*
564 * status = binMerge(dst_bin_value, src_bin_value, reg_data);
565 *
566 * A function similar to this is required when the plug-in will be
567 * used to create an aggregate value field for use by rwstats or or
568 * rwuniq. The binToText() and addRecToBin() functions are also
569 * required.
570 *
571 * This function is used to combine two binary aggregate values,
572 * created by prior calls to addRecToBin(), into a single binary
573 * value. When called, both 'dst_bin_value' and 'src_bin_value'
574 * will have valid binary values. This function should combine the
575 * values (by adding or merging them as appropriate) and put the
576 * resulting value into 'dst_bin_value'.
577 *
578 * The length of 'bin_value' is determined by the 'bin_bytes' field
579 * when the field was registered.
580 *
581 * The 'reg_data' will be the value you specified when you
582 * registered the field. You do not have to use that value.
583 *
584 * When rwstats or rwuniq run out of RAM, they write their current
585 * (key,value) pair to temporary files disk; once all records have
586 * been processed, the (key,value) pairs in the temporary files
587 * must be merged. This function will be called to merge values
588 * for entries with identical keys.
589 */
590 static skplugin_err_t
binMerge(uint8_t * dst_bin_value,const uint8_t * src_bin_value,void * reg_data)591 binMerge(
592 uint8_t *dst_bin_value,
593 const uint8_t *src_bin_value,
594 void *reg_data)
595 {
596 /* return one of the following */
597 return SKPLUGIN_OK;
598 return SKPLUGIN_ERR_FATAL;
599
600 #if 0
601 /* value example: sum of duration for all flows matching key */
602 uint32_t dst_dur;
603 uint32_t src_dur;
604
605 memcpy(&dst_dur, dst_bin_value, sizeof(dst_dur));
606 memcpy(&src_dur, src_bin_value, sizeof(src_dur));
607 dst_dur += src_dur;
608 memcpy(dst_bin_value, &dst_dur, sizeof(dst_dur));
609 return SKPLUGIN_OK;
610 #endif /* 0 */
611 }
612
613
614 /*
615 * status = binCompare(cmp_result, bin_value_a, bin_value_b, reg_data);
616 *
617 * A function similar to this is required when the plug-in will be
618 * used to create an aggregate value field for use by rwstats. The
619 * binToText(), addRecToBin(), and binMerge() functions are also
620 * required.
621 *
622 * This function is used to compare two binary aggregate values,
623 * created by prior calls to addRecToBin(). This is required to
624 * sort the value fields in rwstats to produce a Top-N list. The
625 * function should set 'cmp_result' to a value less than 0, equal
626 * to 0, or greater than 0 when 'bin_value_a' is less then, equal
627 * to, or greater than 'bin_value_b', respectively.
628 *
629 * The length of 'bin_value' is determined by the 'bin_bytes' field
630 * when the field was registered.
631 *
632 * The 'reg_data' will be the value you specified when you
633 * registered the field. You do not have to use that value.
634 */
635 static skplugin_err_t
binCompare(int * cmp_result,const uint8_t * bin_value_a,const uint8_t * bin_value_b,void * reg_data)636 binCompare(
637 int *cmp_result,
638 const uint8_t *bin_value_a,
639 const uint8_t *bin_value_b,
640 void *reg_data)
641 {
642 /* return one of the following */
643 return SKPLUGIN_OK;
644 return SKPLUGIN_ERR_FATAL;
645
646 #if 0
647 /* value example: sum of duration for all flows matching key */
648 uint32_t dur_a;
649 uint32_t dur_b;
650
651 memcpy(&dur_a, bin_value_a, sizeof(dur_a));
652 memcpy(&dur_b, bin_value_b, sizeof(dur_b));
653 if (dur_a < dur_b) {
654 *cmp_result = -1;
655 } else if (dur_a > dur_b) {
656 *cmp_result = 1;
657 } else {
658 *cmp_result = 0;
659 }
660 return SKPLUGIN_OK;
661 #endif /* 0 */
662 }
663
664
665 /*
666 * status = filter(rwrec, reg_data, extra);
667 *
668 * A function similar to this is required when the plug-in will be
669 * used to partition fields into PASS and FAIL streams in rwfilter.
670 *
671 * The function should examine the SiLK flow record and return
672 * SKPLUGIN_FILTER_PASS to write the rwRec to the
673 * pass-destination(s) or SKPLUGIN_FILTER_FAIL to write it to the
674 * fail-destination(s).
675 *
676 * The 'reg_data' will be the value you specified when you
677 * registered the field. You do not have to use that value.
678 *
679 * You will most likely not use the 'extra' parameter.
680 */
681 static skplugin_err_t
filter(const rwRec * rwrec,void * reg_data,void ** extra)682 filter(
683 const rwRec *rwrec,
684 void *reg_data,
685 void **extra)
686 {
687 /* return one of the following */
688 return SKPLUGIN_FILTER_FAIL;
689 return SKPLUGIN_FILTER_PASS;
690
691 #if 0
692 /* example: pass ICMP or ICMPv6 flows */
693 if (IPPROTO_ICMP == rwRecGetProto(rwrec)
694 || IPPROTO_ICMPV6 == rwRecGetProto(rwrec))
695 {
696 return SKPLUGIN_FILTER_PASS;
697 }
698 return SKPLUGIN_FILTER_FAIL;
699 #endif /* 0 */
700 }
701
702
703 /*
704 * status = transform(rwrec, reg_data, extra);
705 *
706 * A function similar to this is required when the plug-in will be
707 * used to modify the SiLK Flow records.
708 *
709 * The function can modify the SiLK flow record in place. One use
710 * for this is to modify records during their creation by the
711 * rwptoflow application.
712 *
713 * The 'reg_data' will be the value you specified when you
714 * registered the field. You do not have to use that value.
715 *
716 * You will most likely not use the 'extra' parameter.
717 */
718 static skplugin_err_t
transform(rwRec * rwrec,void * reg_data,void ** extra)719 transform(
720 rwRec *rwrec,
721 void *reg_data,
722 void **extra)
723 {
724 /* return one of the following */
725 return SKPLUGIN_OK;
726 return SKPLUGIN_ERR_FATAL;
727 }
728
729
730 /*
731 ** Local Variables:
732 ** mode:c
733 ** indent-tabs-mode:nil
734 ** c-basic-offset:4
735 ** End:
736 */
737