1 |
/* Copyright (C) 2003, 2004 Stephane Thiell |
2 |
* |
3 |
* This file is part of pxyservd (from pxys) |
4 |
* |
5 |
* This program is free software; you can redistribute it and/or |
6 |
* modify it under the terms of the GNU General Public License |
7 |
* as published by the Free Software Foundation; either version 2 |
8 |
* of the License, or (at your option) any later version. |
9 |
* |
10 |
* This program is distributed in the hope that it will be useful, |
11 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 |
* GNU General Public License for more details. |
14 |
* |
15 |
* You should have received a copy of the GNU General Public License |
16 |
* along with this program; if not, write to the Free Software |
17 |
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
18 |
* |
19 |
*/ |
20 |
#define RCSID "$Id: irc_parser.c,v 1.5 2004/01/10 18:15:36 mbuna Exp $" |
21 |
|
22 |
/* Here is my exotic/experimental partial IRCU P10 commands parser... |
23 |
* Its main goal is performance. |
24 |
* If i had more time, i would try with a "static" peak's dictionary |
25 |
* and compare the performance, but as IRC doesn't have tons of commands, |
26 |
* not sure the internal peak's dictionary hash table can beat a few integer |
27 |
* comparaisons (see parser_dispatch())... |
28 |
* Some compilers like gcc on i386 worry about multi-character character |
29 |
* constant and print a warning by default. You can usually safely ignore them |
30 |
* for this file. But if there is a problem, please let me know! --mbuna |
31 |
*/ |
32 |
|
33 |
#ifdef HAVE_CONFIG_H |
34 |
#include "config.h" |
35 |
#endif |
36 |
|
37 |
#include "irc_parser.h" |
38 |
|
39 |
#include <assert.h> |
40 |
#include <stdlib.h> |
41 |
#include <string.h> |
42 |
#include <unistd.h> |
43 |
|
44 |
#include "cfgloader.h" |
45 |
#include "debug.h" |
46 |
#include "pxyservd_log.h" |
47 |
#include "session.h" |
48 |
#include "tokenizer.h" |
49 |
|
50 |
#include "irc_client.h" |
51 |
#include "irc_mode.h" |
52 |
#include "irc_msg.h" |
53 |
#include "irc_network.h" |
54 |
#include "irc_numnicks.h" |
55 |
#include "irc_send.h" |
56 |
#include "irc_string.h" |
57 |
#include "irc_userbase.h" |
58 |
#include "irc_yxx.h" |
59 |
|
60 |
#define NOT_BURSTING 0 |
61 |
#define BURSTING 1 |
62 |
|
63 |
#if 0 |
64 |
#define RET_BOGUS do { msg_bogus++; return; } while (0) |
65 |
#else |
66 |
#define RET_BOGUS do { msg_bogus++; \ |
67 |
log_system("irc_parser:%d BOGUS!", __LINE__); \ |
68 |
return; } while (0) |
69 |
#endif |
70 |
|
71 |
int msg_bogus; /* exported to cmd_status */ |
72 |
static time_t burst_start, burst_delay; /* fun */ |
73 |
|
74 |
struct Message |
75 |
{ |
76 |
const char *cmd; |
77 |
unsigned int count; /* number of times command used */ |
78 |
unsigned int rcount; /* number of times command used by server */ |
79 |
unsigned int args_min; /* at least this many args must be passed |
80 |
* or an error will be sent to the user |
81 |
* before the m_func is even called |
82 |
*/ |
83 |
unsigned int args_max; /* maximum permitted parameters */ |
84 |
unsigned int flags; /* bit 0 set means that this command is allowed |
85 |
* to be used only on the average of once per 2 |
86 |
* seconds -SRB |
87 |
*/ |
88 |
uint64_t bytes; /* bytes received for this message */ |
89 |
|
90 |
void (*handler)(toktabptr); |
91 |
}; |
92 |
|
93 |
/* |
94 |
* (based on orabidoo's parser code) |
95 |
* |
96 |
* This has always just been a trie. Look at volume III of Knuth ACP |
97 |
* |
98 |
* |
99 |
* ok, you start out with an array of pointers, each one corresponds |
100 |
* to a letter at the current position in the command being examined. |
101 |
* |
102 |
* so roughly you have this for matching 'trie' or 'tie' |
103 |
* |
104 |
* 't' points -> [MessageTree *] 'r' -> [MessageTree *] -> 'i' |
105 |
* -> [MessageTree *] -> [MessageTree *] -> 'e' and matches |
106 |
* |
107 |
* 'i' -> [MessageTree *] -> 'e' and matches |
108 |
* |
109 |
* BUGS (Limitations!) |
110 |
* |
111 |
* I designed this trie to parse ircd commands. Hence it currently |
112 |
* casefolds. This is trivial to fix by increasing MAXPTRLEN. |
113 |
* This trie also "folds" '{' etc. down. This means, the input to this |
114 |
* trie must be alpha tokens only. This again, is a limitation that |
115 |
* can be overcome by increasing MAXPTRLEN to include upper/lower case |
116 |
* at the expense of more memory. At the extreme end, you could make |
117 |
* MAXPTRLEN 128. |
118 |
* |
119 |
* This is also not a patricia trie. On short ircd tokens, this is |
120 |
* not likely going to matter. |
121 |
* |
122 |
* Diane Bruce (Dianora), June 6 2003 |
123 |
*/ |
124 |
|
125 |
#define MAXPTRLEN 32 |
126 |
/* Must be a power of 2, and |
127 |
* larger than 26 [a-z]|[A-Z] |
128 |
* its used to allocate the set |
129 |
* of pointers at each node of the tree |
130 |
* There are MAXPTRLEN pointers at each node. |
131 |
* Obviously, there have to be more pointers |
132 |
* Than ASCII letters. 32 is a nice number |
133 |
* since there is then no need to shift |
134 |
* 'A'/'a' to base 0 index, at the expense |
135 |
* of a few never used pointers. For a small |
136 |
* parser like this, this is a good compromise |
137 |
* and does make it somewhat faster. |
138 |
* |
139 |
* - Dianora |
140 |
*/ |
141 |
|
142 |
struct MessageTree |
143 |
{ |
144 |
int links; /* Count of all pointers (including msg) at this node |
145 |
* used as reference count for deletion of _this_ node. |
146 |
*/ |
147 |
struct Message *msg; |
148 |
struct MessageTree *pointers[MAXPTRLEN]; |
149 |
}; |
150 |
|
151 |
static struct MessageTree msg_tree; |
152 |
|
153 |
/* add_msg_element() |
154 |
* |
155 |
* inputs - pointer to MessageTree |
156 |
* - pointer to Message to add for given command |
157 |
* - pointer to current portion of command being added |
158 |
* output - NONE |
159 |
* side effects - recursively build the Message Tree ;-) |
160 |
*/ |
161 |
/* |
162 |
* How this works. |
163 |
* |
164 |
* The code first checks to see if its reached the end of the command |
165 |
* If so, that struct MessageTree has a msg pointer updated and the links |
166 |
* count incremented, since a msg pointer is a reference. |
167 |
* Then the code descends recursively, building the trie. |
168 |
* If a pointer index inside the struct MessageTree is NULL a new |
169 |
* child struct MessageTree has to be allocated. |
170 |
* The links (reference count) is incremented as they are created |
171 |
* in the parent. |
172 |
*/ |
173 |
static void |
174 |
add_msg_element(struct MessageTree *mtree_p, struct Message *msg_p, |
175 |
const char *cmd) |
176 |
{ |
177 |
struct MessageTree *ntree_p; |
178 |
|
179 |
if (*cmd == '\0') |
180 |
{ |
181 |
mtree_p->msg = msg_p; |
182 |
mtree_p->links++; /* Have msg pointer, so up ref count */ |
183 |
} |
184 |
else |
185 |
{ |
186 |
/* |
187 |
* *cmd & (MAXPTRLEN-1) |
188 |
* convert the char pointed to at *cmd from ASCII to an integer |
189 |
* between 0 and MAXPTRLEN. |
190 |
* Thus 'A' -> 0x1 'B' -> 0x2 'c' -> 0x3 etc. |
191 |
*/ |
192 |
if ((ntree_p = mtree_p->pointers[*cmd & (MAXPTRLEN - 1)]) == NULL) |
193 |
{ |
194 |
ntree_p = calloc(1, sizeof(struct MessageTree)); |
195 |
mtree_p->pointers[*cmd & (MAXPTRLEN - 1)] = ntree_p; |
196 |
|
197 |
mtree_p->links++; /* Have new pointer, so up ref count */ |
198 |
} |
199 |
|
200 |
add_msg_element(ntree_p, msg_p, cmd + 1); |
201 |
} |
202 |
} |
203 |
|
204 |
/* del_msg_element() |
205 |
* |
206 |
* inputs - Pointer to MessageTree to delete from |
207 |
* - pointer to command name to delete |
208 |
* output - NONE |
209 |
* side effects - recursively deletes a token from the Message Tree ;-) |
210 |
*/ |
211 |
/* |
212 |
* How this works. |
213 |
* |
214 |
* Well, first off, the code recursively descends into the trie |
215 |
* until it finds the terminating letter of the command being removed. |
216 |
* Once it has done that, it marks the msg pointer as NULL then |
217 |
* reduces the reference count on that allocated struct MessageTree |
218 |
* since a command counts as a reference. |
219 |
* |
220 |
* Then it pops up the recurse stack. As it comes back up the recurse |
221 |
* The code checks to see if the child now has no pointers or msg |
222 |
* i.e. the links count has gone to zero. If its no longer used, the |
223 |
* child struct MessageTree can be deleted. The parent reference |
224 |
* to this child is then removed and the parents link count goes down. |
225 |
* Thus, we continue to go back up removing all unused MessageTree(s) |
226 |
*/ |
227 |
static void |
228 |
del_msg_element(struct MessageTree *mtree_p, const char *cmd) |
229 |
{ |
230 |
struct MessageTree *ntree_p; |
231 |
|
232 |
/* |
233 |
* In case this is called for a nonexistent command |
234 |
* check that there is a msg pointer here, else links-- goes -ve |
235 |
* -db |
236 |
*/ |
237 |
if (*cmd == '\0' && mtree_p->msg) |
238 |
{ |
239 |
mtree_p->msg = NULL; |
240 |
mtree_p->links--; |
241 |
} |
242 |
else |
243 |
{ |
244 |
if ((ntree_p = mtree_p->pointers[*cmd & (MAXPTRLEN - 1)])) |
245 |
{ |
246 |
del_msg_element(ntree_p, cmd + 1); |
247 |
|
248 |
if (ntree_p->links == 0) |
249 |
{ |
250 |
mtree_p->pointers[*cmd & (MAXPTRLEN - 1)] = NULL; |
251 |
mtree_p->links--; |
252 |
free(ntree_p); |
253 |
} |
254 |
} |
255 |
} |
256 |
} |
257 |
|
258 |
/* msg_tree_parse() |
259 |
* |
260 |
* inputs - Pointer to command to find |
261 |
* - Pointer to MessageTree root |
262 |
* output - Find given command returning Message * if found NULL if not |
263 |
* side effects - none |
264 |
*/ |
265 |
static struct Message * |
266 |
msg_tree_parse(const char *cmd) |
267 |
{ |
268 |
struct MessageTree *mtree = &msg_tree; |
269 |
|
270 |
assert(cmd && *cmd); |
271 |
|
272 |
while (IsAlpha(*cmd) && (mtree = mtree->pointers[*cmd & (MAXPTRLEN - 1)])) |
273 |
if (*++cmd == '\0') |
274 |
return mtree->msg; |
275 |
|
276 |
return NULL; |
277 |
} |
278 |
|
279 |
void |
280 |
irc_parser_parse_line(char *msg) |
281 |
{ |
282 |
toktabptr ttab; |
283 |
|
284 |
Debug((DL_IRCMSG, "[IN]: %s", msg)); |
285 |
|
286 |
ttab = tokenize(msg, 14); |
287 |
if (ttab->size < 2) |
288 |
{ |
289 |
Debug((DL_BASIC, "BOGUS IRC MSG (%s)", ttab->tok[0])); |
290 |
RET_BOGUS; |
291 |
} |
292 |
|
293 |
if (irc_network_get_remote_server_count() > 0) |
294 |
{ |
295 |
struct Message *mptr = msg_tree_parse(ttab->tok[1]); |
296 |
|
297 |
if (mptr) |
298 |
mptr->handler(ttab); |
299 |
} |
300 |
else |
301 |
{ |
302 |
/* Our direct link only */ |
303 |
msg_bogus = 0; |
304 |
|
305 |
if (strcmp(ttab->tok[0], MSG_SERVER) == 0) |
306 |
{ |
307 |
/* 0 1 2 3 */ |
308 |
/* SERVER hades.arpa 1 :ircd-hybrid test server */ |
309 |
|
310 |
if (ttab->size < 4) |
311 |
RET_BOGUS; |
312 |
|
313 |
numyxx = yxx_to_int(ttab->tok[6]); |
314 |
irc_network_add_server(gMe.nserv, ttab->tok[1], NULL, BURSTING, 0, 0); |
315 |
|
316 |
burst_start = peak_time(); /* = time(0) */ |
317 |
} |
318 |
} |
319 |
} |
320 |
|
321 |
/* CJAAA P BjAAA :blah */ |
322 |
static void |
323 |
parse_privmsg(toktabptr ttab) |
324 |
{ |
325 |
if (ttab->size < 3) |
326 |
RET_BOGUS; |
327 |
|
328 |
/* Shouldn't receive any channel messages unless the client isn't +d */ |
329 |
if (!IsChannelName(ttab->tok[2])) |
330 |
irc_client_handle_private(ttab); |
331 |
} |
332 |
|
333 |
/* CJAAH M testouil :+w */ |
334 |
static void |
335 |
parse_mode(toktabptr ttab) |
336 |
{ |
337 |
char *mode_change; |
338 |
|
339 |
if (ttab->size <= 3) |
340 |
RET_BOGUS; |
341 |
|
342 |
mode_change = ttab->tok[3]; |
343 |
if (*mode_change == ':') |
344 |
mode_change++; |
345 |
|
346 |
/* Ignore channel modes changes. */ |
347 |
if (!IsChannelName(ttab->tok[2])) |
348 |
irc_mode_handle(ttab->tok[0], mode_change); |
349 |
} |
350 |
|
351 |
/* AB N TomSOYer 4 1070318877 soyer thorongil.arda +i DAqAFk ABAAC :So |
352 |
* CJAAA N mbu 1053984498 |
353 |
*/ |
354 |
static void |
355 |
parse_nick(toktabptr ttab) |
356 |
{ |
357 |
/* Check for nick change */ |
358 |
if (ttab->size != 4) |
359 |
RET_BOGUS; |
360 |
|
361 |
irc_userbase_nick_change(ttab->tok[0], ttab->tok[2]); |
362 |
} |
363 |
|
364 |
static void |
365 |
parse_uid(toktabptr ttab) |
366 |
{ |
367 |
if (0) |
368 |
{ |
369 |
|
370 |
} |
371 |
else |
372 |
{ |
373 |
char *mode, *ip, *nn; |
374 |
time_t firsttime; |
375 |
ClientAddr addr; |
376 |
|
377 |
if (ttab->size < 10) |
378 |
RET_BOGUS; |
379 |
|
380 |
firsttime = atoi(ttab->tok[4]); |
381 |
|
382 |
if (*ttab->tok[7] == '+') |
383 |
{ |
384 |
/* Modes ! */ |
385 |
#define ARG_MODES_NUM 2 |
386 |
const char arg_modes[ARG_MODES_NUM] = |
387 |
{ |
388 |
'r' /* undernet; account username */, |
389 |
'h' /* asuka; hostname */ |
390 |
}; |
391 |
char *p; |
392 |
int i, k = 8; |
393 |
|
394 |
for (i = 0; i < ARG_MODES_NUM; i++) |
395 |
for (p = ttab->tok[7]; *p; p++) |
396 |
if (*p == arg_modes[i]) |
397 |
k++; |
398 |
|
399 |
if (k - 8 > ARG_MODES_NUM) |
400 |
RET_BOGUS; /* doh; +rrhrhhrhrh ? */ |
401 |
|
402 |
mode = ttab->tok[7]; |
403 |
ip = ttab->tok[k]; /* base64 IP */ |
404 |
nn = ttab->tok[k + 1]; /* base64 numnick */ |
405 |
} |
406 |
else |
407 |
{ |
408 |
mode = NULL; |
409 |
ip = ttab->tok[7]; |
410 |
nn = ttab->tok[8]; |
411 |
} |
412 |
|
413 |
/* Add IPv6 support here when available. |
414 |
*/ |
415 |
if (strlen(ip) != 6) |
416 |
RET_BOGUS; |
417 |
|
418 |
addr.ip4.s_addr = htonl(base64toint(ip)); |
419 |
irc_userbase_add(ttab->tok[2], ttab->tok[5], firsttime, mode, 0, addr, nn); |
420 |
} |
421 |
|
422 |
/* 0 1 2 3 4 5 */ |
423 |
/* :0MC SID hades.arpa 2 4XY :ircd-hybrid test server */ |
424 |
static void |
425 |
parse_sid(toktabptr ttab) |
426 |
{ |
427 |
if (ttab->size < 6) |
428 |
RET_BOGUS; |
429 |
|
430 |
irc_network_add_server(ttab->tok[0], ttab->tok[2], ttab->tok[4]); |
431 |
} |
432 |
|
433 |
static void |
434 |
parse_quit(toktabptr ttab) |
435 |
{ |
436 |
irc_userbase_remove(ttab->tok[0]); |
437 |
} |
438 |
|
439 |
static void |
440 |
parse_squit(toktabptr ttab) |
441 |
{ |
442 |
if (ttab->size < 4) |
443 |
RET_BOGUS; |
444 |
|
445 |
irc_network_remove_server_name(ttab->tok[2]); |
446 |
} |
447 |
|
448 |
static void |
449 |
parse_kill(toktabptr ttab) |
450 |
{ |
451 |
if (ttab->size < 4) |
452 |
RET_BOGUS; |
453 |
|
454 |
irc_userbase_remove(ttab->tok[2]); |
455 |
} |
456 |
|
457 |
/* CJ G !1053960261.525240 proxyscan.undernet.org 1053960261.525240 |
458 |
* -> CJ G !1053966530.81669 hub5.eu.jeb.com.fr 1053966530.81669 |
459 |
* <- Bk Z Bk !1053966530.81669 1053966530.81669 95363 1053966625.445512 |
460 |
*/ |
461 |
static void |
462 |
parse_ping(toktabptr ttab) |
463 |
{ |
464 |
if (irc_network_is_my_downlink(base64toint(ttab->tok[0]))) |
465 |
send_raw(":%s PONG %s" CRLF, gMe.yy, gMe.yy); |
466 |
} |
467 |
|
468 |
static void |
469 |
parse_pong(toktabptr ttab) |
470 |
{ |
471 |
} |
472 |
|
473 |
static void |
474 |
parse_eob(toktabptr ttab) |
475 |
{ |
476 |
unsigned int yy_int = base64toint(ttab->tok[0]); |
477 |
|
478 |
irc_network_ack_end_of_burst(yy_int); |
479 |
|
480 |
/* Only reply to EB from our direct downlink. */ |
481 |
if (irc_network_is_my_downlink(base64toint(ttab->tok[0]))) |
482 |
{ |
483 |
size_t recvBytes = peak_stream_get_read_count(gIRCStream); |
484 |
|
485 |
burst_delay = peak_time(); |
486 |
burst_delay -= burst_start; |
487 |
|
488 |
send_to_console("[%s] Loaded %ld clients in %ld seconds " |
489 |
"(%ldKB - %4.2fKB/s)", |
490 |
gConfig->server.id, irc_userbase_get_count(), burst_delay, |
491 |
(int)(recvBytes/1024), |
492 |
burst_delay ? (recvBytes/1024.)/burst_delay |
493 |
: recvBytes/1024.); |
494 |
} |
495 |
} |
496 |
|
497 |
static struct Message parse_msgtab[] = |
498 |
{ |
499 |
{ "EOB", 0, 0, 0, MAXPARA, 0, 0, parse_eob }, |
500 |
{ "KILL", 0, 0, 0, MAXPARA, 0, 0, parse_kill }, |
501 |
{ "MODE", 0, 0, 0, MAXPARA, 0, 0, parse_mode }, |
502 |
{ "NICK", 0, 0, 0, MAXPARA, 0, 0, parse_nick }, |
503 |
{ "PING", 0, 0, 0, MAXPARA, 0, 0, parse_ping }, |
504 |
{ "PONG", 0, 0, 0, MAXPARA, 0, 0, parse_pong }, |
505 |
{ "PRIVMSG", 0, 0, 0, MAXPARA, 0, 0, parse_privmsg }, |
506 |
{ "QUIT", 0, 0, 0, MAXPARA, 0, 0, parse_quit }, |
507 |
{ "SERVER", 0, 0, 0, MAXPARA, 0, 0, parse_server }, |
508 |
{ "SID", 0, 0, 0, MAXPARA, 0, 0, parse_sid }, |
509 |
{ "SQUIT", 0, 0, 0, MAXPARA, 0, 0, parse_squit }, |
510 |
{ "UID", 0, 0, 0, MAXPARA, 0, 0, parse_uid }, |
511 |
{ NULL, 0, 0, 0, 0, 0, 0, NULL } |
512 |
}; |
513 |
|
514 |
void |
515 |
irc_parser_init(void) |
516 |
{ |
517 |
struct Message *tab = parse_msgtab; |
518 |
|
519 |
tokenizer_init(); |
520 |
|
521 |
for (; tab->handler; ++tab) |
522 |
add_msg_element(&msg_tree, tab, tab->cmd); |
523 |
} |