/[cvs]/eggdrop1.9/src/userrec.c
ViewVC logotype

Contents of /eggdrop1.9/src/userrec.c

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.3 - (show annotations) (download) (as text)
Thu Oct 21 19:23:01 1999 UTC (20 years, 3 months ago) by fabian
Branch: MAIN
Changes since 1.2: +1 -1 lines
File MIME type: text/x-chdr
resync with 1.3 tree, 939863113-940380771

1 /*
2 * userrec.c -- handles:
3 * add_q() del_q() str2flags() flags2str() str2chflags() chflags2str()
4 * a bunch of functions to find and change user records
5 * change and check user (and channel-specific) flags
6 *
7 * dprintf'ized, 10nov1995
8 */
9 /*
10 * This file is part of the eggdrop source code
11 * copyright (c) 1997 Robey Pointer
12 * and is distributed according to the GNU general public license.
13 * For full details, read the top of 'main.c' or the file called
14 * COPYING that was distributed with this code.
15 */
16
17 #include "main.h"
18 #include <sys/stat.h>
19 #include "users.h"
20 #include "chan.h"
21 #include "modules.h"
22 #include "tandem.h"
23
24 extern struct dcc_t *dcc;
25 extern int dcc_total;
26 extern char userfile[];
27 extern int share_greet;
28 extern struct chanset_t *chanset;
29 extern char ver[];
30 extern char botnetnick[];
31 extern time_t now;
32 extern int default_flags, default_uflags;
33 extern int quiet_save;
34
35 int noshare = 1; /* don't send out to sharebots */
36 int sort_users = 0; /* sort the userlist when saving */
37 struct userrec *userlist = NULL; /* user records are stored here */
38 struct userrec *lastuser = NULL; /* last accessed user record */
39 maskrec *global_bans = NULL,
40 *global_exempts = NULL,
41 *global_invites = NULL;
42 struct igrec *global_ign = NULL;
43 int cache_hit = 0, cache_miss = 0; /* temporary cache accounting */
44
45 #ifdef EBUG_MEM
46 void *_user_malloc(int size, char *file, int line)
47 {
48 char x[1024];
49
50 simple_sprintf(x, "userrec.c:%s", file);
51 return n_malloc(size, x, line);
52 }
53 void *_user_realloc(void *ptr, int size, char *file, int line)
54 {
55 char x[1024];
56
57 simple_sprintf(x, "userrec.c:%s", file);
58 return n_realloc(ptr, size, x, line);
59 }
60 #else
61 void *_user_malloc(int size, char *file, int line)
62 {
63 return nmalloc(size);
64 }
65
66 void *_user_realloc(void *ptr, int size, char *file, int line)
67 {
68 return nrealloc(ptr, size);
69 }
70 #endif
71
72 inline int expmem_mask(struct maskrec *m)
73 {
74 int result = 0;
75
76 while (m) {
77 result += sizeof(struct maskrec);
78 result += strlen(m->mask) + 1;
79 if (m->user)
80 result += strlen(m->user) + 1;
81 if (m->desc)
82 result += strlen(m->desc) + 1;
83
84 m = m->next;
85 }
86
87 return result;
88 }
89
90 /* memory we should be using */
91 int expmem_users()
92 {
93 int tot;
94 struct userrec *u;
95 struct chanuserrec *ch;
96 struct chanset_t *chan;
97 struct user_entry *ue;
98 struct igrec *i;
99
100 context;
101 tot = 0;
102 u = userlist;
103 while (u != NULL) {
104 ch = u->chanrec;
105 while (ch) {
106 tot += sizeof(struct chanuserrec);
107
108 if (ch->info != NULL)
109 tot += strlen(ch->info) + 1;
110 ch = ch->next;
111 }
112 tot += sizeof(struct userrec);
113
114 for (ue = u->entries; ue; ue = ue->next) {
115 tot += sizeof(struct user_entry);
116
117 if (ue->name) {
118 tot += strlen(ue->name) + 1;
119 tot += list_type_expmem(ue->u.list);
120 } else {
121 tot += ue->type->expmem(ue);
122 }
123 }
124 u = u->next;
125 }
126 /* account for each channel's masks */
127 for (chan = chanset; chan; chan = chan->next) {
128
129 /* account for each channel's ban-list user */
130 tot += expmem_mask(chan->bans);
131
132 /* account for each channel's exempt-list user */
133 tot += expmem_mask(chan->exempts);
134
135 /* account for each channel's invite-list user */
136 tot += expmem_mask(chan->invites);
137 }
138
139 tot += expmem_mask(global_bans);
140 tot += expmem_mask(global_exempts);
141 tot += expmem_mask(global_invites);
142
143 for (i = global_ign; i; i = i->next) {
144 tot += sizeof(struct igrec);
145
146 tot += strlen(i->igmask) + 1;
147 if (i->user)
148 tot += strlen(i->user) + 1;
149 if (i->msg)
150 tot += strlen(i->msg) + 1;
151 }
152 return tot;
153 }
154
155 int count_users(struct userrec *bu)
156 {
157 int tot = 0;
158 struct userrec *u = bu;
159
160 while (u != NULL) {
161 tot++;
162 u = u->next;
163 }
164 return tot;
165 }
166
167 struct userrec *check_dcclist_hand(char *handle)
168 {
169 int i;
170
171 for (i = 0; i < dcc_total; i++)
172 if (!strcasecmp(dcc[i].nick, handle))
173 return dcc[i].user;
174 return NULL;
175 }
176
177 struct userrec *get_user_by_handle(struct userrec *bu, char *handle)
178 {
179 struct userrec *u = bu, *ret;
180
181 if (!handle)
182 return NULL;
183 rmspace(handle);
184 if (!handle[0] || (handle[0] == '*'))
185 return NULL;
186 if (bu == userlist) {
187 if (lastuser && !strcasecmp(lastuser->handle, handle)) {
188 cache_hit++;
189 return lastuser;
190 }
191 ret = check_dcclist_hand(handle);
192 if (ret) {
193 cache_hit++;
194 return ret;
195 }
196 ret = check_chanlist_hand(handle);
197 if (ret) {
198 cache_hit++;
199 return ret;
200 }
201 cache_miss++;
202 }
203 while (u) {
204 if (!strcasecmp(u->handle, handle)) {
205 if (bu == userlist)
206 lastuser = u;
207 return u;
208 }
209 u = u->next;
210 }
211 return NULL;
212 }
213
214 /* fix capitalization, etc */
215 void correct_handle(char *handle)
216 {
217 struct userrec *u;
218
219 u = get_user_by_handle(userlist, handle);
220 if (u == NULL)
221 return;
222 strcpy(handle, u->handle);
223 }
224
225 /* This will be usefull in a lot of places, much more code re-use so we
226 * endup with a smaller executable bot. <cybah>
227 */
228 void clear_masks(maskrec *m)
229 {
230 maskrec *temp = NULL;
231
232 while (m) {
233 temp = m->next;
234
235 if (m->mask)
236 nfree(m->mask);
237 if (m->user)
238 nfree(m->user);
239 if (m->desc)
240 nfree(m->desc);
241
242 nfree(m);
243 m = temp;
244 }
245 }
246
247 void clear_userlist(struct userrec *bu)
248 {
249 struct userrec *u = bu, *v;
250 int i;
251
252 context;
253 while (u != NULL) {
254 v = u->next;
255 freeuser(u);
256 u = v;
257 }
258 if (userlist == bu) {
259 struct chanset_t *cst;
260
261 for (i = 0; i < dcc_total; i++)
262 dcc[i].user = NULL;
263 clear_chanlist();
264 lastuser = NULL;
265
266 while (global_ign)
267 delignore(global_ign->igmask);
268
269 clear_masks(global_bans);
270 clear_masks(global_exempts);
271 clear_masks(global_invites);
272 global_exempts = global_invites = global_bans = NULL;
273
274 for (cst = chanset; cst; cst = cst->next) {
275 clear_masks(cst->bans);
276 clear_masks(cst->exempts);
277 clear_masks(cst->invites);
278
279 cst->bans = cst->exempts = cst->invites = NULL;
280 }
281 }
282 /* remember to set your userlist to NULL after calling this */
283 context;
284 }
285
286 /* find CLOSEST host match */
287 /* (if "*!*@*" and "*!*@*clemson.edu" both match, use the latter!) */
288 /* 26feb: CHECK THE CHANLIST FIRST to possibly avoid needless search */
289 struct userrec *get_user_by_host(char *host)
290 {
291 struct userrec *u = userlist, *ret;
292 struct list_type *q;
293 int cnt, i;
294
295 if (host == NULL)
296 return NULL;
297 rmspace(host);
298 if (!host[0])
299 return NULL;
300 ret = check_chanlist(host);
301 cnt = 0;
302 if (ret != NULL) {
303 cache_hit++;
304 return ret;
305 }
306 cache_miss++;
307 while (u != NULL) {
308 q = get_user(&USERENTRY_HOSTS, u);
309 while (q != NULL) {
310 i = wild_match(q->extra, host);
311 if (i > cnt) {
312 ret = u;
313 cnt = i;
314 }
315 q = q->next;
316 }
317 u = u->next;
318 }
319 if (ret != NULL) {
320 lastuser = ret;
321 set_chanlist(host, ret);
322 }
323 return ret;
324 }
325
326 struct userrec *get_user_by_equal_host(char *host)
327 {
328 struct userrec *u = userlist;
329 struct list_type *q;
330
331 while (u != NULL) {
332 q = get_user(&USERENTRY_HOSTS, u);
333 while (q != NULL) {
334 if (!rfc_casecmp(q->extra, host))
335 return u;
336 q = q->next;
337 }
338 u = u->next;
339 }
340 return NULL;
341 }
342
343 /* try: pass_match_by_host("-",host)
344 * will return 1 if no password is set for that host */
345 int u_pass_match(struct userrec *u, char *pass)
346 {
347 char *cmp, new[32];
348
349 if (!u)
350 return 0;
351 cmp = get_user(&USERENTRY_PASS, u);
352 if (!cmp)
353 return 1;
354 if (!pass || !pass[0] || (pass[0] == '-'))
355 return 0;
356 if (u->flags & USER_BOT) {
357 if (!strcmp(cmp, pass))
358 return 1;
359 } else {
360 if (strlen(pass) > 15)
361 pass[15] = 0;
362 encrypt_pass(pass, new);
363 if (!strcmp(cmp, new))
364 return 1;
365 }
366 return 0;
367 }
368
369 int write_user(struct userrec *u, FILE * f, int idx)
370 {
371 char s[181];
372 struct chanuserrec *ch;
373 struct chanset_t *cst;
374 struct user_entry *ue;
375 struct flag_record fr =
376 {FR_GLOBAL, 0, 0, 0, 0, 0};
377
378 fr.global = u->flags;
379
380 fr.udef_global = u->flags_udef;
381 build_flags(s, &fr, NULL);
382 if (fprintf(f, "%-10s - %-24s\n", u->handle, s) == EOF)
383 return 0;
384 for (ch = u->chanrec; ch; ch = ch->next) {
385 cst = findchan(ch->channel);
386 if (cst && ((idx < 0) || channel_shared(cst))) {
387 if (idx >= 0) {
388 fr.match = (FR_CHAN | FR_BOT);
389 get_user_flagrec(dcc[idx].user, &fr, ch->channel);
390 } else
391 fr.chan = BOT_SHARE;
392 if ((fr.chan & BOT_SHARE) || (fr.bot & BOT_GLOBAL)) {
393 fr.match = FR_CHAN;
394 fr.chan = ch->flags;
395 fr.udef_chan = ch->flags_udef;
396 build_flags(s, &fr, NULL);
397 if (fprintf(f, "! %-20s %lu %-10s %s\n", ch->channel, ch->laston, s,
398 (((idx < 0) || share_greet) && ch->info) ? ch->info
399 : "") == EOF)
400 return 0;
401 }
402 }
403 }
404 for (ue = u->entries; ue; ue = ue->next) {
405 if (ue->name) {
406 struct list_type *lt;
407
408 for (lt = ue->u.list; lt; lt = lt->next)
409 if (fprintf(f, "--%s %s\n", ue->name, lt->extra) == EOF)
410 return 0;
411 } else {
412 if (!ue->type->write_userfile(f, u, ue))
413 return 0;
414 }
415 }
416 return 1;
417 }
418
419 int sort_compare(struct userrec *a, struct userrec *b)
420 {
421 /* order by flags, then alphabetically
422 * first bots: +h / +a / +l / other bots
423 * then users: +n / +m / +o / other users
424 * return true if (a > b) */
425 if (a->flags & b->flags & USER_BOT) {
426 if (~bot_flags(a) & bot_flags(b) & BOT_HUB)
427 return 1;
428 if (bot_flags(a) & ~bot_flags(b) & BOT_HUB)
429 return 0;
430 if (~bot_flags(a) & bot_flags(b) & BOT_ALT)
431 return 1;
432 if (bot_flags(a) & ~bot_flags(b) & BOT_ALT)
433 return 0;
434 if (~bot_flags(a) & bot_flags(b) & BOT_LEAF)
435 return 1;
436 if (bot_flags(a) & ~bot_flags(b) & BOT_LEAF)
437 return 0;
438 } else {
439 if (~a->flags & b->flags & USER_BOT)
440 return 1;
441 if (a->flags & ~b->flags & USER_BOT)
442 return 0;
443 if (~a->flags & b->flags & USER_OWNER)
444 return 1;
445 if (a->flags & ~b->flags & USER_OWNER)
446 return 0;
447 if (~a->flags & b->flags & USER_MASTER)
448 return 1;
449 if (a->flags & ~b->flags & USER_MASTER)
450 return 0;
451 if (~a->flags & b->flags & USER_OP)
452 return 1;
453 if (a->flags & ~b->flags & USER_OP)
454 return 0;
455 }
456 return (strcasecmp(a->handle, b->handle) > 0);
457 }
458
459 void sort_userlist()
460 {
461 int again;
462 struct userrec *last, *p, *c, *n;
463
464 again = 1;
465 last = NULL;
466 while ((userlist != last) && (again)) {
467 p = NULL;
468 c = userlist;
469 n = c->next;
470 again = 0;
471 while (n != last) {
472 if (sort_compare(c, n)) {
473 again = 1;
474 c->next = n->next;
475 n->next = c;
476 if (p == NULL)
477 userlist = n;
478 else
479 p->next = n;
480 }
481 p = c;
482 c = n;
483 n = n->next;
484 }
485 last = c;
486 }
487 }
488
489 /* rewrite the entire user file */
490 void write_userfile(int idx)
491 {
492 FILE *f;
493 char s[121], s1[81];
494 time_t tt;
495 struct userrec *u;
496 int ok;
497
498 context;
499 /* also write the channel file at the same time */
500 if (userlist == NULL)
501 return; /* no point in saving userfile */
502 sprintf(s, "%s~new", userfile);
503 f = fopen(s, "w");
504 chmod(s, 0600); /* make it -rw------- */
505 if (f == NULL) {
506 putlog(LOG_MISC, "*", USERF_ERRWRITE);
507 return;
508 }
509 if (!quiet_save)
510 putlog(LOG_MISC, "*", USERF_WRITING);
511 if (sort_users)
512 sort_userlist();
513 tt = now;
514 strcpy(s1, ctime(&tt));
515 fprintf(f, "#4v: %s -- %s -- written %s", ver, botnetnick, s1);
516 context;
517 ok = 1;
518 u = userlist;
519 while ((u != NULL) && (ok)) {
520 ok = write_user(u, f, idx);
521 u = u->next;
522 }
523 context;
524 if (!ok || fflush(f)) {
525 putlog(LOG_MISC, "*", "%s (%s)", USERF_ERRWRITE, strerror(ferror(f)));
526 fclose(f);
527 return;
528 }
529 fclose(f);
530 context;
531 call_hook(HOOK_USERFILE);
532 context;
533 unlink(userfile);
534 sprintf(s, "%s~new", userfile);
535 movefile(s, userfile);
536 }
537
538 int change_handle(struct userrec *u, char *newh)
539 {
540 int i;
541 char s[16];
542
543 if (!u)
544 return 0;
545 /* nothing that will confuse the userfile */
546 if ((newh[1] == 0) && strchr(BADHANDCHARS, newh[0]))
547 return 0;
548 check_tcl_nkch(u->handle, newh);
549 /* yes, even send bot nick changes now: */
550 if ((!noshare) && !(u->flags & USER_UNSHARED))
551 shareout(NULL, "h %s %s\n", u->handle, newh);
552 strcpy(s, u->handle);
553 strcpy(u->handle, newh);
554 for (i = 0; i < dcc_total; i++) {
555 if (!strcasecmp(dcc[i].nick, s) &&
556 (dcc[i].type != &DCC_BOT)) {
557 strcpy(dcc[i].nick, newh);
558 if ((dcc[i].type == &DCC_CHAT) && (dcc[i].u.chat->channel >= 0)) {
559 chanout_but(-1, dcc[i].u.chat->channel,
560 "*** Nick change: %s -> %s\n", s, newh);
561 if (dcc[i].u.chat->channel < 100000)
562 botnet_send_nkch(i, s);
563 }
564 }
565 }
566 return 1;
567 }
568
569 extern int noxtra;
570
571 struct userrec *adduser(struct userrec *bu, char *handle, char *host,
572 char *pass, int flags)
573 {
574 struct userrec *u, *x;
575 struct xtra_key *xk;
576 int oldshare = noshare;
577
578 noshare = 1;
579 u = (struct userrec *) nmalloc(sizeof(struct userrec));
580
581 /* u->next=bu; bu=u; */
582 strncpy(u->handle, handle, HANDLEN);
583 u->handle[HANDLEN] = 0;
584 u->next = NULL;
585 u->chanrec = NULL;
586 u->entries = NULL;
587 if (flags != USER_DEFAULT) { /* drummer */
588 u->flags = flags;
589 u->flags_udef = 0;
590 } else {
591 u->flags = default_flags;
592 u->flags_udef = default_uflags;
593 }
594 set_user(&USERENTRY_PASS, u, pass);
595 if (!noxtra) {
596 xk = nmalloc(sizeof(struct xtra_key));
597
598 xk->key = nmalloc(8);
599 strcpy(xk->key, "created");
600 xk->data = nmalloc(10);
601 sprintf(xk->data, "%09lu", now);
602 set_user(&USERENTRY_XTRA, u, xk);
603 }
604 /* strip out commas -- they're illegal */
605 if (host && host[0]) {
606 char *p = strchr(host, ',');
607
608 while (p != NULL) {
609 *p = '?';
610 p = strchr(host, ',');
611 }
612 set_user(&USERENTRY_HOSTS, u, host);
613 } else
614 set_user(&USERENTRY_HOSTS, u, "none");
615 if (bu == userlist)
616 clear_chanlist();
617 noshare = oldshare;
618 if ((!noshare) && (handle[0] != '*') && (!(flags & USER_UNSHARED)) &&
619 (bu == userlist)) {
620 struct flag_record fr =
621 {FR_GLOBAL, u->flags, u->flags_udef, 0, 0, 0};
622 char x[100];
623
624 build_flags(x, &fr, 0);
625 shareout(NULL, "n %s %s %s %s\n", handle, host, pass, x);
626 }
627 if (bu == NULL)
628 bu = u;
629 else {
630 if ((bu == userlist) && (lastuser != NULL))
631 x = lastuser;
632 else
633 x = bu;
634 while (x->next != NULL)
635 x = x->next;
636 x->next = u;
637 if (bu == userlist)
638 lastuser = u;
639 }
640 return bu;
641 }
642
643 void freeuser(struct userrec *u)
644 {
645 struct user_entry *ue, *ut;
646 struct chanuserrec *ch, *z;
647
648 if (u == NULL)
649 return;
650 ch = u->chanrec;
651 while (ch) {
652 z = ch;
653 ch = ch->next;
654 if (z->info != NULL)
655 nfree(z->info);
656 nfree(z);
657 }
658 u->chanrec = NULL;
659 for (ue = u->entries; ue; ue = ut) {
660 ut = ue->next;
661 if (ue->name) {
662 struct list_type *lt, *ltt;
663
664 for (lt = ue->u.list; lt; lt = ltt) {
665 ltt = lt->next;
666 nfree(lt->extra);
667 nfree(lt);
668 }
669 nfree(ue->name);
670 nfree(ue);
671 } else {
672 ue->type->kill(ue);
673 }
674 }
675 nfree(u);
676 }
677
678 int deluser(char *handle)
679 {
680 struct userrec *u = userlist, *prev = NULL;
681 int fnd = 0;
682
683 while ((u != NULL) && (!fnd)) {
684 if (!strcasecmp(u->handle, handle))
685 fnd = 1;
686 else {
687 prev = u;
688 u = u->next;
689 }
690 }
691 if (!fnd)
692 return 0;
693 if (prev == NULL)
694 userlist = u->next;
695 else
696 prev->next = u->next;
697 if (!noshare && (handle[0] != '*') && !(u->flags & USER_UNSHARED))
698 shareout(NULL, "k %s\n", handle);
699 for (fnd = 0; fnd < dcc_total; fnd++)
700 if (dcc[fnd].user == u)
701 dcc[fnd].user = 0; /* clear any dcc users for this entry,
702 * null is safe-ish */
703 clear_chanlist();
704 freeuser(u);
705 lastuser = NULL;
706 return 1;
707 }
708
709 int delhost_by_handle(char *handle, char *host)
710 {
711 struct userrec *u;
712 struct list_type *q, *qnext, *qprev;
713 struct user_entry *e = NULL;
714 int i = 0;
715
716 context;
717 u = get_user_by_handle(userlist, handle);
718 if (!u)
719 return 0;
720 q = get_user(&USERENTRY_HOSTS, u);
721 qprev = q;
722 if (q) {
723 if (!rfc_casecmp(q->extra, host)) {
724 e = find_user_entry(&USERENTRY_HOSTS, u);
725 e->u.extra = q->next;
726 nfree(q->extra);
727 nfree(q);
728 i++;
729 qprev = NULL;
730 q = e->u.extra;
731 } else
732 q = q->next;
733 while (q) {
734 qnext = q->next;
735 if (!rfc_casecmp(q->extra, host)) {
736 if (qprev)
737 qprev->next = q->next;
738 else if (e) {
739 e->u.extra = q->next;
740 qprev = NULL;
741 }
742 nfree(q->extra);
743 nfree(q);
744 i++;
745 } else
746 qprev = q;
747 q = qnext;
748 }
749 }
750 if (!qprev)
751 set_user(&USERENTRY_HOSTS, u, "none");
752 if (!noshare && i && !(u->flags & USER_UNSHARED))
753 shareout(NULL, "-h %s %s\n", handle, host);
754 clear_chanlist();
755 return i;
756 }
757
758 void addhost_by_handle(char *handle, char *host)
759 {
760 struct userrec *u = get_user_by_handle(userlist, handle);
761
762 set_user(&USERENTRY_HOSTS, u, host);
763 /* u will be cached, so really no overhead, even tho this looks dumb: */
764 if ((!noshare) && !(u->flags & USER_UNSHARED)) {
765 if (u->flags & USER_BOT)
766 shareout(NULL, "+bh %s %s\n", handle, host);
767 else
768 shareout(NULL, "+h %s %s\n", handle, host);
769 }
770 clear_chanlist();
771 }
772
773 void touch_laston(struct userrec *u, char *where, time_t timeval)
774 {
775 if (!u)
776 return;
777 if (timeval > 1) {
778 struct laston_info *li =
779 (struct laston_info *) get_user(&USERENTRY_LASTON, u);
780
781 if (!li)
782 li = nmalloc(sizeof(struct laston_info));
783
784 else if (li->lastonplace)
785 nfree(li->lastonplace);
786 li->laston = timeval;
787 if (where) {
788 li->lastonplace = nmalloc(strlen(where) + 1);
789 strcpy(li->lastonplace, where);
790 } else
791 li->lastonplace = NULL;
792 set_user(&USERENTRY_LASTON, u, li);
793 } else if (timeval == 1) {
794 set_user(&USERENTRY_LASTON, u, 0);
795 }
796 }
797
798 /* Go through all channel records and try to find a matching
799 * nick. Will return the user's user record if that is known
800 * to the bot. (Fabian)
801 *
802 * Warning: This is unreliable by concept!
803 */
804 struct userrec *get_user_by_nick(char *nick)
805 {
806 struct chanset_t *chan = chanset;
807 memberlist *m;
808
809 context;
810 while (chan) {
811 m = chan->channel.member;
812 while (m->nick[0]) {
813 if (!rfc_casecmp(nick, m->nick)) {
814 char word[512];
815
816 sprintf(word, "%s!%s", m->nick, m->userhost);
817 /* no need to check the return value ourself */
818 return get_user_by_host(word);;
819 }
820 m = m->next;
821 }
822 chan = chan->next;
823 }
824 /* sorry, no matches */
825 return NULL;
826 }

webmaster@eggheads.org
ViewVC Help
Powered by ViewVC 1.1.23