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 "handlers.h" |
76 |
#include "client.h" |
77 |
#include "common.h" /* FALSE bleah */ |
78 |
#include "hash.h" |
79 |
#include "hostmask.h" |
80 |
#include "ircd.h" |
81 |
#include "numeric.h" |
82 |
#include "s_serv.h" |
83 |
#include "send.h" |
84 |
#include "msg.h" |
85 |
#include "parse.h" |
86 |
#include "conf/modules.h" |
87 |
|
88 |
static void mo_spoof(struct Client *, struct Client *, int, char *[]); |
89 |
static void mo_delspoof(struct Client *, struct Client *, int, char *[]); |
90 |
|
91 |
struct Message spoof_msgtab = { |
92 |
"SPOOF", 0, 0, 3, 0, MFLG_SLOW, 0, |
93 |
#ifdef RECEIVE_SPOOF |
94 |
{m_unregistered, m_not_oper, mo_spoof, m_ignore, mo_spoof, m_ignore} |
95 |
#else |
96 |
{m_unregistered, m_not_oper, m_ignore, m_ignore, mo_spoof, m_ignore} |
97 |
#endif |
98 |
}; |
99 |
|
100 |
struct Message delspoof_msgtab = { |
101 |
"DELSPOOF", 0, 0, 1, 0, MFLG_SLOW, 0, |
102 |
#ifdef RECEIVE_SPOOF |
103 |
{m_unregistered, m_not_oper, mo_delspoof, m_ignore, mo_delspoof, m_ignore} |
104 |
#else |
105 |
{m_unregistered, m_not_oper, m_ignore, m_ignore, mo_delspoof, m_ignore} |
106 |
#endif |
107 |
}; |
108 |
|
109 |
INIT_MODULE(m_spoof, "$Revision$") |
110 |
{ |
111 |
mod_add_cmd(&spoof_msgtab); |
112 |
mod_add_cmd(&delspoof_msgtab); |
113 |
} |
114 |
|
115 |
CLEANUP_MODULE |
116 |
{ |
117 |
mod_del_cmd(&delspoof_msgtab); |
118 |
mod_del_cmd(&spoof_msgtab); |
119 |
} |
120 |
|
121 |
#ifdef SPOOF_FILE |
122 |
static void |
123 |
try_flag(FBFILE *f, int *flags, int flag, const char *string) |
124 |
{ |
125 |
if ((*flags & flag)) |
126 |
{ |
127 |
fbputs(string, f, strlen(string)); |
128 |
|
129 |
*flags &= ~flag; |
130 |
fbputs(*flags ? ", " : ";\n", f, 2); |
131 |
} |
132 |
} |
133 |
#endif |
134 |
|
135 |
static void |
136 |
mo_spoof(struct Client *client_p, struct Client *source_p, |
137 |
int parc, char *parv[]) |
138 |
{ |
139 |
char *host, *spoof, *password; |
140 |
const char *tmp = NULL; |
141 |
const char *user = NULL; |
142 |
const char *flags = NULL; |
143 |
int i = 0; |
144 |
#ifdef SPOOF_FILE |
145 |
int class_opers; |
146 |
FBFILE *f; |
147 |
char buffer[1024]; |
148 |
struct AddressRec *arec; |
149 |
#endif |
150 |
|
151 |
if (MyConnect(source_p) && !IsOperAdmin(source_p)) |
152 |
{ |
153 |
sendto_one(source_p, form_str(ERR_NOPRIVS), |
154 |
me.name, source_p->name, "SPOOF"); |
155 |
return; |
156 |
} |
157 |
|
158 |
/* check the user@host mask */ |
159 |
if (strchr(parv[1], '!') != NULL) |
160 |
{ |
161 |
syntax: |
162 |
if (MyConnect(source_p)) |
163 |
sendto_one(source_p, ":%s NOTICE %s :Syntax: SPOOF <umask@hmask> " |
164 |
"<spoof/-> [flags/- [password]]", me.name, source_p->name); |
165 |
return; |
166 |
} |
167 |
|
168 |
(void) collapse(parv[1]); |
169 |
|
170 |
for (tmp = parv[1]; *tmp; tmp++) |
171 |
if (!IsKWildChar(*tmp)) |
172 |
if (++i >= ConfigFileEntry.min_nonwildcard) |
173 |
break; |
174 |
if (i < ConfigFileEntry.min_nonwildcard) |
175 |
{ |
176 |
if (MyConnect(source_p)) |
177 |
sendto_one(source_p, ":%s NOTICE %s :Not enough non-wildcard characters " |
178 |
"in user@host mask", |
179 |
me.name, source_p->name); |
180 |
return; |
181 |
} |
182 |
|
183 |
host = strchr(parv[1], '@'); |
184 |
if (host) |
185 |
{ |
186 |
user = parv[1]; |
187 |
*host = '\0'; |
188 |
host++; |
189 |
} |
190 |
else |
191 |
{ |
192 |
user = "*"; |
193 |
host = parv[1]; |
194 |
} |
195 |
|
196 |
/* check the spoof field */ |
197 |
spoof = parv[2]; |
198 |
if (spoof == NULL || !*spoof) |
199 |
goto syntax; |
200 |
|
201 |
if (spoof[0] != '-' || spoof[1] != '\0') |
202 |
{ |
203 |
for (tmp = spoof; *tmp; tmp++) |
204 |
if (!IsHostChar(*tmp)) { |
205 |
if (MyConnect(source_p)) |
206 |
sendto_one(source_p, ":%s NOTICE %s :The spoof [%s] is invalid", |
207 |
me.name, source_p->name, spoof); |
208 |
return; |
209 |
} |
210 |
if (strlen(spoof) >= HOSTLEN) { |
211 |
if (MyConnect(source_p)) |
212 |
sendto_one(source_p, ":%s NOTICE %s :Spoofs must be less than %d.." |
213 |
"ignoring it", me.name, source_p->name, HOSTLEN); |
214 |
return; |
215 |
} |
216 |
} |
217 |
|
218 |
flags = (parc > 3) ? parv[3] : "-"; |
219 |
password = (parc > 4 && parv[4][0]) ? parv[4] : NULL; |
220 |
|
221 |
#ifdef PROPAGATE_SPOOF |
222 |
sendto_server(client_p, source_p, NULL, NOCAPS, NOCAPS, |
223 |
":%s SPOOF %s@%s %s %s :%s", |
224 |
source_p->name, user, host, spoof, flags, password ? password : ""); |
225 |
#endif |
226 |
|
227 |
#ifdef SPOOF_FILE |
228 |
/* Walk through auth {} items and check if we have another auth block |
229 |
* for this hostname */ |
230 |
for (i = 0; i < ATABLE_SIZE; i++) |
231 |
for (arec = atable[i]; arec; arec = arec->next) |
232 |
if (arec->type == CONF_CLIENT && !irccmp(arec->aconf->host, host) && |
233 |
!irccmp(arec->aconf->user, user)) |
234 |
{ |
235 |
/* auth entry already exists */ |
236 |
if (MyConnect(source_p)) |
237 |
sendto_one(source_p, |
238 |
":%s NOTICE %s :auth for %s@%s already exists, you need " |
239 |
"to use /DELSPOOF first", me.name, source_p->name, user, host); |
240 |
#ifdef LOG_SPOOF |
241 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
242 |
"%s attemped to re-add auth for %s@%s " |
243 |
"[spoof: %s, flags: %s]", source_p->name, user, host, |
244 |
spoof, flags); |
245 |
#endif |
246 |
return; |
247 |
} |
248 |
|
249 |
/* Add the spoof to the the spoof file */ |
250 |
if ((f = fbopen(SPOOF_FILE, "a")) == NULL) |
251 |
{ |
252 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
253 |
"Could not open %s file, auth for %s@%s " |
254 |
"[spoof: %s, flags: %s, requested by %s] not added", |
255 |
SPOOF_FILE, user, host, spoof, flags, source_p->name); |
256 |
return; |
257 |
} |
258 |
|
259 |
/* write the auth {} block */ |
260 |
fbputs("auth {\n", f, 7); |
261 |
i = ircsprintf(buffer, "\tuser = \"%s@%s\";\n", user, host); |
262 |
fbputs(buffer, f, i); |
263 |
if (spoof[0] != '-' || spoof[1] != '\0') |
264 |
{ |
265 |
i = ircsprintf(buffer, "\tspoof = \"%s\";\n", spoof); |
266 |
fbputs(buffer, f, i); |
267 |
} |
268 |
if (password) |
269 |
{ |
270 |
i = ircsprintf(buffer, "\tpassword = \"%s\";\n", password); |
271 |
fbputs(buffer, f, i); |
272 |
} |
273 |
|
274 |
/* process given flags */ |
275 |
i = class_opers = 0; |
276 |
for (tmp = flags; *tmp; ++tmp) |
277 |
switch (*tmp) |
278 |
{ |
279 |
case 't': i |= CONF_FLAGS_NO_TILDE; /* no_tilde = yes; */ |
280 |
break; |
281 |
case 'i': i |= CONF_FLAGS_NEED_IDENTD; /* need_ident = yes; */ |
282 |
break; |
283 |
case 'k': i |= CONF_FLAGS_EXEMPTKLINE; /* kline_exempt = yes; */ |
284 |
break; |
285 |
case 'g': i |= CONF_FLAGS_EXEMPTGLINE; /* gline_exempt = yes; */ |
286 |
break; |
287 |
case 'l': i |= CONF_FLAGS_NOLIMIT; /* exceed_limit = yes; */ |
288 |
break; |
289 |
case 'o': class_opers = 1; /* class = "opers"; */ |
290 |
break; |
291 |
case 'f': i |= CONF_FLAGS_CAN_FLOOD; /* can_flood = yes; */ |
292 |
break; |
293 |
case 'p': i|= CONF_FLAGS_NEED_PASSWORD; /* need_password = yes; */ |
294 |
} |
295 |
|
296 |
if (i) |
297 |
{ |
298 |
fbputs("\tflags = ", f, 9); |
299 |
try_flag(f, &i, CONF_FLAGS_NO_TILDE, "no_tilde"); |
300 |
try_flag(f, &i, CONF_FLAGS_NEED_IDENTD, "need_ident"); |
301 |
try_flag(f, &i, CONF_FLAGS_EXEMPTKLINE, "kline_exempt"); |
302 |
try_flag(f, &i, CONF_FLAGS_EXEMPTGLINE, "gline_exempt"); |
303 |
try_flag(f, &i, CONF_FLAGS_NOLIMIT, "exceed_limit"); |
304 |
try_flag(f, &i, CONF_FLAGS_CAN_FLOOD, "can_flood"); |
305 |
try_flag(f, &i, CONF_FLAGS_NEED_PASSWORD, "need_password"); |
306 |
} |
307 |
|
308 |
if (class_opers) |
309 |
fbputs("\tclass = \"opers\";\n", f, 18); |
310 |
else |
311 |
fbputs("\tclass = \"users\";\n", f, 18); |
312 |
|
313 |
fbputs("};\n\n", f, 4); |
314 |
fbclose(f); |
315 |
|
316 |
rehash(0); |
317 |
#endif |
318 |
|
319 |
#ifdef LOG_SPOOF |
320 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
321 |
"%s added auth for %s@%s [spoof: %s, flags: %s]", |
322 |
source_p->name, user, host, spoof, flags); |
323 |
ilog(L_TRACE, "%s added auth for %s@%s [spoof: %s, flags: %s]", |
324 |
source_p->name, user, host, spoof, flags); |
325 |
#endif |
326 |
} |
327 |
|
328 |
/* Now, our job is a bit harder. I will scan through the SPOOF_FILE |
329 |
* and read all auths{} (assuming they are written in our line formatting..), |
330 |
* then rewrite them skipping the one to delete. --adx */ |
331 |
static void |
332 |
mo_delspoof(struct Client *client_p, struct Client *source_p, |
333 |
int parc, char *parv[]) |
334 |
{ |
335 |
#ifdef SPOOF_FILE |
336 |
FBFILE *f, *fout; |
337 |
int ignore_it = 1, spoof_found = 0; |
338 |
char buffer[1024], *tmp; |
339 |
#endif |
340 |
const char *user = NULL; |
341 |
char *host = NULL; |
342 |
|
343 |
if (MyConnect(source_p) && !IsOperAdmin(source_p)) |
344 |
{ |
345 |
sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, parv[0], "DELSPOOF"); |
346 |
return; |
347 |
} |
348 |
|
349 |
if (parv[1] == NULL || !*parv[1]) |
350 |
{ |
351 |
if (MyConnect(source_p)) |
352 |
sendto_one(source_p, ":%s NOTICE %s :Syntax: /DELSPOOF <user@host>", |
353 |
me.name, source_p->name); |
354 |
return; |
355 |
} |
356 |
|
357 |
/* check user@host mask */ |
358 |
(void) collapse(parv[1]); |
359 |
|
360 |
host = strchr(parv[1], '@'); |
361 |
if (host != NULL) |
362 |
{ |
363 |
user = parv[1]; |
364 |
*host = '\0'; |
365 |
host++; |
366 |
} |
367 |
else |
368 |
{ |
369 |
user = "*"; |
370 |
host = parv[1]; |
371 |
} |
372 |
|
373 |
#ifdef PROPAGATE_SPOOF |
374 |
sendto_server(client_p, source_p, NULL, NOCAPS, NOCAPS, |
375 |
":%s DELSPOOF %s@%s", source_p->name, user, host); |
376 |
#endif |
377 |
|
378 |
#ifdef SPOOF_FILE |
379 |
if ((f = fbopen(SPOOF_FILE, "r")) == NULL) |
380 |
{ |
381 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
382 |
"Could not open %s file, auth for %s@%s not deleted " |
383 |
"(requested by %s)", |
384 |
SPOOF_FILE, user, host, source_p->name); |
385 |
return; |
386 |
} |
387 |
|
388 |
if ((fout = fbopen(SPOOF_FILE ".new", "w")) == NULL) |
389 |
{ |
390 |
sendto_realops_flags(UMODE_ALL, L_ALL, |
391 |
"Could not create %s.new file, auth for %s@%s not " |
392 |
"deleted (requested by %s)", |
393 |
SPOOF_FILE, user, host, source_p->name); |
394 |
return; |
395 |
} |
396 |
|
397 |
while (fbgets(buffer, 1024, f)) |
398 |
{ |
399 |
if (!ircncmp(buffer, "auth {", 6)) |
400 |
{ |
401 |
/* don't process it yet.. we have to check whether the user="..."; field |
402 |
* matches the user@host mask which is being deleted |
403 |
*/ |
404 |
ignore_it = 1; |
405 |
continue; |
406 |
} |
407 |
|
408 |
/* a simple parser substitute... */ |
409 |
for (tmp = buffer; *tmp == '\t' || *tmp == ' '; tmp++) |
410 |
; |
411 |
if (!ircncmp(tmp, "user", 4)) |
412 |
{ |
413 |
for (tmp += 4; *tmp == '\t' || *tmp == ' '; tmp++) |
414 |
; |
415 |
if (*tmp == '=') { |
416 |
for (++tmp; *tmp == '\t' || *tmp == ' '; tmp++) |
417 |
; |
418 |
if (*tmp == '\"') |
419 |
{ |
420 |
/* yuppi, we've just reached the user="..."; field */ |
421 |
int matches; |
422 |
char *tmp2 = strchr(++tmp, '\"'); |
423 |
|
424 |
if (tmp2 != NULL) |
425 |
*tmp2 = '\0'; |
426 |
tmp2 = strchr(tmp, '@'); |
427 |
|
428 |
/* is it matching our mask? */ |
429 |
if (tmp2 == NULL) |
430 |
matches = !irccmp(user, "*") && !irccmp(host, tmp); |
431 |
else |
432 |
{ |
433 |
*tmp2++ = '\0'; |
434 |
matches = !irccmp(user, tmp) && !irccmp(host, tmp2); |
435 |
} |
436 |
|
437 |
if (!matches) |
438 |
{ |
439 |
/* no.. so leave it unchanged */ |
440 |
if (ignore_it) |
441 |
{ |
442 |
ignore_it = 0; |
443 |
fbputs("auth {\n", fout, 7); |
444 |
/* user="..." should be the first field in the auth {}; block, |
445 |
* otherwise we could have problems... |
446 |
*/ |
447 |
} |
448 |
|
449 |
fbputs("\tuser = \"", fout, 9); |
450 |
if (tmp2 == NULL) |
451 |
fbputs("*", fout, 1); |
452 |
else |
453 |
fbputs(tmp, fout, strlen(tmp)); |
454 |
fbputs("@", fout, 1); |
455 |
fbputs(tmp2, fout, strlen(tmp2)); |
456 |
fbputs("\";\n", fout, 3); |
457 |
} |
458 |
else |
459 |
{ |
460 |
/* we've got it! - omit and continue working */ |
461 |
spoof_found = 1; |
462 |
} |
463 |
|
464 |
continue; |
465 |
} |
466 |
} |
467 |
} |
468 |
|
469 |
if (!ignore_it) |
470 |
fbputs(buffer, fout, strlen(buffer)); |
471 |
} |
472 |
|
473 |
fbclose(f); |
474 |
fbclose(fout); |
475 |
|
476 |
if (!spoof_found) |
477 |
{ |
478 |
if (MyConnect(source_p)) |
479 |
sendto_one(source_p, ":%s NOTICE %s :No auth for %s@%s found", |
480 |
me.name, source_p->name, user, host); |
481 |
unlink(SPOOF_FILE ".new"); |
482 |
return; |
483 |
} |
484 |
|
485 |
unlink(SPOOF_FILE); |
486 |
rename(SPOOF_FILE ".new", SPOOF_FILE); |
487 |
rehash(0); |
488 |
#endif |
489 |
|
490 |
#ifdef LOG_SPOOF |
491 |
sendto_realops_flags(UMODE_ALL, L_ALL, "%s deleted auth for %s@%s", |
492 |
source_p->name, user, host); |
493 |
#endif |
494 |
} |