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

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

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


Revision 1.2 - (show annotations) (download) (as text)
Tue Oct 19 12:13:33 2010 UTC (9 years, 7 months ago) by pseudo
Branch: MAIN
Changes since 1.1: +35 -1 lines
File MIME type: text/x-chdr
Added full SSL support including Tcl commands.
Added support for certificate authentication.
Added support for botnet and partyline encryption using ssl.
Documented the new features and commands.
Fixed add_server() problems with IPv6 addresses in the server list.

1 /*
2 * tclhash.c -- handles:
3 * bind and unbind
4 * checking and triggering the various in-bot bindings
5 * listing current bindings
6 * adding/removing new binding tables
7 * (non-Tcl) procedure lookups for msg/dcc/file commands
8 * (Tcl) binding internal procedures to msg/dcc/file commands
9 *
10 * $Id: tclhash.c,v 1.1.1.1 2010/07/26 21:11:06 simple 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 #include "chan.h"
33 #include "users.h"
34
35 extern Tcl_Interp *interp;
36 extern struct dcc_t *dcc;
37 extern struct userrec *userlist;
38 extern int dcc_total;
39 extern time_t now;
40
41 p_tcl_bind_list bind_table_list;
42 p_tcl_bind_list H_chat, H_act, H_bcst, H_chon, H_chof, H_load, H_unld, H_link,
43 H_disc, H_dcc, H_chjn, H_chpt, H_bot, H_time, H_nkch, H_away,
44 H_note, H_filt, H_event, H_cron, H_log = NULL;
45 #ifdef TLS
46 p_tcl_bind_list H_tls = NULL;
47 static int builtin_idx();
48 #endif
49
50 static int builtin_2char();
51 static int builtin_3char();
52 static int builtin_5int();
53 static int builtin_cron();
54 static int builtin_char();
55 static int builtin_chpt();
56 static int builtin_chjn();
57 static int builtin_idxchar();
58 static int builtin_charidx();
59 static int builtin_chat();
60 static int builtin_dcc();
61 static int builtin_log();
62
63
64 /* Allocate and initialise a chunk of memory.
65 */
66 static inline void *n_malloc_null(int size, const char *file, int line)
67 {
68 #ifdef DEBUG_MEM
69 # define nmalloc_null(size) n_malloc_null(size, __FILE__, __LINE__)
70 void *ptr = n_malloc(size, file, line);
71 #else
72 # define nmalloc_null(size) n_malloc_null(size, NULL, 0)
73 void *ptr = nmalloc(size);
74 #endif
75
76 egg_memset(ptr, 0, size);
77 return ptr;
78 }
79
80
81 /* Delete trigger/command.
82 */
83 static inline void tcl_cmd_delete(tcl_cmd_t *tc)
84 {
85 nfree(tc->func_name);
86 nfree(tc);
87 }
88
89 /* Delete bind and its elements.
90 */
91 static inline void tcl_bind_mask_delete(tcl_bind_mask_t *tm)
92 {
93 tcl_cmd_t *tc, *tc_next;
94
95 for (tc = tm->first; tc; tc = tc_next) {
96 tc_next = tc->next;
97 tcl_cmd_delete(tc);
98 }
99 nfree(tm->mask);
100 nfree(tm);
101 }
102
103 /* Delete bind list and its elements.
104 */
105 static inline void tcl_bind_list_delete(tcl_bind_list_t *tl)
106 {
107 tcl_bind_mask_t *tm, *tm_next;
108
109 for (tm = tl->first; tm; tm = tm_next) {
110 tm_next = tm->next;
111 tcl_bind_mask_delete(tm);
112 }
113 nfree(tl);
114 }
115
116 inline void garbage_collect_tclhash(void)
117 {
118 tcl_bind_list_t *tl, *tl_next, *tl_prev;
119 tcl_bind_mask_t *tm, *tm_next, *tm_prev;
120 tcl_cmd_t *tc, *tc_next, *tc_prev;
121
122 for (tl = bind_table_list, tl_prev = NULL; tl; tl = tl_next) {
123 tl_next = tl->next;
124
125 if (tl->flags & HT_DELETED) {
126 if (tl_prev)
127 tl_prev->next = tl->next;
128 else
129 bind_table_list = tl->next;
130 tcl_bind_list_delete(tl);
131 } else {
132 for (tm = tl->first, tm_prev = NULL; tm; tm = tm_next) {
133 tm_next = tm->next;
134
135 if (!(tm->flags & TBM_DELETED)) {
136 for (tc = tm->first, tc_prev = NULL; tc; tc = tc_next) {
137 tc_next = tc->next;
138
139 if (tc->attributes & TC_DELETED) {
140 if (tc_prev)
141 tc_prev->next = tc->next;
142 else
143 tm->first = tc->next;
144 tcl_cmd_delete(tc);
145 } else
146 tc_prev = tc;
147 }
148 }
149
150 /* Delete the bind when it's marked as deleted or when it's empty. */
151 if ((tm->flags & TBM_DELETED) || tm->first == NULL) {
152 if (tm_prev)
153 tm_prev->next = tm->next;
154 else
155 tl->first = tm_next;
156 tcl_bind_mask_delete(tm);
157 } else
158 tm_prev = tm;
159 }
160 tl_prev = tl;
161 }
162 }
163 }
164
165 static inline int tcl_cmd_expmem(tcl_cmd_t *tc)
166 {
167 int tot;
168
169 tot = sizeof(*tc);
170 if (tc->func_name)
171 tot += strlen(tc->func_name) + 1;
172 return tot;
173 }
174
175 static inline int tcl_bind_mask_expmem(tcl_bind_mask_t *tm)
176 {
177 int tot = 0;
178 tcl_cmd_t *tc;
179
180 for (tc = tm->first; tc; tc = tc->next)
181 tot += tcl_cmd_expmem(tc);
182 if (tm->mask)
183 tot += strlen(tm->mask) + 1;
184 tot += sizeof(*tm);
185 return tot;
186 }
187
188 static inline int tcl_bind_list_expmem(tcl_bind_list_t *tl)
189 {
190 int tot = 0;
191 tcl_bind_mask_t *tm;
192
193 for (tm = tl->first; tm; tm = tm->next)
194 tot += tcl_bind_mask_expmem(tm);
195 tot += sizeof(*tl);
196 return tot;
197 }
198
199 int expmem_tclhash(void)
200 {
201 int tot = 0;
202 tcl_bind_list_t *tl;
203
204 for (tl = bind_table_list; tl; tl = tl->next)
205 tot += tcl_bind_list_expmem(tl);
206 return tot;
207 }
208
209
210 extern cmd_t C_dcc[];
211 static int tcl_bind();
212
213 static cd_tcl_cmd cd_cmd_table[] = {
214 {"bind", tcl_bind, (void *) 0},
215 {"unbind", tcl_bind, (void *) 1},
216 {0}
217 };
218
219 void init_bind(void)
220 {
221 bind_table_list = NULL;
222 Context;
223 add_cd_tcl_cmds(cd_cmd_table);
224 H_unld = add_bind_table("unld", HT_STACKABLE, builtin_char);
225 H_time = add_bind_table("time", HT_STACKABLE, builtin_5int);
226 H_cron = add_bind_table("cron", HT_STACKABLE, builtin_cron);
227 H_note = add_bind_table("note", 0, builtin_3char);
228 H_nkch = add_bind_table("nkch", HT_STACKABLE, builtin_2char);
229 H_load = add_bind_table("load", HT_STACKABLE, builtin_char);
230 H_link = add_bind_table("link", HT_STACKABLE, builtin_2char);
231 H_filt = add_bind_table("filt", HT_STACKABLE, builtin_idxchar);
232 H_disc = add_bind_table("disc", HT_STACKABLE, builtin_char);
233 H_dcc = add_bind_table("dcc", 0, builtin_dcc);
234 H_chpt = add_bind_table("chpt", HT_STACKABLE, builtin_chpt);
235 H_chon = add_bind_table("chon", HT_STACKABLE, builtin_charidx);
236 H_chof = add_bind_table("chof", HT_STACKABLE, builtin_charidx);
237 H_chjn = add_bind_table("chjn", HT_STACKABLE, builtin_chjn);
238 H_chat = add_bind_table("chat", HT_STACKABLE, builtin_chat);
239 H_bot = add_bind_table("bot", 0, builtin_3char);
240 H_bcst = add_bind_table("bcst", HT_STACKABLE, builtin_chat);
241 H_away = add_bind_table("away", HT_STACKABLE, builtin_chat);
242 H_act = add_bind_table("act", HT_STACKABLE, builtin_chat);
243 H_event = add_bind_table("evnt", HT_STACKABLE, builtin_char);
244 H_log = add_bind_table("log", HT_STACKABLE, builtin_log);
245 #ifdef TLS
246 H_tls = add_bind_table("tls", HT_STACKABLE, builtin_idx);
247 #endif
248 add_builtins(H_dcc, C_dcc);
249 Context;
250 }
251
252 void kill_bind(void)
253 {
254 tcl_bind_list_t *tl, *tl_next;
255
256 rem_builtins(H_dcc, C_dcc);
257 for (tl = bind_table_list; tl; tl = tl_next) {
258 tl_next = tl->next;
259
260 if (!(tl->flags |= HT_DELETED))
261 putlog(LOG_DEBUG, "*", "De-Allocated bind table %s", tl->name);
262 tcl_bind_list_delete(tl);
263 }
264 H_log = NULL;
265 bind_table_list = NULL;
266 }
267
268 tcl_bind_list_t *add_bind_table(const char *nme, int flg, IntFunc func)
269 {
270 tcl_bind_list_t *tl, *tl_prev;
271 int v;
272
273 /* Do not allow coders to use bind table names longer than
274 * 4 characters. */
275 Assert(strlen(nme) <= 4);
276
277 for (tl = bind_table_list, tl_prev = NULL; tl; tl_prev = tl, tl = tl->next) {
278 if (tl->flags & HT_DELETED)
279 continue;
280 v = egg_strcasecmp(tl->name, nme);
281 if (!v)
282 return tl; /* Duplicate, just return old value. */
283 if (v > 0)
284 break; /* New. Insert at start of list. */
285 }
286
287 tl = nmalloc_null(sizeof *tl);
288 strcpy(tl->name, nme);
289 tl->flags = flg;
290 tl->func = func;
291
292 if (tl_prev) {
293 tl->next = tl_prev->next;
294 tl_prev->next = tl;
295 } else {
296 tl->next = bind_table_list;
297 bind_table_list = tl;
298 }
299
300 putlog(LOG_DEBUG, "*", "Allocated bind table %s (flags %d)", nme, flg);
301 return tl;
302 }
303
304 void del_bind_table(tcl_bind_list_t *tl_which)
305 {
306 tcl_bind_list_t *tl;
307
308 for (tl = bind_table_list; tl; tl = tl->next) {
309 if (tl->flags & HT_DELETED)
310 continue;
311 if (tl == tl_which) {
312 tl->flags |= HT_DELETED;
313 putlog(LOG_DEBUG, "*", "De-Allocated bind table %s", tl->name);
314 return;
315 }
316 }
317 putlog(LOG_DEBUG, "*", "??? Tried to delete not listed bind table ???");
318 }
319
320 tcl_bind_list_t *find_bind_table(const char *nme)
321 {
322 tcl_bind_list_t *tl;
323 int v;
324
325 for (tl = bind_table_list; tl; tl = tl->next) {
326 if (tl->flags & HT_DELETED)
327 continue;
328 v = egg_strcasecmp(tl->name, nme);
329 if (!v)
330 return tl;
331 if (v > 0)
332 return NULL;
333 }
334 return NULL;
335 }
336
337 static void dump_bind_tables(Tcl_Interp *irp)
338 {
339 tcl_bind_list_t *tl;
340 u_8bit_t i;
341
342 for (tl = bind_table_list, i = 0; tl; tl = tl->next) {
343 if (tl->flags & HT_DELETED)
344 continue;
345 if (i)
346 Tcl_AppendResult(irp, ", ", NULL);
347 else
348 i = 1;
349 Tcl_AppendResult(irp, tl->name, NULL);
350 }
351 }
352
353 static int unbind_bind_entry(tcl_bind_list_t *tl, const char *flags,
354 const char *cmd, const char *proc)
355 {
356 tcl_bind_mask_t *tm;
357
358 /* Search for matching bind in bind list. */
359 for (tm = tl->first; tm; tm = tm->next) {
360 if (tm->flags & TBM_DELETED)
361 continue;
362 if (!strcmp(cmd, tm->mask))
363 break; /* Found it! fall out! */
364 }
365
366 if (tm) {
367 tcl_cmd_t *tc;
368
369 /* Search for matching proc in bind. */
370 for (tc = tm->first; tc; tc = tc->next) {
371 if (tc->attributes & TC_DELETED)
372 continue;
373 if (!egg_strcasecmp(tc->func_name, proc)) {
374 /* Erase proc regardless of flags. */
375 tc->attributes |= TC_DELETED;
376 return 1; /* Match. */
377 }
378 }
379 }
380 return 0; /* No match. */
381 }
382
383 /* Add command (remove old one if necessary)
384 */
385 static int bind_bind_entry(tcl_bind_list_t *tl, const char *flags,
386 const char *cmd, const char *proc)
387 {
388 tcl_cmd_t *tc;
389 tcl_bind_mask_t *tm;
390
391 /* Search for matching bind in bind list. */
392 for (tm = tl->first; tm; tm = tm->next) {
393 if (tm->flags & TBM_DELETED)
394 continue;
395 if (!strcmp(cmd, tm->mask))
396 break; /* Found it! fall out! */
397 }
398
399 /* Create bind if it doesn't exist yet. */
400 if (!tm) {
401 tm = nmalloc_null(sizeof *tm);
402 tm->mask = nmalloc(strlen(cmd) + 1);
403 strcpy(tm->mask, cmd);
404
405 /* Link into linked list of binds. */
406 tm->next = tl->first;
407 tl->first = tm;
408 }
409
410 /* Proc already defined? If so, replace. */
411 for (tc = tm->first; tc; tc = tc->next) {
412 if (tc->attributes & TC_DELETED)
413 continue;
414 if (!egg_strcasecmp(tc->func_name, proc)) {
415 tc->flags.match = FR_GLOBAL | FR_CHAN;
416 break_down_flags(flags, &(tc->flags), NULL);
417 return 1;
418 }
419 }
420
421 /* If this bind list is not stackable, remove the
422 * old entry from this bind. */
423 if (!(tl->flags & HT_STACKABLE)) {
424 for (tc = tm->first; tc; tc = tc->next) {
425 if (tc->attributes & TC_DELETED)
426 continue;
427 /* NOTE: We assume there's only one not-yet-deleted entry. */
428 tc->attributes |= TC_DELETED;
429 break;
430 }
431 }
432
433 tc = nmalloc_null(sizeof *tc);
434 tc->flags.match = FR_GLOBAL | FR_CHAN;
435 break_down_flags(flags, &(tc->flags), NULL);
436 tc->func_name = nmalloc(strlen(proc) + 1);
437 strcpy(tc->func_name, proc);
438
439 /* Link into linked list of the bind's command list. */
440 tc->next = tm->first;
441 tm->first = tc;
442
443 return 1;
444 }
445
446 static int tcl_getbinds(tcl_bind_list_t *tl_kind, const char *name)
447 {
448 tcl_bind_mask_t *tm;
449
450 for (tm = tl_kind->first; tm; tm = tm->next) {
451 if (tm->flags & TBM_DELETED)
452 continue;
453 if (!egg_strcasecmp(tm->mask, name)) {
454 tcl_cmd_t *tc;
455
456 for (tc = tm->first; tc; tc = tc->next) {
457 if (tc->attributes & TC_DELETED)
458 continue;
459 Tcl_AppendElement(interp, tc->func_name);
460 }
461 break;
462 }
463 }
464 return TCL_OK;
465 }
466
467 static int tcl_bind STDVAR
468 {
469 tcl_bind_list_t *tl;
470
471 /* cd defines what tcl_bind is supposed do: 0 = bind, 1 = unbind. */
472 if ((long int) cd == 1)
473 BADARGS(5, 5, " type flags cmd/mask procname");
474
475 else
476 BADARGS(4, 5, " type flags cmd/mask ?procname?");
477
478 tl = find_bind_table(argv[1]);
479 if (!tl) {
480 Tcl_AppendResult(irp, "bad type, should be one of: ", NULL);
481 dump_bind_tables(irp);
482 return TCL_ERROR;
483 }
484 if ((long int) cd == 1) {
485 if (!unbind_bind_entry(tl, argv[2], argv[3], argv[4])) {
486 /* Don't error if trying to re-unbind a builtin */
487 if (argv[4][0] != '*' || argv[4][4] != ':' ||
488 strcmp(argv[3], &argv[4][5]) || strncmp(argv[1], &argv[4][1], 3)) {
489 Tcl_AppendResult(irp, "no such binding", NULL);
490 return TCL_ERROR;
491 }
492 }
493 } else {
494 if (argc == 4)
495 return tcl_getbinds(tl, argv[3]);
496 bind_bind_entry(tl, argv[2], argv[3], argv[4]);
497 }
498 Tcl_AppendResult(irp, argv[3], NULL);
499 return TCL_OK;
500 }
501
502 int check_validity(char *nme, IntFunc func)
503 {
504 char *p;
505 tcl_bind_list_t *tl;
506
507 if (*nme != '*')
508 return 0;
509 p = strchr(nme + 1, ':');
510 if (p == NULL)
511 return 0;
512 *p = 0;
513 tl = find_bind_table(nme + 1);
514 *p = ':';
515 if (!tl)
516 return 0;
517 if (tl->func != func)
518 return 0;
519 return 1;
520 }
521
522 static int builtin_3char STDVAR
523 {
524 Function F = (Function) cd;
525
526 BADARGS(4, 4, " from to args");
527
528 CHECKVALIDITY(builtin_3char);
529 F(argv[1], argv[2], argv[3]);
530 return TCL_OK;
531 }
532
533 static int builtin_2char STDVAR
534 {
535 Function F = (Function) cd;
536
537 BADARGS(3, 3, " nick msg");
538
539 CHECKVALIDITY(builtin_2char);
540 F(argv[1], argv[2]);
541 return TCL_OK;
542 }
543
544 static int builtin_5int STDVAR
545 {
546 Function F = (Function) cd;
547
548 BADARGS(6, 6, " min hrs dom mon year");
549
550 CHECKVALIDITY(builtin_5int);
551 F(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), atoi(argv[5]));
552 return TCL_OK;
553 }
554
555 static int builtin_cron STDVAR
556 {
557 Function F = (Function) cd;
558
559 BADARGS(6, 6, " min hrs dom mon weekday");
560
561 CHECKVALIDITY(builtin_cron);
562 F(atoi(argv[1]), atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), atoi(argv[5]));
563 return TCL_OK;
564 }
565
566 static int builtin_char STDVAR
567 {
568 Function F = (Function) cd;
569
570 BADARGS(2, 2, " handle");
571
572 CHECKVALIDITY(builtin_char);
573 F(argv[1]);
574 return TCL_OK;
575 }
576
577 static int builtin_chpt STDVAR
578 {
579 Function F = (Function) cd;
580
581 BADARGS(3, 3, " bot nick sock");
582
583 CHECKVALIDITY(builtin_chpt);
584 F(argv[1], argv[2], atoi(argv[3]));
585 return TCL_OK;
586 }
587
588 static int builtin_chjn STDVAR
589 {
590 Function F = (Function) cd;
591
592 BADARGS(6, 6, " bot nick chan# flag&sock host");
593
594 CHECKVALIDITY(builtin_chjn);
595 F(argv[1], argv[2], atoi(argv[3]), argv[4][0],
596 argv[4][0] ? atoi(argv[4] + 1) : 0, argv[5]);
597 return TCL_OK;
598 }
599
600 static int builtin_idxchar STDVAR
601 {
602 Function F = (Function) cd;
603 int idx;
604 char *r;
605
606 BADARGS(3, 3, " idx args");
607
608 CHECKVALIDITY(builtin_idxchar);
609 idx = findidx(atoi(argv[1]));
610 if (idx < 0) {
611 Tcl_AppendResult(irp, "invalid idx", NULL);
612 return TCL_ERROR;
613 }
614 r = (((char *(*)()) F) (idx, argv[2]));
615
616 Tcl_ResetResult(irp);
617 Tcl_AppendResult(irp, r, NULL);
618 return TCL_OK;
619 }
620
621 static int builtin_charidx STDVAR
622 {
623 Function F = (Function) cd;
624 int idx;
625
626 BADARGS(3, 3, " handle idx");
627
628 CHECKVALIDITY(builtin_charidx);
629 idx = findanyidx(atoi(argv[2]));
630 if (idx < 0) {
631 Tcl_AppendResult(irp, "invalid idx", NULL);
632 return TCL_ERROR;
633 }
634 Tcl_AppendResult(irp, int_to_base10(F(argv[1], idx)), NULL);
635
636 return TCL_OK;
637 }
638
639 static int builtin_chat STDVAR
640 {
641 Function F = (Function) cd;
642 int ch;
643
644 BADARGS(4, 4, " handle idx text");
645
646 CHECKVALIDITY(builtin_chat);
647 ch = atoi(argv[2]);
648 F(argv[1], ch, argv[3]);
649 return TCL_OK;
650 }
651
652 static int builtin_dcc STDVAR
653 {
654 int idx;
655 Function F = (Function) cd;
656
657 BADARGS(4, 4, " hand idx param");
658
659 CHECKVALIDITY(builtin_dcc);
660 idx = findidx(atoi(argv[2]));
661 if (idx < 0) {
662 Tcl_AppendResult(irp, "invalid idx", NULL);
663 return TCL_ERROR;
664 }
665
666 /* FIXME: This is an ugly hack. It is not documented as a
667 * 'feature' because it will eventually go away.
668 */
669 if (F == CMD_LEAVE) {
670 Tcl_AppendResult(irp, "break", NULL);
671 return TCL_OK;
672 }
673
674 /* Check if it's a password change, if so, don't show the password. We
675 * don't need pretty formats here, as it's only for debugging purposes.
676 */
677 debug4("tcl: builtin dcc call: %s %s %s %s", argv[0], argv[1], argv[2],
678 (!strcmp(argv[0] + 5, "newpass") || !strcmp(argv[0] + 5, "chpass")) ?
679 "[something]" : argv[3]);
680 F(dcc[idx].user, idx, argv[3]);
681 Tcl_ResetResult(irp);
682 Tcl_AppendResult(irp, "0", NULL);
683 return TCL_OK;
684 }
685
686 static int builtin_log STDVAR
687 {
688 Function F = (Function) cd;
689
690 BADARGS(3, 3, " lvl chan msg");
691
692 CHECKVALIDITY(builtin_log);
693 F(argv[1], argv[2], argv[3]);
694 return TCL_OK;
695 }
696
697 #ifdef TLS
698 static int builtin_idx STDVAR
699 {
700 Function F = (Function) cd;
701
702 BADARGS(2, 2, " idx");
703
704 CHECKVALIDITY(builtin_idx);
705 F(atoi(argv[1]));
706 return TCL_OK;
707 }
708 #endif
709
710 /* Trigger (execute) a Tcl proc
711 *
712 * Note: This is INLINE code for check_tcl_bind().
713 */
714 static inline int trigger_bind(const char *proc, const char *param,
715 char *mask)
716 {
717 int x;
718 #ifdef DEBUG_CONTEXT
719 const char *msg = "Tcl proc: %s, param: %s";
720 char *buf;
721
722 /* We now try to debug the Tcl_VarEval() call below by remembering both
723 * the called proc name and it's parameters. This should render us a bit
724 * less helpless when we see context dumps.
725 */
726 Context;
727 buf = nmalloc(strlen(msg) + (proc ? strlen(proc) : 6)
728 + (param ? strlen(param) : 6) + 1);
729 sprintf(buf, msg, proc ? proc : "<null>", param ? param : "<null>");
730 ContextNote(buf);
731 nfree(buf);
732 #endif /* DEBUG_CONTEXT */
733
734 /* Set the lastbind variable before evaluating the proc so that the name
735 * of the command that triggered the bind will be available to the proc.
736 * This feature is used by scripts such as userinfo.tcl
737 */
738 Tcl_SetVar(interp, "lastbind", (char *) mask, TCL_GLOBAL_ONLY);
739
740 x = Tcl_VarEval(interp, proc, param, NULL);
741 Context;
742
743 if (x == TCL_ERROR) {
744 /* FIXME: we really should be able to log longer errors */
745 putlog(LOG_MISC, "*", "Tcl error [%s]: %.*s", proc, 400, tcl_resultstring());
746
747 return BIND_EXECUTED;
748 }
749
750 /* FIXME: This is an ugly hack. It is not documented as a
751 * 'feature' because it will eventually go away.
752 */
753 if (!strcmp(tcl_resultstring(), "break"))
754 return BIND_QUIT;
755
756 return (tcl_resultint() > 0) ? BIND_EXEC_LOG : BIND_EXECUTED;
757 }
758
759
760 /* Find out whether this bind matches the mask or provides the
761 * requested attributes, depending on the specified requirements.
762 *
763 * Note: This is INLINE code for check_tcl_bind().
764 */
765 static inline int check_bind_match(const char *match, char *mask,
766 int match_type)
767 {
768 switch (match_type & 0x07) {
769 case MATCH_PARTIAL:
770 return (!egg_strncasecmp(match, mask, strlen(match)));
771 break;
772 case MATCH_EXACT:
773 return (!egg_strcasecmp(match, mask));
774 break;
775 case MATCH_CASE:
776 return (!strcmp(match, mask));
777 break;
778 case MATCH_MASK:
779 return (wild_match_per(mask, match));
780 break;
781 case MATCH_MODE:
782 return (wild_match_partial_case(mask, match));
783 break;
784 case MATCH_CRON:
785 return (cron_match(mask, match));
786 break;
787 default:
788 /* Do nothing */
789 break;
790 }
791 return 0;
792 }
793
794
795 /* Check if the provided flags suffice for this command/trigger.
796 *
797 * Note: This is INLINE code for check_tcl_bind().
798 */
799 static inline int check_bind_flags(struct flag_record *flags,
800 struct flag_record *atr, int match_type)
801 {
802 if (match_type & BIND_USE_ATTR) {
803 if (match_type & BIND_HAS_BUILTINS)
804 return (flagrec_ok(flags, atr));
805 else
806 return (flagrec_eq(flags, atr));
807 } else
808 return 1;
809 return 0;
810 }
811
812
813 /* Check for and process Tcl binds */
814 int check_tcl_bind(tcl_bind_list_t *tl, const char *match,
815 struct flag_record *atr, const char *param, int match_type)
816 {
817 int x, result = 0, cnt = 0, finish = 0;
818 char *proc = NULL, *mask = NULL;
819 tcl_bind_mask_t *tm, *tm_last = NULL, *tm_p = NULL;
820 tcl_cmd_t *tc, *htc = NULL;
821
822 for (tm = tl->first; tm && !finish; tm_last = tm, tm = tm->next) {
823
824 if (tm->flags & TBM_DELETED)
825 continue; /* This bind mask was deleted */
826
827 if (!check_bind_match(match, tm->mask, match_type))
828 continue; /* This bind does not match. */
829
830 for (tc = tm->first; tc; tc = tc->next) {
831
832 /* Search for valid entry. */
833 if (!(tc->attributes & TC_DELETED)) {
834
835 /* Check if the provided flags suffice for this command. */
836 if (check_bind_flags(&tc->flags, atr, match_type)) {
837 cnt++;
838 tm_p = tm_last;
839
840 /* Not stackable */
841 if (!(match_type & BIND_STACKABLE)) {
842
843 /* Remember information about this bind. */
844 proc = tc->func_name;
845 mask = tm->mask;
846 htc = tc;
847
848 /* Either this is a non-partial match, which means we
849 * only want to execute _one_ bind ...
850 */
851 if ((match_type & 0x07) != MATCH_PARTIAL ||
852 /* ... or this happens to be an exact match. */
853 !egg_strcasecmp(match, tm->mask)) {
854 cnt = 1;
855 finish = 1;
856 }
857
858 /* We found a match so break out of the inner loop. */
859 break;
860 }
861
862 /*
863 * Stackable; could be multiple commands/triggers.
864 * Note: This code assumes BIND_ALTER_ARGS, BIND_WANTRET, and
865 * BIND_STACKRET will only be used for stackable binds.
866 */
867
868 /* We will only return if BIND_ALTER_ARGS or BIND_WANTRET was
869 * specified because we want to trigger all binds in a stack.
870 */
871
872 tc->hits++;
873 x = trigger_bind(tc->func_name, param, tm->mask);
874
875 if (match_type & BIND_ALTER_ARGS) {
876 if (tcl_resultempty())
877 return x;
878 } else if ((match_type & BIND_STACKRET) && x == BIND_EXEC_LOG) {
879 /* If we have multiple commands/triggers, and if any of the
880 * commands return 1, we store the result so we can return it
881 * after processing all stacked binds.
882 */
883 if (!result)
884 result = x;
885 continue;
886 } else if ((match_type & BIND_WANTRET) && x == BIND_EXEC_LOG)
887 /* Return immediately if any commands return 1 */
888 return x;
889 }
890 }
891 }
892 }
893
894 if (!cnt)
895 return BIND_NOMATCH;
896
897 /* Do this before updating the preferred entries information,
898 * since we don't want to change the order of stacked binds
899 */
900 if (result) /* BIND_STACKRET */
901 return result;
902
903 if ((match_type & 0x07) == MATCH_MASK || (match_type & 0x07) == MATCH_CASE)
904 return BIND_EXECUTED;
905
906 /* Hit counter */
907 if (htc)
908 htc->hits++;
909
910 /* Now that we have found at least one bind, we can update the
911 * preferred entries information.
912 */
913 if (tm_p && tm_p->next) {
914 tm = tm_p->next; /* Move mask to front of bind's mask list. */
915 tm_p->next = tm->next; /* Unlink mask from list. */
916 tm->next = tl->first; /* Readd mask to front of list. */
917 tl->first = tm;
918 }
919
920 if (cnt > 1)
921 return BIND_AMBIGUOUS;
922
923 return trigger_bind(proc, param, mask);
924 }
925
926
927 /* Check for tcl-bound dcc command, return 1 if found
928 * dcc: proc-name <handle> <sock> <args...>
929 */
930 int check_tcl_dcc(const char *cmd, int idx, const char *args)
931 {
932 struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
933 int x;
934 char s[11];
935
936 get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
937 egg_snprintf(s, sizeof s, "%ld", dcc[idx].sock);
938 Tcl_SetVar(interp, "_dcc1", (char *) dcc[idx].nick, 0);
939 Tcl_SetVar(interp, "_dcc2", (char *) s, 0);
940 Tcl_SetVar(interp, "_dcc3", (char *) args, 0);
941 x = check_tcl_bind(H_dcc, cmd, &fr, " $_dcc1 $_dcc2 $_dcc3",
942 MATCH_PARTIAL | BIND_USE_ATTR | BIND_HAS_BUILTINS);
943 if (x == BIND_AMBIGUOUS) {
944 dprintf(idx, MISC_AMBIGUOUS);
945 return 0;
946 }
947 if (x == BIND_NOMATCH) {
948 dprintf(idx, MISC_NOSUCHCMD);
949 return 0;
950 }
951
952 /* We return 1 to leave the partyline */
953 if (x == BIND_QUIT) /* CMD_LEAVE, 'quit' */
954 return 1;
955
956 if (x == BIND_EXEC_LOG)
957 putlog(LOG_CMDS, "*", "#%s# %s %s", dcc[idx].nick, cmd, args);
958 return 0;
959 }
960
961 void check_tcl_bot(const char *nick, const char *code, const char *param)
962 {
963 Tcl_SetVar(interp, "_bot1", (char *) nick, 0);
964 Tcl_SetVar(interp, "_bot2", (char *) code, 0);
965 Tcl_SetVar(interp, "_bot3", (char *) param, 0);
966 check_tcl_bind(H_bot, code, 0, " $_bot1 $_bot2 $_bot3", MATCH_EXACT);
967 }
968
969 void check_tcl_chonof(char *hand, int sock, tcl_bind_list_t *tl)
970 {
971 struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
972 char s[11];
973 struct userrec *u;
974
975 u = get_user_by_handle(userlist, hand);
976 touch_laston(u, "partyline", now);
977 get_user_flagrec(u, &fr, NULL);
978 Tcl_SetVar(interp, "_chonof1", (char *) hand, 0);
979 egg_snprintf(s, sizeof s, "%d", sock);
980 Tcl_SetVar(interp, "_chonof2", (char *) s, 0);
981 check_tcl_bind(tl, hand, &fr, " $_chonof1 $_chonof2", MATCH_MASK |
982 BIND_USE_ATTR | BIND_STACKABLE | BIND_WANTRET);
983 }
984
985 void check_tcl_chatactbcst(const char *from, int chan, const char *text,
986 tcl_bind_list_t *tl)
987 {
988 char s[11];
989
990 egg_snprintf(s, sizeof s, "%d", chan);
991 Tcl_SetVar(interp, "_cab1", (char *) from, 0);
992 Tcl_SetVar(interp, "_cab2", (char *) s, 0);
993 Tcl_SetVar(interp, "_cab3", (char *) text, 0);
994 check_tcl_bind(tl, text, 0, " $_cab1 $_cab2 $_cab3",
995 MATCH_MASK | BIND_STACKABLE);
996 }
997
998 void check_tcl_nkch(const char *ohand, const char *nhand)
999 {
1000 Tcl_SetVar(interp, "_nkch1", (char *) ohand, 0);
1001 Tcl_SetVar(interp, "_nkch2", (char *) nhand, 0);
1002 check_tcl_bind(H_nkch, ohand, 0, " $_nkch1 $_nkch2",
1003 MATCH_MASK | BIND_STACKABLE);
1004 }
1005
1006 void check_tcl_link(const char *bot, const char *via)
1007 {
1008 Tcl_SetVar(interp, "_link1", (char *) bot, 0);
1009 Tcl_SetVar(interp, "_link2", (char *) via, 0);
1010 check_tcl_bind(H_link, bot, 0, " $_link1 $_link2",
1011 MATCH_MASK | BIND_STACKABLE);
1012 }
1013
1014 void check_tcl_disc(const char *bot)
1015 {
1016 Tcl_SetVar(interp, "_disc1", (char *) bot, 0);
1017 check_tcl_bind(H_disc, bot, 0, " $_disc1", MATCH_MASK | BIND_STACKABLE);
1018 }
1019
1020 void check_tcl_loadunld(const char *mod, tcl_bind_list_t *tl)
1021 {
1022 Tcl_SetVar(interp, "_lu1", (char *) mod, 0);
1023 check_tcl_bind(tl, mod, 0, " $_lu1", MATCH_MASK | BIND_STACKABLE);
1024 }
1025
1026 const char *check_tcl_filt(int idx, const char *text)
1027 {
1028 char s[11];
1029 int x;
1030 struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0, 0, 0 };
1031
1032 egg_snprintf(s, sizeof s, "%ld", dcc[idx].sock);
1033 get_user_flagrec(dcc[idx].user, &fr, dcc[idx].u.chat->con_chan);
1034 Tcl_SetVar(interp, "_filt1", (char *) s, 0);
1035 Tcl_SetVar(interp, "_filt2", (char *) text, 0);
1036 x = check_tcl_bind(H_filt, text, &fr, " $_filt1 $_filt2",
1037 MATCH_MASK | BIND_USE_ATTR | BIND_STACKABLE |
1038 BIND_WANTRET | BIND_ALTER_ARGS);
1039 if (x == BIND_EXECUTED || x == BIND_EXEC_LOG) {
1040 if (tcl_resultempty())
1041 return "";
1042 else
1043 return tcl_resultstring();
1044 } else
1045 return text;
1046 }
1047
1048 int check_tcl_note(const char *from, const char *to, const char *text)
1049 {
1050 int x;
1051
1052 Tcl_SetVar(interp, "_note1", (char *) from, 0);
1053 Tcl_SetVar(interp, "_note2", (char *) to, 0);
1054 Tcl_SetVar(interp, "_note3", (char *) text, 0);
1055
1056 x = check_tcl_bind(H_note, to, 0, " $_note1 $_note2 $_note3",
1057 MATCH_MASK | BIND_STACKABLE | BIND_WANTRET);
1058
1059 return (x == BIND_EXEC_LOG);
1060 }
1061
1062 void check_tcl_listen(const char *cmd, int idx)
1063 {
1064 char s[11];
1065 int x;
1066
1067 egg_snprintf(s, sizeof s, "%d", idx);
1068 Tcl_SetVar(interp, "_n", (char *) s, 0);
1069 x = Tcl_VarEval(interp, cmd, " $_n", NULL);
1070 if (x == TCL_ERROR)
1071 putlog(LOG_MISC, "*", "error on listen: %s", tcl_resultstring());
1072 }
1073
1074 void check_tcl_chjn(const char *bot, const char *nick, int chan,
1075 const char type, int sock, const char *host)
1076 {
1077 struct flag_record fr = { FR_GLOBAL, 0, 0, 0, 0, 0 };
1078 char s[11], t[2], u[11];
1079
1080 t[0] = type;
1081 t[1] = 0;
1082 switch (type) {
1083 case '*':
1084 fr.global = USER_OWNER;
1085
1086 break;
1087 case '+':
1088 fr.global = USER_MASTER;
1089
1090 break;
1091 case '@':
1092 fr.global = USER_OP;
1093
1094 break;
1095 case '^':
1096 fr.global = USER_HALFOP;
1097
1098 break;
1099 case '%':
1100 fr.global = USER_BOTMAST;
1101 }
1102 egg_snprintf(s, sizeof s, "%d", chan);
1103 egg_snprintf(u, sizeof u, "%d", sock);
1104 Tcl_SetVar(interp, "_chjn1", (char *) bot, 0);
1105 Tcl_SetVar(interp, "_chjn2", (char *) nick, 0);
1106 Tcl_SetVar(interp, "_chjn3", (char *) s, 0);
1107 Tcl_SetVar(interp, "_chjn4", (char *) t, 0);
1108 Tcl_SetVar(interp, "_chjn5", (char *) u, 0);
1109 Tcl_SetVar(interp, "_chjn6", (char *) host, 0);
1110 check_tcl_bind(H_chjn, s, &fr,
1111 " $_chjn1 $_chjn2 $_chjn3 $_chjn4 $_chjn5 $_chjn6",
1112 MATCH_MASK | BIND_STACKABLE);
1113 }
1114
1115 void check_tcl_chpt(const char *bot, const char *hand, int sock, int chan)
1116 {
1117 char u[11], v[11];
1118
1119 egg_snprintf(u, sizeof u, "%d", sock);
1120 egg_snprintf(v, sizeof v, "%d", chan);
1121 Tcl_SetVar(interp, "_chpt1", (char *) bot, 0);
1122 Tcl_SetVar(interp, "_chpt2", (char *) hand, 0);
1123 Tcl_SetVar(interp, "_chpt3", (char *) u, 0);
1124 Tcl_SetVar(interp, "_chpt4", (char *) v, 0);
1125 check_tcl_bind(H_chpt, v, 0, " $_chpt1 $_chpt2 $_chpt3 $_chpt4",
1126 MATCH_MASK | BIND_STACKABLE);
1127 }
1128
1129 void check_tcl_away(const char *bot, int idx, const char *msg)
1130 {
1131 char u[11];
1132
1133 egg_snprintf(u, sizeof u, "%d", idx);
1134 Tcl_SetVar(interp, "_away1", (char *) bot, 0);
1135 Tcl_SetVar(interp, "_away2", (char *) u, 0);
1136 Tcl_SetVar(interp, "_away3", msg ? (char *) msg : "", 0);
1137 check_tcl_bind(H_away, bot, 0, " $_away1 $_away2 $_away3",
1138 MATCH_MASK | BIND_STACKABLE);
1139 }
1140
1141 void check_tcl_time(struct tm *tm)
1142 {
1143 char y[18];
1144
1145 egg_snprintf(y, sizeof y, "%02d", tm->tm_min);
1146 Tcl_SetVar(interp, "_time1", (char *) y, 0);
1147 egg_snprintf(y, sizeof y, "%02d", tm->tm_hour);
1148 Tcl_SetVar(interp, "_time2", (char *) y, 0);
1149 egg_snprintf(y, sizeof y, "%02d", tm->tm_mday);
1150 Tcl_SetVar(interp, "_time3", (char *) y, 0);
1151 egg_snprintf(y, sizeof y, "%02d", tm->tm_mon);
1152 Tcl_SetVar(interp, "_time4", (char *) y, 0);
1153 egg_snprintf(y, sizeof y, "%04d", tm->tm_year + 1900);
1154 Tcl_SetVar(interp, "_time5", (char *) y, 0);
1155 egg_snprintf(y, sizeof y, "%02d %02d %02d %02d %04d", tm->tm_min, tm->tm_hour,
1156 tm->tm_mday, tm->tm_mon, tm->tm_year + 1900);
1157 check_tcl_bind(H_time, y, 0,
1158 " $_time1 $_time2 $_time3 $_time4 $_time5",
1159 MATCH_MASK | BIND_STACKABLE);
1160 }
1161
1162 void check_tcl_cron(struct tm *tm)
1163 {
1164 char y[15];
1165
1166 egg_snprintf(y, sizeof y, "%02d", tm->tm_min);
1167 Tcl_SetVar(interp, "_cron1", (char *) y, 0);
1168 egg_snprintf(y, sizeof y, "%02d", tm->tm_hour);
1169 Tcl_SetVar(interp, "_cron2", (char *) y, 0);
1170 egg_snprintf(y, sizeof y, "%02d", tm->tm_mday);
1171 Tcl_SetVar(interp, "_cron3", (char *) y, 0);
1172 egg_snprintf(y, sizeof y, "%02d", tm->tm_mon + 1);
1173 Tcl_SetVar(interp, "_cron4", (char *) y, 0);
1174 egg_snprintf(y, sizeof y, "%02d", tm->tm_wday);
1175 Tcl_SetVar(interp, "_cron5", (char *) y, 0);
1176 egg_snprintf(y, sizeof y, "%02d %02d %02d %02d %02d", tm->tm_min, tm->tm_hour,
1177 tm->tm_mday, tm->tm_mon + 1, tm->tm_wday);
1178 check_tcl_bind(H_cron, y, 0,
1179 " $_cron1 $_cron2 $_cron3 $_cron4 $_cron5",
1180 MATCH_CRON | BIND_STACKABLE);
1181 }
1182
1183 void check_tcl_event(const char *event)
1184 {
1185 Tcl_SetVar(interp, "_event1", (char *) event, 0);
1186 check_tcl_bind(H_event, event, 0, " $_event1", MATCH_EXACT | BIND_STACKABLE);
1187 }
1188
1189 void check_tcl_log(int lv, char *chan, char *msg)
1190 {
1191 char mask[512];
1192
1193 snprintf(mask, sizeof mask, "%s %s", chan, msg);
1194 Tcl_SetVar(interp, "_log1", masktype(lv), 0);
1195 Tcl_SetVar(interp, "_log2", chan, 0);
1196 Tcl_SetVar(interp, "_log3", msg, 0);
1197 check_tcl_bind(H_log, mask, 0, " $_log1 $_log2 $_log3",
1198 MATCH_MASK | BIND_STACKABLE);
1199 }
1200
1201 #ifdef TLS
1202 int check_tcl_tls(int sock)
1203 {
1204 int x;
1205 char s[11];
1206
1207 egg_snprintf(s, sizeof s, "%d", sock);
1208 Tcl_SetVar(interp, "_tls", s, 0);
1209 x = check_tcl_bind(H_tls, s, 0, " $_tls", MATCH_MASK | BIND_STACKABLE |
1210 BIND_WANTRET);
1211 return (x == BIND_EXEC_LOG);
1212 }
1213 #endif
1214
1215 void tell_binds(int idx, char *par)
1216 {
1217 tcl_bind_list_t *tl, *tl_kind;
1218 tcl_bind_mask_t *tm;
1219 int fnd = 0, showall = 0, patmatc = 0;
1220 tcl_cmd_t *tc;
1221 char *name, *proc, *s, flg[100];
1222
1223 if (par[0])
1224 name = newsplit(&par);
1225 else
1226 name = NULL;
1227 if (par[0])
1228 s = newsplit(&par);
1229 else
1230 s = NULL;
1231
1232 if (name)
1233 tl_kind = find_bind_table(name);
1234 else
1235 tl_kind = NULL;
1236
1237 if ((name && name[0] && !egg_strcasecmp(name, "all")) ||
1238 (s && s[0] && !egg_strcasecmp(s, "all")))
1239 showall = 1;
1240 if (tl_kind == NULL && name && name[0] && egg_strcasecmp(name, "all"))
1241 patmatc = 1;
1242
1243 dprintf(idx, MISC_CMDBINDS);
1244 dprintf(idx, " TYPE FLAGS COMMAND HITS BINDING (TCL)\n");
1245
1246 for (tl = tl_kind ? tl_kind : bind_table_list; tl;
1247 tl = tl_kind ? 0 : tl->next) {
1248 if (tl->flags & HT_DELETED)
1249 continue;
1250 for (tm = tl->first; tm; tm = tm->next) {
1251 if (tm->flags & TBM_DELETED)
1252 continue;
1253 for (tc = tm->first; tc; tc = tc->next) {
1254 if (tc->attributes & TC_DELETED)
1255 continue;
1256 proc = tc->func_name;
1257 build_flags(flg, &(tc->flags), NULL);
1258 if (showall || proc[0] != '*') {
1259 int ok = 0;
1260
1261 if (patmatc == 1) {
1262 if (wild_match_per(name, tl->name) ||
1263 wild_match_per(name, tm->mask) ||
1264 wild_match_per(name, tc->func_name))
1265 ok = 1;
1266 } else
1267 ok = 1;
1268
1269 if (ok) {
1270 dprintf(idx, " %-4s %-8s %-20s %4d %s\n", tl->name, flg, tm->mask,
1271 tc->hits, tc->func_name);
1272 fnd = 1;
1273 }
1274 }
1275 }
1276 }
1277 }
1278 if (!fnd) {
1279 if (patmatc)
1280 dprintf(idx, "No command bindings found that match %s\n", name);
1281 else if (tl_kind)
1282 dprintf(idx, "No command bindings for type: %s.\n", name);
1283 else
1284 dprintf(idx, "No command bindings exist.\n");
1285 }
1286 }
1287
1288 /* Bring the default msg/dcc/fil commands into the Tcl interpreter */
1289 void add_builtins(tcl_bind_list_t *tl, cmd_t *cc)
1290 {
1291 int k, i;
1292 char p[1024], *l;
1293 cd_tcl_cmd table[2];
1294
1295 table[0].name = p;
1296 table[0].callback = tl->func;
1297 table[1].name = NULL;
1298 for (i = 0; cc[i].name; i++) {
1299 egg_snprintf(p, sizeof p, "*%s:%s", tl->name,
1300 cc[i].funcname ? cc[i].funcname : cc[i].name);
1301 l = nmalloc(Tcl_ScanElement(p, &k));
1302 Tcl_ConvertElement(p, l, k | TCL_DONT_USE_BRACES);
1303 table[0].cdata = (void *) cc[i].func;
1304 add_cd_tcl_cmds(table);
1305 bind_bind_entry(tl, cc[i].flags, cc[i].name, l);
1306 nfree(l);
1307 }
1308 }
1309
1310 /* Remove the default msg/dcc/fil commands from the Tcl interpreter */
1311 void rem_builtins(tcl_bind_list_t *table, cmd_t *cc)
1312 {
1313 int k, i;
1314 char p[1024], *l;
1315
1316 for (i = 0; cc[i].name; i++) {
1317 egg_snprintf(p, sizeof p, "*%s:%s", table->name,
1318 cc[i].funcname ? cc[i].funcname : cc[i].name);
1319 l = nmalloc(Tcl_ScanElement(p, &k));
1320 Tcl_ConvertElement(p, l, k | TCL_DONT_USE_BRACES);
1321 Tcl_DeleteCommand(interp, p);
1322 unbind_bind_entry(table, cc[i].flags, cc[i].name, l);
1323 nfree(l);
1324 }
1325 }

webmaster@eggheads.org
ViewVC Help
Powered by ViewVC 1.1.23