/[cvs]/eggdrop1.8/src/chanprog.c
ViewVC logotype

Contents of /eggdrop1.8/src/chanprog.c

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


Revision 1.7.2.1 - (show annotations) (download) (as text)
Wed Nov 10 13:39:19 2010 UTC (9 years ago) by pseudo
Branch: gettext
Changes since 1.7: +60 -63 lines
File MIME type: text/x-chdr
Converted remaining lang #defines in the core to english strings from core.english.lang. Gettextified most of the hardcoded strings.

1 /*
2 * chanprog.c -- handles:
3 * rmspace()
4 * maintaining the server list
5 * revenge punishment
6 * timers, utimers
7 * telling the current programmed settings
8 * initializing a lot of stuff and loading the tcl scripts
9 *
10 * $Id: chanprog.c,v 1.7 2010/11/04 17:54:04 thommey Exp $
11 */
12 /*
13 * Copyright (C) 1997 Robey Pointer
14 * Copyright (C) 1999 - 2010 Eggheads Development Team
15 *
16 * This program is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU General Public License
18 * as published by the Free Software Foundation; either version 2
19 * of the License, or (at your option) any later version.
20 *
21 * This program is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 * GNU General Public License for more details.
25 *
26 * You should have received a copy of the GNU General Public License
27 * along with this program; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 */
30
31 #include "main.h"
32
33 #ifdef HAVE_GETRUSAGE
34 # include <sys/resource.h>
35 # ifdef HAVE_SYS_RUSAGE_H
36 # include <sys/rusage.h>
37 # endif
38 #endif
39
40 #ifdef HAVE_UNAME
41 # include <sys/utsname.h>
42 #endif
43
44 #include "modules.h"
45
46 extern struct userrec *userlist;
47 extern log_t *logs;
48 extern Tcl_Interp *interp;
49 extern char ver[], botnetnick[], firewall[], motdfile[], userfile[], helpdir[],
50 tempdir[], moddir[], notify_new[], owner[], configfile[];
51 extern time_t now, online_since;
52 extern int backgrd, term_z, con_chan, cache_hit, cache_miss, firewallport,
53 default_flags, max_logs, conmask, protect_readonly, make_userfile,
54 noshare, ignore_time, max_socks;
55 #ifdef TLS
56 extern SSL_CTX *ssl_ctx;
57 #endif
58
59 tcl_timer_t *timer = NULL; /* Minutely timer */
60 tcl_timer_t *utimer = NULL; /* Secondly timer */
61 unsigned long timer_id = 1; /* Next timer of any sort will
62 * have this number */
63 struct chanset_t *chanset = NULL; /* Channel list */
64 char admin[121] = ""; /* Admin info */
65 char origbotname[NICKLEN + 1];
66 char botname[NICKLEN + 1]; /* Primary botname */
67
68
69 /* Remove leading and trailing whitespaces.
70 */
71 void rmspace(char *s)
72 {
73 register char *p = NULL, *q = NULL;
74
75 if (!s || !*s)
76 return;
77
78 /* Remove trailing whitespaces. */
79 for (q = s + strlen(s) - 1; q >= s && egg_isspace(*q); q--);
80 *(q + 1) = 0;
81
82 /* Remove leading whitespaces. */
83 for (p = s; egg_isspace(*p); p++);
84
85 if (p != s)
86 memmove(s, p, q - p + 2);
87 }
88
89
90 /* Returns memberfields if the nick is in the member list.
91 */
92 memberlist *ismember(struct chanset_t *chan, char *nick)
93 {
94 register memberlist *x;
95
96 for (x = chan->channel.member; x && x->nick[0]; x = x->next)
97 if (!rfc_casecmp(x->nick, nick))
98 return x;
99 return NULL;
100 }
101
102 /* Find a chanset by channel name as the server knows it (ie !ABCDEchannel)
103 */
104 struct chanset_t *findchan(const char *name)
105 {
106 register struct chanset_t *chan;
107
108 for (chan = chanset; chan; chan = chan->next)
109 if (!rfc_casecmp(chan->name, name))
110 return chan;
111 return NULL;
112 }
113
114 /* Find a chanset by display name (ie !channel)
115 */
116 struct chanset_t *findchan_by_dname(const char *name)
117 {
118 register struct chanset_t *chan;
119
120 for (chan = chanset; chan; chan = chan->next)
121 if (!rfc_casecmp(chan->dname, name))
122 return chan;
123 return NULL;
124 }
125
126
127 /*
128 * "caching" functions
129 */
130
131 /* Shortcut for get_user_by_host -- might have user record in one
132 * of the channel caches.
133 */
134 struct userrec *check_chanlist(const char *host)
135 {
136 char *nick, *uhost, buf[UHOSTLEN];
137 register memberlist *m;
138 register struct chanset_t *chan;
139
140 strncpyz(buf, host, sizeof buf);
141 uhost = buf;
142 nick = splitnick(&uhost);
143 for (chan = chanset; chan; chan = chan->next)
144 for (m = chan->channel.member; m && m->nick[0]; m = m->next)
145 if (!rfc_casecmp(nick, m->nick) && !egg_strcasecmp(uhost, m->userhost))
146 return m->user;
147 return NULL;
148 }
149
150 /* Shortcut for get_user_by_handle -- might have user record in channels
151 */
152 struct userrec *check_chanlist_hand(const char *hand)
153 {
154 register struct chanset_t *chan;
155 register memberlist *m;
156
157 for (chan = chanset; chan; chan = chan->next)
158 for (m = chan->channel.member; m && m->nick[0]; m = m->next)
159 if (m->user && !egg_strcasecmp(m->user->handle, hand))
160 return m->user;
161 return NULL;
162 }
163
164 /* Clear the user pointers in the chanlists.
165 *
166 * Necessary when a hostmask is added/removed, a user is added or a new
167 * userfile is loaded.
168 */
169 void clear_chanlist(void)
170 {
171 register memberlist *m;
172 register struct chanset_t *chan;
173
174 for (chan = chanset; chan; chan = chan->next)
175 for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
176 m->user = NULL;
177 m->tried_getuser = 0;
178 }
179 }
180
181 /* Clear the user pointer of a specific nick in the chanlists.
182 *
183 * Necessary when a hostmask is added/removed, a nick changes, etc.
184 * Does not completely invalidate the channel cache like clear_chanlist().
185 */
186 void clear_chanlist_member(const char *nick)
187 {
188 register memberlist *m;
189 register struct chanset_t *chan;
190
191 for (chan = chanset; chan; chan = chan->next)
192 for (m = chan->channel.member; m && m->nick[0]; m = m->next)
193 if (!rfc_casecmp(m->nick, nick)) {
194 m->user = NULL;
195 m->tried_getuser = 0;
196 break;
197 }
198 }
199
200 /* If this user@host is in a channel, set it (it was null)
201 */
202 void set_chanlist(const char *host, struct userrec *rec)
203 {
204 char *nick, *uhost, buf[UHOSTLEN];
205 register memberlist *m;
206 register struct chanset_t *chan;
207
208 strncpyz(buf, host, sizeof buf);
209 uhost = buf;
210 nick = splitnick(&uhost);
211 for (chan = chanset; chan; chan = chan->next)
212 for (m = chan->channel.member; m && m->nick[0]; m = m->next)
213 if (!rfc_casecmp(nick, m->nick) && !egg_strcasecmp(uhost, m->userhost))
214 m->user = rec;
215 }
216
217 /* Calculate the memory we should be using
218 */
219 int expmem_chanprog()
220 {
221 register int tot = 0;
222 register tcl_timer_t *t;
223
224 for (t = timer; t; t = t->next)
225 tot += sizeof(tcl_timer_t) + strlen(t->cmd) + 1;
226 for (t = utimer; t; t = t->next)
227 tot += sizeof(tcl_timer_t) + strlen(t->cmd) + 1;
228 return tot;
229 }
230
231 float getcputime()
232 {
233 #ifdef HAVE_GETRUSAGE
234 float stime, utime;
235 struct rusage ru;
236
237 getrusage(RUSAGE_SELF, &ru);
238 utime = ru.ru_utime.tv_sec + (ru.ru_utime.tv_usec / 1000000.00);
239 stime = ru.ru_stime.tv_sec + (ru.ru_stime.tv_usec / 1000000.00);
240 return (utime + stime);
241 #else
242 # ifdef HAVE_CLOCK
243 return (clock() / (CLOCKS_PER_SEC * 1.00));
244 # else
245 return -1.00;
246 # endif
247 #endif
248 }
249
250 /* Dump uptime info out to dcc (guppy 9Jan99)
251 */
252 void tell_verbose_uptime(int idx)
253 {
254 char s[256], s1[121];
255 time_t now2, hr, min;
256
257 now2 = now - online_since;
258 s[0] = 0;
259 if (now2 > 86400) {
260 /* days */
261 sprintf(s, "%d day", (int) (now2 / 86400));
262 if ((int) (now2 / 86400) >= 2)
263 strcat(s, "s");
264 strcat(s, ", ");
265 now2 -= (((int) (now2 / 86400)) * 86400);
266 }
267 hr = (time_t) ((int) now2 / 3600);
268 now2 -= (hr * 3600);
269 min = (time_t) ((int) now2 / 60);
270 sprintf(&s[strlen(s)], "%02d:%02d", (int) hr, (int) min);
271 s1[0] = 0;
272 if (backgrd)
273 strcpy(s1, _("background mode"));
274 else {
275 if (term_z)
276 strcpy(s1, _("terminal mode"));
277 else if (con_chan)
278 strcpy(s1, _("status mode"));
279 else
280 strcpy(s1, _("log dump mode"));
281 }
282 dprintf(idx, _("Online for %s (%s)\n"), s, s1);
283 }
284
285 /* Dump status info out to dcc
286 */
287 void tell_verbose_status(int idx)
288 {
289 char s[256], s1[121], s2[81];
290 char *vers_t, *uni_t;
291 int i;
292 time_t now2 = now - online_since, hr, min;
293 float cputime;
294 #ifdef HAVE_UNAME
295 struct utsname un;
296
297 if (uname(&un) < 0) {
298 #endif
299 vers_t = " ";
300 uni_t = _("*Unknown*");
301 #ifdef HAVE_UNAME
302 } else {
303 vers_t = un.release;
304 uni_t = un.sysname;
305 }
306 #endif
307
308 i = count_users(userlist);
309 dprintf(idx, P_("I am %s, running %s: %d user (mem: %uk).\n",
310 "I am %s, running %s: %d users (mem: %uk).\n", i),
311 botnetnick, ver, i, (int) (expected_memory() / 1024));
312
313 s[0] = 0;
314 if (now2 > 86400) {
315 /* days */
316 sprintf(s, "%d day", (int) (now2 / 86400));
317 if ((int) (now2 / 86400) >= 2)
318 strcat(s, "s");
319 strcat(s, ", ");
320 now2 -= (((int) (now2 / 86400)) * 86400);
321 }
322 hr = (time_t) ((int) now2 / 3600);
323 now2 -= (hr * 3600);
324 min = (time_t) ((int) now2 / 60);
325 sprintf(&s[strlen(s)], "%02d:%02d", (int) hr, (int) min);
326 s1[0] = 0;
327 if (backgrd)
328 strcpy(s1, _("background"));
329 else {
330 if (term_z)
331 strcpy(s1, _("terminal mode"));
332 else if (con_chan)
333 strcpy(s1, _("status mode"));
334 else
335 strcpy(s1, _("log dump mode"));
336 }
337 cputime = getcputime();
338 if (cputime < 0)
339 sprintf(s2, _("CPU: unknown"));
340 else {
341 hr = cputime / 60;
342 cputime -= hr * 60;
343 sprintf(s2, _("CPU: %02d:%05.2f"), (int) hr, cputime); /* min/sec */
344 }
345 dprintf(idx, _("Online for %s (%s) - %s - Cache hit: %4.1f%%\n"), s, s1, s2,
346 100.0 * ((float) cache_hit) / ((float) (cache_hit + cache_miss)));
347
348 dprintf(idx, _("Configured with: %s\n"), EGG_AC_ARGS);
349 if (admin[0])
350 dprintf(idx, _("Admin: %s\n"), admin);
351
352 dprintf(idx, _("Config file: %s\n"), configfile);
353 dprintf(idx, _("OS: %s %s\n"), uni_t, vers_t);
354 dprintf(idx, _("Process ID: %d (parent %d)\n"), getpid(), getppid());
355
356 /* info library */
357 dprintf(idx, _("Tcl library: %s\n"),
358 ((interp) && (Tcl_Eval(interp, "info library") == TCL_OK)) ?
359 tcl_resultstring() : _("*unknown*"));
360
361 /* info tclversion/patchlevel */
362 dprintf(idx, _("Tcl version: %s (%s %s)\n"),
363 ((interp) && (Tcl_Eval(interp, "info patchlevel") == TCL_OK)) ?
364 tcl_resultstring() : (Tcl_Eval(interp, "info tclversion") == TCL_OK) ?
365 tcl_resultstring() : _("*unknown*"), _("header version"),
366 TCL_PATCH_LEVEL ? TCL_PATCH_LEVEL : _("*unknown*"));
367
368 if (tcl_threaded())
369 dprintf(idx, _("Tcl is threaded.\n"));
370 #ifdef TLS
371 dprintf(idx, _("TLS support is enabled.\n"));
372 dprintf(idx, _("TLS library: %s\n"), SSLeay_version(SSLEAY_VERSION));
373 #else
374 dprintf(idx, _("TLS support is not available.\n"));
375 #endif
376 #ifdef IPV6
377 dprintf(idx, _("IPv6 support is enabled.\n"));
378 #else
379 dprintf(idx, _("IPv6 support is not available.\n"));
380 #endif
381 dprintf(idx, _("Socket table: %d/%d\n"), threaddata()->MAXSOCKS, max_socks);
382 }
383
384 /* Show all internal state variables
385 */
386 void tell_settings(int idx)
387 {
388 char s[1024];
389 int i;
390 struct flag_record fr = { FR_GLOBAL, 0, 0, 0, 0, 0 };
391
392 dprintf(idx, _("Botnet nickname: %s\n"), botnetnick);
393 if (firewall[0])
394 dprintf(idx, _("Firewall: %s:%d\n"), firewall, firewallport);
395 dprintf(idx, _("Userfile: %s\n"), userfile);
396 dprintf(idx, _("Motd: %s\n"), motdfile);
397 dprintf(idx, _("Directories:\n"));
398 dprintf(idx, _(" Help : %s\n"), helpdir);
399 dprintf(idx, _(" Temp : %s\n"), tempdir);
400 #ifdef STATIC
401 dprintf(idx, _(" Modules: %s\n"), moddir);
402 #endif
403 fr.global = default_flags;
404
405 build_flags(s, &fr, NULL);
406 dprintf(idx, _("New users get flags [%s], notify: %s\n"), s, notify_new);
407 if (owner[0])
408 dprintf(idx, _("Permanent owner(s): %s\n"), owner);
409 for (i = 0; i < max_logs; i++)
410 if (logs[i].filename != NULL) {
411 dprintf(idx, _("Logfile #%d: %s on %s (%s: %s)\n"), i + 1,
412 logs[i].filename, logs[i].chname,
413 masktype(logs[i].mask), maskname(logs[i].mask));
414 }
415 dprintf(idx, _("Ignores last %d minute%s.\n"), ignore_time,
416 (ignore_time != 1) ? "s" : "");
417 }
418
419 void reaffirm_owners()
420 {
421 char *p, *q, s[121];
422 struct userrec *u;
423
424 /* Please stop breaking this function. */
425 if (owner[0]) {
426 q = owner;
427 p = strchr(q, ',');
428 while (p) {
429 strncpyz(s, q, (p - q) + 1);
430 rmspace(s);
431 u = get_user_by_handle(userlist, s);
432 if (u)
433 u->flags = sanity_check(u->flags | USER_OWNER);
434 q = p + 1;
435 p = strchr(q, ',');
436 }
437 strcpy(s, q);
438 rmspace(s);
439 u = get_user_by_handle(userlist, s);
440 if (u)
441 u->flags = sanity_check(u->flags | USER_OWNER);
442 }
443 }
444
445 void chanprog()
446 {
447 int i;
448 FILE *f;
449 char s[161], rands[8];
450
451 admin[0] = 0;
452 helpdir[0] = 0;
453 tempdir[0] = 0;
454 conmask = 0;
455
456 for (i = 0; i < max_logs; i++)
457 logs[i].flags |= LF_EXPIRING;
458
459 /* Turn off read-only variables (make them write-able) for rehash */
460 protect_readonly = 0;
461
462 /* Now read it */
463 if (!readtclprog(configfile))
464 fatal(_("CONFIG FILE NOT LOADED (NOT FOUND, OR ERROR)"), 0);
465
466 for (i = 0; i < max_logs; i++) {
467 if (logs[i].flags & LF_EXPIRING) {
468 if (logs[i].filename != NULL) {
469 nfree(logs[i].filename);
470 logs[i].filename = NULL;
471 }
472 if (logs[i].chname != NULL) {
473 nfree(logs[i].chname);
474 logs[i].chname = NULL;
475 }
476 if (logs[i].f != NULL) {
477 fclose(logs[i].f);
478 logs[i].f = NULL;
479 }
480 logs[i].mask = 0;
481 logs[i].flags = 0;
482 }
483 }
484
485 /* We should be safe now */
486 call_hook(HOOK_REHASH);
487 protect_readonly = 1;
488
489 if (!botnetnick[0])
490 strncpyz(botnetnick, origbotname, HANDLEN + 1);
491
492 if (!botnetnick[0])
493 fatal(_("I don't have a botnet nick!!\n"), 0);
494
495 if (!userfile[0])
496 fatal(_("USER FILE NOT FOUND! (try './eggdrop -m %s' to make one)\n"), 0);
497
498 if (!readuserfile(userfile, &userlist)) {
499 if (!make_userfile) {
500 char tmp[178];
501
502 egg_snprintf(tmp, sizeof tmp, _("USER FILE NOT FOUND! "
503 "(try './eggdrop -m %s' to make one)\n"), configfile);
504 fatal(tmp, 0);
505 }
506 printf(_("STARTING BOT IN USERFILE CREATION MODE.\n"
507 "Telnet to the bot and enter 'NEW' as your nickname."));
508 if (module_find("server", 0, 0))
509 printf(_("OR go to IRC and type: /msg %s hello\n"), origbotname);
510 printf("This will make the bot recognize you as the master.\n");
511 } else if (make_userfile) {
512 make_userfile = 0;
513 printf("%s\n", _("USERFILE ALREADY EXISTS (drop the '-m')"));
514 }
515
516 if (helpdir[0])
517 if (helpdir[strlen(helpdir) - 1] != '/')
518 strcat(helpdir, "/");
519
520 if (tempdir[0])
521 if (tempdir[strlen(tempdir) - 1] != '/')
522 strcat(tempdir, "/");
523
524 /* Test tempdir: it's vital. */
525
526 /* Possible file race condition solved by using a random string
527 * and the process id in the filename.
528 * FIXME: This race is only partitially fixed. We could still be
529 * overwriting an existing file / following a malicious
530 * link.
531 */
532 make_rand_str(rands, 7); /* create random string */
533 sprintf(s, "%s.test-%u-%s", tempdir, getpid(), rands);
534 f = fopen(s, "w");
535 if (f == NULL)
536 fatal(_("CAN'T WRITE TO TEMP DIR"), 0);
537 fclose(f);
538 unlink(s);
539 reaffirm_owners();
540 check_tcl_event("userfile-loaded");
541 }
542
543 /* Reload the user file from disk
544 */
545 void reload()
546 {
547 if (!file_readable(userfile)) {
548 putlog(LOG_MISC, "*", _("Can't reload user file!"));
549 return;
550 }
551
552 noshare = 1;
553 clear_userlist(userlist);
554 noshare = 0;
555 userlist = NULL;
556 if (!readuserfile(userfile, &userlist))
557 fatal(_("User file is missing!"), 0);
558 reaffirm_owners();
559 check_tcl_event("userfile-loaded");
560 call_hook(HOOK_READ_USERFILE);
561 }
562
563 void rehash()
564 {
565 call_hook(HOOK_PRE_REHASH);
566 noshare = 1;
567 clear_userlist(userlist);
568 noshare = 0;
569 userlist = NULL;
570 chanprog();
571 }
572
573 /*
574 * Brief venture into timers
575 */
576
577 /* Add a timer
578 */
579 unsigned long add_timer(tcl_timer_t ** stack, int elapse, int count,
580 char *cmd, unsigned long prev_id)
581 {
582 tcl_timer_t *old = (*stack);
583
584 *stack = nmalloc(sizeof **stack);
585 (*stack)->next = old;
586 (*stack)->mins = (*stack)->interval = elapse;
587 (*stack)->count = count;
588 (*stack)->cmd = nmalloc(strlen(cmd) + 1);
589 strcpy((*stack)->cmd, cmd);
590 /* If it's just being added back and already had an id,
591 * don't create a new one.
592 */
593 if (prev_id > 0)
594 (*stack)->id = prev_id;
595 else
596 (*stack)->id = timer_id++;
597 return (*stack)->id;
598 }
599
600 /* Remove a timer, by id
601 */
602 int remove_timer(tcl_timer_t ** stack, unsigned long id)
603 {
604 tcl_timer_t *old;
605 int ok = 0;
606
607 while (*stack) {
608 if ((*stack)->id == id) {
609 ok++;
610 old = *stack;
611 *stack = ((*stack)->next);
612 nfree(old->cmd);
613 nfree(old);
614 } else
615 stack = &((*stack)->next);
616 }
617 return ok;
618 }
619
620 /* Check timers, execute the ones that have expired.
621 */
622 void do_check_timers(tcl_timer_t ** stack)
623 {
624 tcl_timer_t *mark = *stack, *old = NULL;
625 char x[16];
626
627 /* New timers could be added by a Tcl script inside a current timer
628 * so i'll just clear out the timer list completely, and add any
629 * unexpired timers back on.
630 */
631 *stack = NULL;
632 while (mark) {
633 if (mark->mins > 0)
634 mark->mins--;
635 old = mark;
636 mark = mark->next;
637 if (!old->mins) {
638 egg_snprintf(x, sizeof x, "timer%lu", old->id);
639 do_tcl(x, old->cmd);
640 if (old->count == 1) {
641 nfree(old->cmd);
642 nfree(old);
643 continue;
644 } else {
645 old->mins = old->interval;
646 if (old->count > 1)
647 old->count--;
648 }
649 }
650 old->next = *stack;
651 *stack = old;
652 }
653 }
654
655 /* Wipe all timers.
656 */
657 void wipe_timers(Tcl_Interp *irp, tcl_timer_t **stack)
658 {
659 tcl_timer_t *mark = *stack, *old;
660
661 while (mark) {
662 old = mark;
663 mark = mark->next;
664 nfree(old->cmd);
665 nfree(old);
666 }
667 *stack = NULL;
668 }
669
670 /* Return list of timers
671 */
672 void list_timers(Tcl_Interp *irp, tcl_timer_t *stack)
673 {
674 char mins[10], count[10], id[16], *x;
675 EGG_CONST char *argv[4];
676 tcl_timer_t *mark;
677
678 for (mark = stack; mark; mark = mark->next) {
679 egg_snprintf(mins, sizeof mins, "%u", mark->mins);
680 egg_snprintf(id, sizeof id, "timer%lu", mark->id);
681 egg_snprintf(count, sizeof count, "%u", mark->count);
682 argv[0] = mins;
683 argv[1] = mark->cmd;
684 argv[2] = id;
685 argv[3] = count;
686 x = Tcl_Merge(sizeof(argv)/sizeof(*argv), argv);
687 Tcl_AppendElement(irp, x);
688 Tcl_Free((char *) x);
689 }
690 }
691
692 /* Oddly enough, written by Sup (former(?) Eggdrop coder)
693 */
694 int isowner(char *name)
695 {
696 register char *ptr = NULL, *s = NULL, *n = NULL;
697
698 if (!name)
699 return 0;
700
701 ptr = owner - 1;
702
703 do {
704 ptr++;
705 if (*ptr && !egg_isspace(*ptr) && *ptr != ',') {
706 if (!s)
707 s = ptr;
708 } else if (s) {
709 for (n = name; *n && *s && s < ptr &&
710 tolower((unsigned) *n) == tolower((unsigned) *s); n++, s++);
711
712 if (s == ptr && !*n)
713 return 1;
714
715 s = NULL;
716 }
717 } while (*ptr);
718
719 return 0;
720 }

webmaster@eggheads.org
ViewVC Help
Powered by ViewVC 1.1.23