1 |
/* |
2 |
* ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd). |
3 |
* m_spoof.c: Supports dynamic auth{} creation/deletion. |
4 |
* |
5 |
* Copyright (C) 2002 by the past and present ircd coders, and others. |
6 |
* |
7 |
* This program is free software; you can redistribute it and/or modify |
8 |
* it under the terms of the GNU General Public License as published by |
9 |
* the Free Software Foundation; either version 2 of the License, or |
10 |
* (at your option) any later version. |
11 |
* |
12 |
* This program is distributed in the hope that it will be useful, |
13 |
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
* GNU General Public License for more details. |
16 |
* |
17 |
* You should have received a copy of the GNU General Public License |
18 |
* along with this program; if not, write to the Free Software |
19 |
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
20 |
* USA |
21 |
* |
22 |
* $Id$ |
23 |
*/ |
24 |
|
25 |
/* MODULE CONFIGURATION FOLLOWS -- please read!! */ |
26 |
|
27 |
/* |
28 |
* change to #define if you want to propagate received SPOOF/DELSPOOF messages |
29 |
* to other servers. This allows you create subnets inside which spoofs are |
30 |
* propagated. By manipulating PROPAGATE_SPOOF and RECEIVE_SPOOF, you can |
31 |
* prepare boundary hubs of such subnets. |
32 |
* |
33 |
* I realize a shared{} could be better, but I don't want to touch core code. |
34 |
* |
35 |
* If you decide to enable this, remember to load m_spoof on all servers |
36 |
* I am connected to, or you'll get plenty of "Unknown command" errors... |
37 |
*/ |
38 |
#undef PROPAGATE_SPOOF |
39 |
|
40 |
/* |
41 |
* this server is allowed to receive spoofs/delspoofs from other servers. |
42 |
* Use in conjunction with PROPAGATE_SPOOF (on target servers). |
43 |
*/ |
44 |
#undef RECEIVE_SPOOF |
45 |
|
46 |
/* where to put dynamic auth's -- this must be included from ircd.conf! |
47 |
* Ideally put .include "spoof.conf" before all other auths. |
48 |
* #undef if you want only a propagating hub server, not storing any data */ |
49 |
#define SPOOF_FILE "etc/spoof.conf" |
50 |
|
51 |
/* disable if you don't want opers notices/logs */ |
52 |
#define LOG_SPOOF |
53 |
|
54 |
|
55 |
/* END OF MODULE CONFIGURATION */ |
56 |
|
57 |
/* Usage: SPOOF <umask@hmask> <free.form.spoof|-> [flags|- [password]] |
58 |
* -- Appends an auth{} block. Flags consist of characters: |
59 |
* t (no_tilde), i (need_ident), k (kline_exempt), |
60 |
* g (gline_exempt), l (exceed_limit), o (class = "opers"), |
61 |
* f (can_flood), p (need_password), everything other is ignored. |
62 |
* DELSPOOF <umask@hmask> |
63 |
* -- Removes an auth{} block of exact umask@hmask, if found |
64 |
* |
65 |
* These commands are restricted to admins, so make sure your oper{} block |
66 |
* has admin = yes or so. |
67 |
*/ |
68 |
|
69 |
#if !defined(PROPAGATE_SPOOF) && !defined(SPOOF_FILE) |
70 |
#error You disabled both SPOOF_FILE and PROPAGATE_SPOOF, what do you expect me to do? |
71 |
#endif |
72 |
|
73 |
/* List of ircd includes from ../include/ */ |
74 |
#include "stdinc.h" |
75 |
#include "tools.h" |
76 |
#include "handlers.h" |
77 |
#include "client.h" |
78 |
#include "common.h" /* FALSE bleah */ |
79 |
#include "hash.h" |
80 |
#include "hostmask.h" |
81 |
#include "ircd.h" |
82 |
#include "irc_string.h" |
83 |
#include "sprintf_irc.h" |
84 |
#include "numeric.h" |
85 |
#include "fdlist.h" |
86 |
#include "s_bsd.h" |
87 |
#include "s_conf.h" |
88 |
#include "s_log.h" |
89 |
#include "s_serv.h" |
90 |
#include "send.h" |
91 |
#include "msg.h" |
92 |
#include "parse.h" |
93 |
#include "modules.h" |
94 |
|
95 |
static void mo_spoof(struct Client *, struct Client *, int, char *[]); |
96 |
static void mo_delspoof(struct Client *, struct Client *, int, char *[]); |
97 |
|
98 |
struct Message spoof_msgtab = { |
99 |
"SPOOF", 0, 0, 3, 0, MFLG_SLOW, 0, |
100 |
#ifdef RECEIVE_SPOOF |
101 |
{m_unregistered, m_not_oper, mo_spoof, m_ignore, mo_spoof, m_ignore} |
102 |
#else |
103 |
{m_unregistered, m_not_oper, m_ignore, m_ignore, mo_spoof, m_ignore} |
104 |
#endif |
105 |
}; |
106 |
|
107 |
struct Message delspoof_msgtab = { |
108 |
"DELSPOOF", 0, 0, 1, 0, MFLG_SLOW, 0, |
109 |
#ifdef RECEIVE_SPOOF |
110 |
{m_unregistered, m_not_oper, mo_delspoof, m_ignore, mo_delspoof, m_ignore} |
111 |
#else |
112 |
{m_unregistered, m_not_oper, m_ignore, m_ignore, mo_delspoof, m_ignore} |
113 |
#endif |
114 |
}; |
115 |
|
116 |
#ifndef STATIC_MODULES |
117 |
void |
118 |
_modinit(void) |
119 |
{ |
120 |
mod_add_cmd(&spoof_msgtab); |
121 |
mod_add_cmd(&delspoof_msgtab); |
122 |
} |
123 |
|
124 |
void |
125 |
_moddeinit(void) |
126 |
{ |
127 |
mod_del_cmd(&delspoof_msgtab); |
128 |
mod_del_cmd(&spoof_msgtab); |
129 |
} |
130 |
|
131 |
const char *_version = "$Revision$"; |
132 |
#endif |
133 |
|
134 |
#ifdef SPOOF_FILE |
135 |
static void |
136 |
try_flag(FBFILE *f, int *flags, int flag, const char *string) |
137 |
{ |
138 |
if ((*flags & flag)) |
139 |
{ |
140 |
fbputs(string, f, strlen(string)); |
141 |
|
142 |
*flags &= ~flag; |
143 |
fbputs(*flags ? ", " : ";\n", f, 2); |
144 |
} |
145 |
} |
146 |
#endif |
147 |
|
148 |
static void |
149 |
mo_spoof(struct Client *client_p, struct Client *source_p, |
150 |
int parc, char *parv[]) |
151 |
{ |
152 |
char *host, *spoof, *password; |
153 |
const char *tmp = NULL; |
154 |
const char *user = NULL; |
155 |
const char *flags = NULL; |
156 |
int i = 0; |
157 |
#ifdef SPOOF_FILE |
158 |
int class_opers; |
159 |
FBFILE *f; |
160 |
char buffer[1024]; |
161 |
struct AddressRec *arec; |
162 |
#endif |
163 |
|
164 |
if (MyConnect(source_p) && !IsOperAdmin(source_p)) |
165 |
{ |
166 |
sendto_one(source_p, form_str(ERR_NOPRIVS), |
167 |
me.name, source_p->name, "SPOOF"); |
168 |
return; |
169 |
} |
170 |
|
171 |
/* check the user@host mask */ |
172 |
if (strchr(parv[1], '!') != NULL) |
173 |
{ |
174 |
syntax: |
175 |
if (MyConnect(source_p)) |
176 |
sendto_one(source_p, ":%s NOTICE %s :Syntax: SPOOF <umask@hmask> " |
177 |
"<spoof/-> [flags/- [password]]", me.name, source_p->name); |
178 |
return; |
179 |
} |
180 |
|
181 |
(void) collapse(parv[1]); |
182 |
|
183 |
for (tmp = parv[1]; *tmp; tmp++) |
184 |
if (!IsKWildChar(*tmp)) |
185 |
if (++i >= ConfigFileEntry.min_nonwildcard) |
186 |
break; |
187 |
if (i < ConfigFileEntry.min_nonwildcard) |
188 |
{ |
189 |
if (MyConnect(source_p)) |
190 |
sendto_one(source_p, ":%s NOTICE %s :Not enough non-wildcard characters " |
191 |
"in user@host mask", |
192 |
me.name, source_p->name); |
193 |
return; |
194 |
} |
195 |
|
196 |
host = strchr(parv[1], '@'); |
197 |
if (host) |
198 |
{ |
199 |
user = parv[1]; |
200 |
*host = '\0'; |
201 |
host++; |
202 |
} |
203 |
else |
204 |
{ |
205 |
user = "*"; |
206 |
host = parv[1]; |
207 |
} |
208 |
|
209 |
/* check the spoof field */ |
210 |
spoof = parv[2]; |
211 |
if (spoof == NULL || !*spoof) |
212 |
goto syntax; |
213 |
|
214 |
if (spoof[0] != '-' || spoof[1] != '\0') |
215 |
{ |
216 |
for (tmp = spoof; *tmp; tmp++) |
217 |
if (!IsHostChar(*tmp)) { |
218 |
if (MyConnect(source_p)) |
219 |
sendto_one(source_p, ":%s NOTICE %s :The spoof [%s] is invalid", |
220 |
me.name, source_p->name, spoof); |
221 |
return; |
222 |
} |
223 |
if (strlen(spoof) >= HOSTLEN) { |
224 |
if (MyConnect(source_p)) |
225 |
sendto_one(source_p, ":%s NOTICE %s :Spoofs must be less than %d.." |
226 |
"ignoring it", me.name, source_p->name, HOSTLEN); |
227 |
return; |
228 |
} |
229 |
} |
230 |
|
231 |
flags = (parc > 3) ? parv[3] : "-"; |
232 |
password = (parc > 4 && parv[4][0]) ? parv[4] : NULL; |
233 |
|
234 |
#ifdef PROPAGATE_SPOOF |
235 |
sendto_server(client_p, source_p, NULL, NOCAPS, NOCAPS, LL_ICLIENT, |
236 |
":%s SPOOF %s@%s %s %s :%s", |
237 |
source_p->name, user, host, spoof, flags, password ? password : ""); |
238 |
#endif |
239 |
|
240 |
#ifdef SPOOF_FILE |
241 |
/* Walk through auth {} items and check if we have another auth block |
242 |
* for this hostname */ |
243 |
for (i = 0; i < ATABLE_SIZE; i++) |
244 |
for (arec = atable[i]; arec; arec = arec->next) |
245 |
if (arec->type == CONF_CLIENT && !irccmp(arec->aconf->host, host) && |
246 |
!irccmp(arec->aconf->user, user)) |
247 |
{ |
248 |
/* auth entry already exists */ |
249 |
if (MyConnect(source_p)) |
250 |
sendto_one(source_p, |
251 |
":%s NOTICE %s :auth for %s@%s already exists, you need " |
252 |
"to use /DELSPOOF first", me.name, source_p->name, user, host); |
253 |
#ifdef LOG_SPOOF |
254 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
255 |
"%s attemped to re-add auth for %s@%s " |
256 |
"[spoof: %s, flags: %s]", source_p->name, user, host, |
257 |
spoof, flags); |
258 |
#endif |
259 |
return; |
260 |
} |
261 |
|
262 |
/* Add the spoof to the the spoof file */ |
263 |
if ((f = fbopen(SPOOF_FILE, "a")) == NULL) |
264 |
{ |
265 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
266 |
"Could not open %s file, auth for %s@%s " |
267 |
"[spoof: %s, flags: %s, requested by %s] not added", |
268 |
SPOOF_FILE, user, host, spoof, flags, source_p->name); |
269 |
return; |
270 |
} |
271 |
|
272 |
/* write the auth {} block */ |
273 |
fbputs("auth {\n", f, 7); |
274 |
i = ircsprintf(buffer, "\tuser = \"%s@%s\";\n", user, host); |
275 |
fbputs(buffer, f, i); |
276 |
if (spoof[0] != '-' || spoof[1] != '\0') |
277 |
{ |
278 |
i = ircsprintf(buffer, "\tspoof = \"%s\";\n", spoof); |
279 |
fbputs(buffer, f, i); |
280 |
} |
281 |
if (password) |
282 |
{ |
283 |
i = ircsprintf(buffer, "\tpassword = \"%s\";\n", password); |
284 |
fbputs(buffer, f, i); |
285 |
} |
286 |
|
287 |
/* process given flags */ |
288 |
i = class_opers = 0; |
289 |
for (tmp = flags; *tmp; ++tmp) |
290 |
switch (*tmp) |
291 |
{ |
292 |
case 't': i |= CONF_FLAGS_NO_TILDE; /* no_tilde = yes; */ |
293 |
break; |
294 |
case 'i': i |= CONF_FLAGS_NEED_IDENTD; /* need_ident = yes; */ |
295 |
break; |
296 |
case 'k': i |= CONF_FLAGS_EXEMPTKLINE; /* kline_exempt = yes; */ |
297 |
break; |
298 |
case 'g': i |= CONF_FLAGS_EXEMPTGLINE; /* gline_exempt = yes; */ |
299 |
break; |
300 |
case 'l': i |= CONF_FLAGS_NOLIMIT; /* exceed_limit = yes; */ |
301 |
break; |
302 |
case 'o': class_opers = 1; /* class = "opers"; */ |
303 |
break; |
304 |
case 'f': i |= CONF_FLAGS_CAN_FLOOD; /* can_flood = yes; */ |
305 |
break; |
306 |
case 'p': i|= CONF_FLAGS_NEED_PASSWORD; /* need_password = yes; */ |
307 |
} |
308 |
|
309 |
if (i) |
310 |
{ |
311 |
fbputs("\tflags = ", f, 9); |
312 |
try_flag(f, &i, CONF_FLAGS_NO_TILDE, "no_tilde"); |
313 |
try_flag(f, &i, CONF_FLAGS_NEED_IDENTD, "need_ident"); |
314 |
try_flag(f, &i, CONF_FLAGS_EXEMPTKLINE, "kline_exempt"); |
315 |
try_flag(f, &i, CONF_FLAGS_EXEMPTGLINE, "gline_exempt"); |
316 |
try_flag(f, &i, CONF_FLAGS_NOLIMIT, "exceed_limit"); |
317 |
try_flag(f, &i, CONF_FLAGS_CAN_FLOOD, "can_flood"); |
318 |
try_flag(f, &i, CONF_FLAGS_NEED_PASSWORD, "need_password"); |
319 |
} |
320 |
|
321 |
if (class_opers) |
322 |
fbputs("\tclass = \"opers\";\n", f, 18); |
323 |
else |
324 |
fbputs("\tclass = \"users\";\n", f, 18); |
325 |
|
326 |
fbputs("};\n\n", f, 4); |
327 |
fbclose(f); |
328 |
|
329 |
rehash(0); |
330 |
#endif |
331 |
|
332 |
#ifdef LOG_SPOOF |
333 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
334 |
"%s added auth for %s@%s [spoof: %s, flags: %s]", |
335 |
source_p->name, user, host, spoof, flags); |
336 |
ilog(L_TRACE, "%s added auth for %s@%s [spoof: %s, flags: %s]", |
337 |
source_p->name, user, host, spoof, flags); |
338 |
#endif |
339 |
} |
340 |
|
341 |
/* Now, our job is a bit harder. I will scan through the SPOOF_FILE |
342 |
* and read all auths{} (assuming they are written in our line formatting..), |
343 |
* then rewrite them skipping the one to delete. --adx */ |
344 |
static void |
345 |
mo_delspoof(struct Client *client_p, struct Client *source_p, |
346 |
int parc, char *parv[]) |
347 |
{ |
348 |
#ifdef SPOOF_FILE |
349 |
FBFILE *f, *fout; |
350 |
int ignore_it = 1, spoof_found = 0; |
351 |
char buffer[1024], *tmp; |
352 |
#endif |
353 |
const char *user = NULL; |
354 |
char *host = NULL; |
355 |
|
356 |
if (MyConnect(source_p) && !IsOperAdmin(source_p)) |
357 |
{ |
358 |
sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, parv[0], "DELSPOOF"); |
359 |
return; |
360 |
} |
361 |
|
362 |
if (parv[1] == NULL || !*parv[1]) |
363 |
{ |
364 |
if (MyConnect(source_p)) |
365 |
sendto_one(source_p, ":%s NOTICE %s :Syntax: /DELSPOOF <user@host>", |
366 |
me.name, source_p->name); |
367 |
return; |
368 |
} |
369 |
|
370 |
/* check user@host mask */ |
371 |
(void) collapse(parv[1]); |
372 |
|
373 |
host = strchr(parv[1], '@'); |
374 |
if (host != NULL) |
375 |
{ |
376 |
user = parv[1]; |
377 |
*host = '\0'; |
378 |
host++; |
379 |
} |
380 |
else |
381 |
{ |
382 |
user = "*"; |
383 |
host = parv[1]; |
384 |
} |
385 |
|
386 |
#ifdef PROPAGATE_SPOOF |
387 |
sendto_server(client_p, source_p, NULL, NOCAPS, NOCAPS, LL_ICLIENT, |
388 |
":%s DELSPOOF %s@%s", source_p->name, user, host); |
389 |
#endif |
390 |
|
391 |
#ifdef SPOOF_FILE |
392 |
if ((f = fbopen(SPOOF_FILE, "r")) == NULL) |
393 |
{ |
394 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
395 |
"Could not open %s file, auth for %s@%s not deleted " |
396 |
"(requested by %s)", |
397 |
SPOOF_FILE, user, host, source_p->name); |
398 |
return; |
399 |
} |
400 |
|
401 |
if ((fout = fbopen(SPOOF_FILE ".new", "w")) == NULL) |
402 |
{ |
403 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
404 |
"Could not create %s.new file, auth for %s@%s not " |
405 |
"deleted (requested by %s)", |
406 |
SPOOF_FILE, user, host, source_p->name); |
407 |
return; |
408 |
} |
409 |
|
410 |
while (fbgets(buffer, 1024, f)) |
411 |
{ |
412 |
if (!ircncmp(buffer, "auth {", 6)) |
413 |
{ |
414 |
/* don't process it yet.. we have to check whether the user="..."; field |
415 |
* matches the user@host mask which is being deleted |
416 |
*/ |
417 |
ignore_it = 1; |
418 |
continue; |
419 |
} |
420 |
|
421 |
/* a simple parser substitute... */ |
422 |
for (tmp = buffer; *tmp == '\t' || *tmp == ' '; tmp++) |
423 |
; |
424 |
if (!ircncmp(tmp, "user", 4)) |
425 |
{ |
426 |
for (tmp += 4; *tmp == '\t' || *tmp == ' '; tmp++) |
427 |
; |
428 |
if (*tmp == '=') { |
429 |
for (++tmp; *tmp == '\t' || *tmp == ' '; tmp++) |
430 |
; |
431 |
if (*tmp == '\"') |
432 |
{ |
433 |
/* yuppi, we've just reached the user="..."; field */ |
434 |
int matches; |
435 |
char *tmp2 = strchr(++tmp, '\"'); |
436 |
|
437 |
if (tmp2 != NULL) |
438 |
*tmp2 = '\0'; |
439 |
tmp2 = strchr(tmp, '@'); |
440 |
|
441 |
/* is it matching our mask? */ |
442 |
if (tmp2 == NULL) |
443 |
matches = !irccmp(user, "*") && !irccmp(host, tmp); |
444 |
else |
445 |
{ |
446 |
*tmp2++ = '\0'; |
447 |
matches = !irccmp(user, tmp) && !irccmp(host, tmp2); |
448 |
} |
449 |
|
450 |
if (!matches) |
451 |
{ |
452 |
/* no.. so leave it unchanged */ |
453 |
if (ignore_it) |
454 |
{ |
455 |
ignore_it = 0; |
456 |
fbputs("auth {\n", fout, 7); |
457 |
/* user="..." should be the first field in the auth {}; block, |
458 |
* otherwise we could have problems... |
459 |
*/ |
460 |
} |
461 |
|
462 |
fbputs("\tuser = \"", fout, 9); |
463 |
if (tmp2 == NULL) |
464 |
fbputs("*", fout, 1); |
465 |
else |
466 |
fbputs(tmp, fout, strlen(tmp)); |
467 |
fbputs("@", fout, 1); |
468 |
fbputs(tmp2, fout, strlen(tmp2)); |
469 |
fbputs("\";\n", fout, 3); |
470 |
} |
471 |
else |
472 |
{ |
473 |
/* we've got it! - omit and continue working */ |
474 |
spoof_found = 1; |
475 |
} |
476 |
|
477 |
continue; |
478 |
} |
479 |
} |
480 |
} |
481 |
|
482 |
if (!ignore_it) |
483 |
fbputs(buffer, fout, strlen(buffer)); |
484 |
} |
485 |
|
486 |
fbclose(f); |
487 |
fbclose(fout); |
488 |
|
489 |
if (!spoof_found) |
490 |
{ |
491 |
if (MyConnect(source_p)) |
492 |
sendto_one(source_p, ":%s NOTICE %s :No auth for %s@%s found", |
493 |
me.name, source_p->name, user, host); |
494 |
unlink(SPOOF_FILE ".new"); |
495 |
return; |
496 |
} |
497 |
|
498 |
unlink(SPOOF_FILE); |
499 |
rename(SPOOF_FILE ".new", SPOOF_FILE); |
500 |
rehash(0); |
501 |
#endif |
502 |
|
503 |
#ifdef LOG_SPOOF |
504 |
sendto_realops_flags(UMODE_ALL, L_ALL, "%s deleted auth for %s@%s", |
505 |
source_p->name, user, host); |
506 |
#endif |
507 |
} |