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