1 /*
2 * $Id: com-misc.c,v 1.9.2.1 2003/05/07 11:15:05 mt Exp $
3 *
4 * Common miscellaneous functions
5 *
6 * Author(s): Jens-Gero Boehm <jens-gero.boehm@suse.de>
7 * Pieter Hollants <pieter.hollants@suse.de>
8 * Marius Tomaschewski <mt@suse.de>
9 * Volker Wiegand <volker.wiegand@suse.de>
10 *
11 * This file is part of the SuSE Proxy Suite
12 * See also http://proxy-suite.suse.de/
13 *
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version
17 * 2 of the License, or (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the
26 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 * Boston, MA 02111-1307, USA.
28 *
29 * A history log can be found at the end of this file.
30 */
31
32 #ifndef lint
33 static char rcsid[] = "$Id: com-misc.c,v 1.9.2.1 2003/05/07 11:15:05 mt Exp $";
34 #endif
35
36 #include <config.h>
37
38 #if defined(STDC_HEADERS)
39 # include <stdio.h>
40 # include <string.h>
41 # include <stdlib.h>
42 # include <stdarg.h>
43 # include <errno.h>
44 #endif
45
46 #include <sys/types.h>
47 #if defined(HAVE_UNISTD_H)
48 # include <unistd.h>
49 #endif
50
51 #if TIME_WITH_SYS_TIME
52 # include <sys/time.h>
53 # include <time.h>
54 #else
55 # if HAVE_SYS_TIME_H
56 # include <sys/time.h>
57 # else
58 # include <time.h>
59 # endif
60 #endif
61
62 #if defined(HAVE_FCNTL_H)
63 # include <fcntl.h>
64 #elif defined(HAVE_SYS_FCNTL_H)
65 # include <sys/fcntl.h>
66 #endif
67
68 #include "com-config.h"
69 #include "com-debug.h"
70 #include "com-misc.h"
71 #include "com-syslog.h"
72
73
74 /* ------------------------------------------------------------ */
75
76 static void misc_cleanup(void);
77
78
79 /* ------------------------------------------------------------ */
80
81 static int initflag = 0; /* Have we been initialized? */
82
83 static char p_name[512] = "[unknown name]";
84 static char p_vers[512] = "[unknown version]";
85 static char p_date[512] = "[unknown date]";
86
87 static char **use_ptr = NULL; /* Usage information array */
88 static char *pid_name = NULL; /* Name of ProcID file */
89
90
91 /* ------------------------------------------------------------ **
92 **
93 ** Function......: misc_cleanup
94 **
95 ** Parameters....: (none)
96 **
97 ** Return........: (none)
98 **
99 ** Purpose.......: Clean up at program exit.
100 **
101 ** ------------------------------------------------------------ */
102
misc_cleanup(void)103 static void misc_cleanup(void)
104 {
105 if (pid_name != NULL) {
106 void *tmp = (void *) pid_name;
107 unlink(pid_name);
108 pid_name = NULL;
109 misc_free(FL, tmp);
110 }
111 }
112
113
114 /* ------------------------------------------------------------ **
115 **
116 ** Function......: misc_forget
117 **
118 ** Parameters....: (none)
119 **
120 ** Return........: (none)
121 **
122 ** Purpose.......: Forget cleanup's (for forked children).
123 **
124 ** ------------------------------------------------------------ */
125
misc_forget(void)126 void misc_forget(void)
127 {
128 if (pid_name != NULL) {
129 void *tmp = (void *) pid_name;
130 pid_name = NULL;
131 misc_free(FL, tmp);
132 }
133 }
134
135
136 /* ------------------------------------------------------------ **
137 **
138 ** Function......: misc_setprog / misc_getprog
139 **
140 ** Parameters....: prog_str Program name
141 ** usage_arr Usage info string array
142 **
143 ** Return........: Program basename
144 **
145 ** Purpose.......: Makes the prog-name known to logging,
146 ** provides a short name without path.
147 **
148 ** ------------------------------------------------------------ */
149
misc_setprog(char * prog_str,char * usage_arr[])150 char *misc_setprog(char *prog_str, char *usage_arr[])
151 {
152 char *p;
153
154 if (prog_str == NULL)
155 p = "[unknown name]";
156 else if ((p = strrchr(prog_str, '/')) != NULL)
157 p++;
158 else
159 p = prog_str;
160 misc_strncpy(p_name, p, sizeof(p_name));
161
162 if (usage_arr != NULL)
163 use_ptr = usage_arr;
164
165 return p_name;
166 }
167
168
misc_getprog(void)169 char *misc_getprog(void)
170 {
171 return p_name;
172 }
173
174
175 /* ------------------------------------------------------------ **
176 **
177 ** Function......: misc_setvers / misc_getvers
178 ** misc_setdate / misc_getdate
179 ** misc_getvsdt
180 **
181 ** Parameters....: version Program version
182 **
183 ** Return........: Program version
184 **
185 ** Purpose.......: Sets and retrieves the program version
186 ** and program compilation date and time.
187 ** And a "standard version + date" thing.
188 **
189 ** ------------------------------------------------------------ */
190
misc_setvers(char * vers_str)191 void misc_setvers(char *vers_str)
192 {
193 if (vers_str == NULL)
194 vers_str = "[unknown version]";
195 misc_strncpy(p_vers, vers_str, sizeof(p_vers));
196 }
197
198
misc_getvers(void)199 char *misc_getvers(void)
200 {
201 return p_vers;
202 }
203
204
misc_setdate(char * date_str)205 void misc_setdate(char *date_str)
206 {
207 if (date_str == NULL)
208 date_str = "[unknown date]";
209 misc_strncpy(p_date, date_str, sizeof(p_date));
210 }
211
212
misc_getdate(void)213 char *misc_getdate(void)
214 {
215 return p_date;
216 }
217
218
misc_getvsdt(void)219 char *misc_getvsdt(void)
220 {
221 static char str[MAX_PATH_SIZE * 2];
222
223 #if defined(HAVE_SNPRINTF)
224 snprintf(str, sizeof(str), "Version %s - %s", p_vers, p_date);
225 #else
226 sprintf(str, "Version %s - %s", p_vers, p_date);
227 #endif
228 return str;
229 }
230
231
232 /* ------------------------------------------------------------ **
233 **
234 ** Function......: misc_alloc
235 **
236 ** Parameters....: file Filename of requestor
237 ** line Line number of requestor
238 ** len Number of bytes requested
239 **
240 ** Return........: Pointer to memory
241 **
242 ** Purpose.......: Allocate memory with malloc. The program
243 ** dies if no memory is available.
244 ** The memory is automatically zero'ed.
245 **
246 ** ------------------------------------------------------------ */
247
misc_alloc(char * file,int line,size_t len)248 void *misc_alloc(char *file, int line, size_t len)
249 {
250 void *ptr;
251
252 if (file == NULL) /* Sanity check */
253 file = "[unknown file]";
254
255 if (len == 0) /* Another check ... */
256 misc_die(file, line, "misc_alloc: ?len?");
257
258 if ((ptr = malloc(len)) == NULL)
259 misc_die(file, line, "out of memory");
260
261 #if defined(COMPILE_DEBUG)
262 debug(4, "alloc %u (%.*s:%d): %p",
263 (unsigned) len, MAX_PATH_SIZE, file, line, ptr);
264 #endif
265
266 memset(ptr, 0, len);
267 return ptr;
268 }
269
270
271 /* ------------------------------------------------------------ **
272 **
273 ** Function......: misc_strdup
274 **
275 ** Parameters....: file Filename of requestor
276 ** line Line number of requestor
277 ** str Pointer to original string
278 **
279 ** Return........: Pointer to allocated string
280 **
281 ** Purpose.......: Allocate memory for a copy of the given
282 ** string with misc_alloc and copy the
283 ** string in place.
284 **
285 ** ------------------------------------------------------------ */
286
misc_strdup(char * file,int line,char * str)287 char *misc_strdup(char *file, int line, char *str)
288 {
289 char *ptr;
290 int len;
291
292 /* Basic sanity check */
293 if (str == NULL)
294 misc_die(file, line, "misc_strdup: ?str?");
295
296 len = strlen(str);
297 ptr = (char *) misc_alloc(file, line, len + 1);
298 strncpy(ptr, str, len);
299
300 return ptr;
301 }
302
303
304 /* ------------------------------------------------------------ **
305 **
306 ** Function......: misc_free
307 **
308 ** Parameters....: file Filename of requestor
309 ** line Line number of requestor
310 ** ptr Memory area to be freed
311 **
312 ** Return........: (none)
313 **
314 ** Purpose.......: Free memory allocated with misc_alloc.
315 **
316 ** ------------------------------------------------------------ */
317
misc_free(char * file,int line,void * ptr)318 void misc_free(char *file, int line, void *ptr)
319 {
320 if (file == NULL) /* Sanity check */
321 file = "[unknown file]";
322
323 #if defined(COMPILE_DEBUG)
324 debug(4, "free %p (%.*s:%d)", ptr, MAX_PATH_SIZE, file, line);
325 #else
326 line = line; /* Calm down picky compilers... */
327 #endif
328
329 if (ptr != NULL)
330 free(ptr);
331 }
332
333
334 /* ------------------------------------------------------------ **
335 **
336 ** Function......: misc_usage
337 **
338 ** Parameters....: fmt Printf-string with usage
339 **
340 ** Return........: (none)
341 **
342 ** Purpose.......: Print a usage info and terminate.
343 **
344 ** ------------------------------------------------------------ */
345
misc_usage(char * fmt,...)346 void misc_usage(char *fmt, ...)
347 {
348 va_list aptr;
349 int i;
350
351 if (use_ptr != NULL) {
352 for (i = 0; use_ptr[i] != NULL; i++)
353 fprintf(stderr, "%s\n", use_ptr[i]);
354 }
355
356 if (fmt != NULL && *fmt != '\0') {
357 fprintf(stderr, "%s Error: ", p_name);
358 va_start(aptr, fmt);
359 vfprintf(stderr, fmt, aptr);
360 va_end(aptr);
361 fprintf(stderr, "\n\n");
362 }
363
364 exit(EXIT_FAILURE);
365 }
366
367
368 /* ------------------------------------------------------------ **
369 **
370 ** Function......: misc_die
371 **
372 ** Parameters....: fmt Printf-string with message
373 **
374 ** Return........: (none)
375 **
376 ** Purpose.......: Print an error message and terminate.
377 **
378 ** ------------------------------------------------------------ */
379
misc_die(char * file,int line,char * fmt,...)380 void misc_die(char *file, int line, char *fmt, ...)
381 {
382 int tmperr = errno; /* Save errno for later */
383 char str[MAX_PATH_SIZE * 4];
384 va_list aptr;
385 size_t len;
386
387 if (file == NULL) /* Sanity check */
388 file = "[unknown file]";
389
390 memset(str, 0, sizeof(str));
391 #if defined(HAVE_SNPRINTF)
392 snprintf(str, sizeof(str), "%s (%.*s:%d): ",
393 p_name, MAX_PATH_SIZE, file, line);
394 #else
395 sprintf(str, "%s (%.*s:%d): ",
396 p_name, MAX_PATH_SIZE, file, line);
397 #endif
398 len = strlen(str);
399
400 if (fmt != NULL && *fmt != '\0') {
401 va_start(aptr, fmt);
402 #if defined(HAVE_VSNPRINTF)
403 vsnprintf(str + len, sizeof(str)-len, fmt, aptr);
404 #else
405 vsprintf(str + len, fmt, aptr);
406 #endif
407 va_end(aptr);
408 len = strlen(str);
409 }
410 if (tmperr) {
411 #if defined(HAVE_SNPRINTF)
412 snprintf(str + len, sizeof(str)-len,
413 " (errno=%d [%.256s])",
414 tmperr, strerror(tmperr));
415 #else
416 sprintf(str + len, " (errno=%d [%.256s])",
417 tmperr, strerror(tmperr));
418 #endif
419 }
420
421 fprintf(stderr, "%s\n", str);
422 syslog_write(T_FTL, "%s", str);
423
424 errno = tmperr; /* Restore errno */
425 exit(EXIT_FAILURE);
426 }
427
428
429 /* ------------------------------------------------------------ **
430 **
431 ** Function......: misc_pidfile
432 **
433 ** Parameters....: name Desired PID-file name
434 **
435 ** Return........: (none)
436 **
437 ** Purpose.......: Create a file with the Process-ID.
438 **
439 ** ------------------------------------------------------------ */
440
misc_pidfile(char * name)441 void misc_pidfile(char *name)
442 {
443 FILE *fp;
444 int fd;
445
446 if (initflag == 0) {
447 atexit(misc_cleanup);
448 initflag = 1;
449 }
450
451 /*
452 ** Do some housekeeping (maybe it's just a close)
453 */
454 if (misc_strequ(name, pid_name))
455 return;
456 if (pid_name != NULL) {
457 void *tmp = (void *) pid_name;
458 unlink(pid_name);
459 pid_name = NULL;
460 misc_free(FL, tmp);
461 }
462
463 /*
464 ** Do we have a real filename now?
465 */
466 if (name != NULL) {
467 if (unlink(name) < 0 && errno != ENOENT) {
468 syslog_error("can't remove pidfile '%.*s'",
469 MAX_PATH_SIZE, name);
470 exit(EXIT_FAILURE);
471 }
472 if ((fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0644)) < 0)
473 {
474 syslog_error("can't open pidfile '%.*s'",
475 MAX_PATH_SIZE, name);
476 exit(EXIT_FAILURE);
477 }
478 if ((fp = fdopen(fd, "w")) == NULL) {
479 syslog_error("can't open pidfile '%.*s'",
480 MAX_PATH_SIZE, name);
481 exit(EXIT_FAILURE);
482 }
483 fprintf(fp, "%d\n", (int) getpid());
484 fclose(fp);
485 pid_name = misc_strdup(FL, name);
486 }
487
488 #if defined(COMPILE_DEBUG)
489 debug(2, "pid-file: '%s'", NIL(pid_name));
490 #endif
491 }
492
493
494 /* ------------------------------------------------------------ **
495 **
496 ** Function......: misc_strtrim
497 **
498 ** Parameters....: s String to be trimmed
499 **
500 ** Return........: String without leading or trailing space
501 **
502 ** Purpose.......: Trims white space at the beginning and end
503 ** of a given string (this is done in-place).
504 **
505 ** ------------------------------------------------------------ */
506
misc_strtrim(char * s)507 char *misc_strtrim(char *s)
508 {
509 char *p;
510
511 if (s == NULL)
512 return NULL;
513 while (*s == ' ' || *s == '\t')
514 s++;
515
516 p = s + strlen(s);
517 while (p > s && (p[-1] == ' ' || p[-1] == '\t' ||
518 p[-1] == '\n' || p[-1] == '\r'))
519 *--p = '\0';
520
521 return s;
522 }
523
524
525 /* ------------------------------------------------------------ **
526 **
527 ** Function......: misc_strequ / misc_strcaseequ
528 **
529 ** Parameters....: s1 First string to compare
530 ** s2 Second string to compare
531 **
532 ** Return........: 1=strings are equal, 0=strings differ
533 **
534 ** Purpose.......: Check if two strings are equal. The
535 ** strings could well be NULL pointers.
536 ** And strcasecmp ignores upper/lower case.
537 **
538 ** ------------------------------------------------------------ */
539
misc_strequ(const char * s1,const char * s2)540 int misc_strequ(const char *s1, const char *s2)
541 {
542 if (s1 == NULL && s2 == NULL)
543 return 1;
544 if (s1 == NULL && s2 != NULL)
545 return 0;
546 if (s1 != NULL && s2 == NULL)
547 return 0;
548 return (strcmp(s1, s2) == 0);
549 }
550
551
misc_strcaseequ(const char * s1,const char * s2)552 int misc_strcaseequ(const char *s1, const char *s2)
553 {
554 if (s1 == NULL && s2 == NULL)
555 return 1;
556 if (s1 == NULL && s2 != NULL)
557 return 0;
558 if (s1 != NULL && s2 == NULL)
559 return 0;
560 return (strcasecmp(s1, s2) == 0);
561 }
562
563
564 /* ------------------------------------------------------------ **
565 **
566 ** Function......: misc_strnequ / misc_strncaseequ
567 **
568 ** Parameters....: s1 First string to compare
569 ** s2 Second string to compare
570 ** n number of characters in
571 ** in s1 to compare
572 **
573 ** Return........: 1=strings are equal, 0=strings differ
574 **
575 ** Purpose.......: Check if two strings are equal. The
576 ** strings could well be NULL pointers.
577 ** strncasecmp ignores upper/lower case.
578 **
579 ** ------------------------------------------------------------ */
580
misc_strnequ(const char * s1,const char * s2,size_t n)581 int misc_strnequ(const char *s1, const char *s2, size_t n)
582 {
583 if (s1 == NULL && s2 == NULL)
584 return 1;
585 if (s1 == NULL && s2 != NULL)
586 return 0;
587 if (s1 != NULL && s2 == NULL)
588 return 0;
589 return (strncmp(s1, s2, n) == 0);
590 }
591
misc_strncaseequ(const char * s1,const char * s2,size_t n)592 int misc_strncaseequ(const char *s1, const char *s2, size_t n)
593 {
594 if (s1 == NULL && s2 == NULL)
595 return 1;
596 if (s1 == NULL && s2 != NULL)
597 return 0;
598 if (s1 != NULL && s2 == NULL)
599 return 0;
600 return (strncasecmp(s1, s2, n) == 0);
601 }
602
603
604 /* ------------------------------------------------------------ **
605 **
606 ** Function......: misc_strncpy
607 **
608 ** Parameters....: s1 Destination pointer
609 ** s2 Source pointer
610 ** len Size of Destination buffer
611 **
612 ** Return........: Destination pointer
613 **
614 ** Purpose.......: Copies at most (len - 1) bytes from source
615 ** to destination and fills the residual space
616 ** of the destination buffer with null bytes.
617 **
618 ** ------------------------------------------------------------ */
619
misc_strncpy(char * s1,const char * s2,size_t len)620 char *misc_strncpy(char *s1, const char *s2, size_t len)
621 {
622 size_t cnt;
623
624 /*
625 ** Prepare the destination buffer
626 */
627 if (s1 == NULL)
628 return NULL;
629 memset(s1, 0, len);
630
631 /*
632 ** Check the source and get its size
633 */
634 if (s2 == NULL || (cnt = strlen(s2)) == 0)
635 return s1;
636
637 /*
638 ** Copy at most (len - 1) bytes
639 */
640 if (cnt >= len)
641 cnt = len - 1;
642 memcpy(s1, s2, cnt);
643
644 /*
645 ** Done -- return destination pointer
646 */
647 return s1;
648 }
649
650
651 /* ------------------------------------------------------------ **
652 **
653 ** Function......: misc_chroot
654 **
655 ** Parameters....: dir chroot directory
656 **
657 ** Return........: 0 on success, -1 if dir argument
658 ** was emtpy; exits program on error
659 **
660 ** Purpose.......: change root into specified directory
661 **
662 ** ------------------------------------------------------------ */
663
misc_chroot(char * dir)664 int misc_chroot (char *dir)
665 {
666 if(dir && *dir) {
667 chdir("/");
668 if (chroot(dir)) {
669 syslog_error("can't chroot to '%.1024s'", dir);
670 exit(EXIT_FAILURE);
671 }
672 chdir("/");
673 return 0;
674 }
675 return -1;
676 }
677
678
679 /* ------------------------------------------------------------ **
680 **
681 ** Function......: misc_uidgid
682 **
683 ** Parameters....: uid UID (-1 -> use config_uid)
684 ** gid GID (-1 -> use config_gid)
685 **
686 ** Return........: (none), exits program on error
687 **
688 ** Purpose.......: Set the UID and GID for the current process.
689 ** If the parameters are -1, use the config
690 ** file's "User" and "Group" variables.
691 **
692 ** ------------------------------------------------------------ */
693
misc_uidgid(uid_t uid,gid_t gid)694 void misc_uidgid(uid_t uid, gid_t gid)
695 {
696 #if defined(COMPILE_DEBUG)
697 debug(2, "uid-gid desired: uid=%d gid=%d",
698 (int) uid, (int) gid);
699 #endif
700
701 if (gid == CONFIG_GID) {
702 if(config_str(NULL, "Group", NULL)) {
703 /*
704 ** if config defines a group, use it
705 ** or complain (not found in system)
706 */
707 gid = config_gid(NULL, "Group", CONFIG_GID);
708 } else {
709 gid = getgid();
710 }
711 }
712 if (gid == CONFIG_GID) {
713 syslog_error("can't determine Group-ID to use");
714 exit(EXIT_FAILURE);
715 }
716 if (setgid(gid) < 0) {
717 syslog_error("can't set Group-ID to %d", (int) gid);
718 exit(EXIT_FAILURE);
719 }
720 if (getegid() != gid) {
721 syslog_error("can't set Group-ID to %d", (int) gid);
722 exit(EXIT_FAILURE);
723 }
724
725 if (uid == CONFIG_UID) {
726 if(config_str(NULL, "User", NULL)) {
727 /*
728 ** if config defines a user, use it
729 ** or complain (not found in system)
730 */
731 uid = config_uid(NULL, "User", CONFIG_UID);
732 } else {
733 uid = getuid();
734 }
735 }
736 if (uid == CONFIG_UID) {
737 syslog_error("can't determine User-ID to use");
738 exit(EXIT_FAILURE);
739 }
740 if (setuid(uid) < 0) {
741 syslog_error("can't set User-ID to %d", (int) uid);
742 exit(EXIT_FAILURE);
743 }
744 if (geteuid() != uid) {
745 syslog_error("can't set User-ID to %d", (int) uid);
746 exit(EXIT_FAILURE);
747 }
748
749 #if defined(COMPILE_DEBUG)
750 debug(2, "uid-gid adopted: uid=%d gid=%d",
751 (int) getuid(), (int) getgid());
752 #endif
753 }
754
755
756 /* ------------------------------------------------------------ **
757 **
758 ** Function......: misc_rand
759 **
760 ** Parameters....: lower range mark
761 ** upper range mark
762 **
763 ** Return........: random number between lower and upper mark
764 **
765 ** Purpose.......: generates a random number in specified range.
766 **
767 ** ------------------------------------------------------------ */
768
misc_rand(int lrng,int urng)769 int misc_rand (int lrng, int urng)
770 {
771 struct timeval t;
772
773 if (lrng == urng) return lrng;
774 if (lrng > urng) {
775 /* swap values */
776 lrng ^= urng;
777 urng ^= lrng;
778 lrng ^= urng;
779 }
780
781 gettimeofday (&t, NULL);
782 srand (t.tv_usec);
783
784 return (lrng + (rand () % (urng - lrng + 1)));
785 }
786
787 /* ------------------------------------------------------------
788 * $Log: com-misc.c,v $
789 * Revision 1.9.2.1 2003/05/07 11:15:05 mt
790 * misc_strdup: changed to allow empty strings
791 * misc_rand: removed sequence in lrng/urng swapping
792 *
793 * Revision 1.9 2002/05/02 12:59:00 mt
794 * merged with v1.8.2.2
795 *
796 * Revision 1.8.2.1 2002/01/28 01:53:07 mt
797 * implemented misc_strnequ misc_strncaseequ wrappers
798 *
799 * Revision 1.8 2002/01/14 18:18:50 mt
800 * implemented misc_chroot wrapper function
801 * added checks in misc_uidgid if User/Group are set in config
802 * added snprintf usage if supported, replaced all strcpy with strncpy
803 *
804 * Revision 1.7 2001/11/06 23:04:43 mt
805 * applied / merged with transparent proxy patches v8
806 * see ftp-proxy/NEWS for more detailed release news
807 *
808 * Revision 1.6 1999/09/30 09:49:45 wiegand
809 * updated string trim function to trim also newlines
810 *
811 * Revision 1.5 1999/09/26 13:25:05 wiegand
812 * protection of debug/pid/log files against attacks
813 *
814 * Revision 1.4 1999/09/21 05:42:28 wiegand
815 * syslog / abort review
816 *
817 * Revision 1.3 1999/09/17 06:32:28 wiegand
818 * buffer length and overflow protection review
819 *
820 * Revision 1.2 1999/09/16 14:26:33 wiegand
821 * minor code review and cleanup
822 *
823 * Revision 1.1 1999/09/15 14:05:38 wiegand
824 * initial checkin
825 *
826 * ------------------------------------------------------------ */
827
828