ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/saslserv/main.C
Revision: 1.10
Committed: Sat Sep 22 14:27:30 2007 UTC (16 years, 8 months ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +5 -4 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

# Content
1 /**
2 * main.C: This file contains the main() routine.
3 *
4 * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team
5 * Rights to this code are as documented in COPYING.
6 *
7 *
8 * Portions of this file were derived from sources bearing the following license:
9 * Copyright © 2005 Atheme Development Group
10 * Rights to this code are documented in doc/pod/license.pod.
11 *
12 * $Id: main.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $
13 */
14
15 #include "atheme.h"
16 #include <libermyth.h>
17 #include "confparse.h"
18 #include <ermyth/module.h>
19 #include <account/myuser.h>
20 #include <util/base64.h>
21 #include <sasl.h>
22
23 static char const rcsid[] = "$Id: main.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $";
24
25 REGISTER_MODULE ("saslserv/main", false, "The Ermyth Team <http://ermyth.xinutec.org>");
26
27 list_t sessions;
28 list_t sasl_mechanisms;
29
30 /* main services client routine */
31 static void
32 saslserv (sourceinfo_t *si, int parc, char *parv[])
33 {
34 char *cmd;
35 char *text;
36 char orig[BUFSIZE];
37
38 /* this should never happen */
39 if (parv[0][0] == '&')
40 {
41 slog (LG_ERROR, "services(): got parv with local channel: %s", parv[0]);
42 return;
43 }
44
45 /* make a copy of the original for debugging */
46 strlcpy (orig, parv[parc - 1], BUFSIZE);
47
48 /* lets go through this to get the command */
49 cmd = strtok (parv[parc - 1], " ");
50 text = strtok (NULL, "");
51
52 if (!cmd)
53 return;
54 if (*cmd == '\001')
55 {
56 handle_ctcp_common (si, cmd, text);
57 return;
58 }
59
60 command_fail (si, fault::noprivs, "This service exists to identify "
61 "connecting clients to the network. It has no "
62 "public interface.");
63 }
64
65 static void
66 on_config_ready (void)
67 {
68 if (saslsvs.me)
69 del_service (saslsvs.me);
70
71 saslsvs.me = add_service (saslsvs.nick, saslsvs.user, saslsvs.host, saslsvs.real, saslserv);
72 }
73
74 /*
75 * Begin SASL-specific code
76 */
77
78 static void
79 sasl_logcommand (sasl_session_t *p, myuser_t *login, int level, char const * const fmt, ...)
80 {
81 va_list args;
82 char lbuf[BUFSIZE];
83
84 va_start (args, fmt);
85 vsnprintf (lbuf, BUFSIZE, fmt, args);
86 slog (level, "%s %s:%s %s", saslsvs.nick, login ? login->name : "", p->uid, lbuf);
87 va_end (args);
88 }
89
90 /* find an existing session by uid */
91 static sasl_session_t *
92 find_session (char const * const uid)
93 {
94 sasl_session_t *p;
95 node_t *n;
96
97 LIST_FOREACH (n, sessions.head)
98 {
99 p = static_cast<sasl_session_t *> (n->data);
100 if (!strcmp (p->uid, uid))
101 return p;
102 }
103
104 return NULL;
105 }
106
107 /* create a new session if it does not already exist */
108 static sasl_session_t *
109 make_session (char const * const uid)
110 {
111 sasl_session_t *p = find_session (uid);
112 node_t *n;
113
114 if (p)
115 return p;
116
117 p = new sasl_session_t;
118 strlcpy (p->uid, uid, IDLEN);
119
120 n = node_create ();
121 node_add (p, n, &sessions);
122
123 return p;
124 }
125
126 /* free a session and all its contents */
127 static void
128 destroy_session (sasl_session_t *p)
129 {
130 node_t *n, *tn;
131 myuser_t *mu;
132
133 if (p->flags & ASASL_NEED_LOG && p->username != NULL)
134 {
135 mu = myuser_t::find (p->username);
136 if (mu != NULL)
137 sasl_logcommand (p, mu, CMDLOG_LOGIN, "LOGIN (session timed out)");
138 }
139
140 LIST_FOREACH_SAFE (n, tn, sessions.head)
141 {
142 if (n->data == p)
143 {
144 node_del (n, &sessions);
145 node_free (n);
146 }
147 }
148
149 sfree (p->buf);
150 p->buf = p->p = NULL;
151 if (p->mechptr)
152 p->mechptr->mech_finish (p); /* Free up any mechanism data */
153 p->mechptr = NULL; /* We're not freeing the mechanism, just "dereferencing" it */
154 sfree (p->username);
155
156 delete p;
157 }
158
159 /* find a mechanism by name */
160 static sasl_mechanism_t *
161 find_mechanism (char *name)
162 {
163 node_t *n;
164 sasl_mechanism_t *mptr;
165
166 LIST_FOREACH (n, sasl_mechanisms.head)
167 {
168 mptr = static_cast<sasl_mechanism_t *> (n->data);
169 if (!strcmp (mptr->name, name))
170 return mptr;
171 }
172
173 slog (LG_DEBUG, "find_mechanism(): cannot find mechanism `%s'!", name);
174
175 return NULL;
176 }
177
178 /* output an arbitrary amount of data to the SASL client */
179 static void
180 sasl_write (char *target, char *data, int length)
181 {
182 char out[401];
183 int last = 400, rem = length;
184
185 while (rem)
186 {
187 int nbytes = rem > 400 ? 400 : rem;
188 memcpy (out, data, nbytes);
189 out[nbytes] = '\0';
190 phandler->sasl_sts (target, 'C', out);
191
192 data += nbytes;
193 rem -= nbytes;
194 last = nbytes;
195 }
196
197 /* The end of a packet is indicated by a string not of length 400.
198 * If last piece is exactly 400 in size, send an empty string to
199 * finish the transaction.
200 * Also if there is no data at all.
201 */
202 if (last == 400)
203 phandler->sasl_sts (target, 'C', "+");
204 }
205
206 /* authenticated, now double check that their account is ok for login */
207 static int
208 login_user (sasl_session_t *p)
209 {
210 myuser_t *mu = myuser_t::find (p->username);
211 metadata *md;
212
213 if (mu == NULL) /* WTF? */
214 return 0;
215
216 if ((md = mu->find_metadata ("private:freeze:freezer")))
217 {
218 sasl_logcommand (p, NULL, CMDLOG_LOGIN, "failed LOGIN to %s (frozen)", mu->name);
219 return 0;
220 }
221
222 if (mu->logins.size () >= me.maxlogins)
223 {
224 sasl_logcommand (p, NULL, CMDLOG_LOGIN, "failed LOGIN to %s (too many logins)", mu->name);
225 return 0;
226 }
227
228 /* Log it with the full n!u@h later */
229 p->flags |= ASASL_NEED_LOG;
230
231 return 1;
232 }
233
234 /* given an entire sasl message, advance session by passing data to mechanism
235 * and feeding returned data back to client.
236 */
237 static void
238 sasl_packet (sasl_session_t *p, char *buf, int len)
239 {
240 int rc;
241 size_t tlen = 0;
242 char const *cloak;
243 char *out = NULL;
244 char *temp;
245 char mech[21];
246 int out_len = 0;
247 metadata *md;
248 base64::encoder b64enc;
249 base64::decoder b64dec;
250
251 /* First piece of data in a session is the name of
252 * the SASL mechanism that will be used.
253 */
254 if (!p->mechptr)
255 {
256 if (len > 20)
257 {
258 phandler->sasl_sts (p->uid, 'D', "F");
259 destroy_session (p);
260 return;
261 }
262
263 memcpy (mech, buf, len);
264 mech[len] = '\0';
265
266 if (!(p->mechptr = find_mechanism (mech)))
267 {
268 /* Generate a list of supported mechanisms (disabled since charybdis doesn't support this yet). */
269 #if 0
270 char temp[400], *ptr = temp;
271 int l = 0;
272 node_t *n;
273
274 LIST_FOREACH(n, sasl_mechanisms.head)
275 {
276 sasl_mechanism_t *mptr = n->data;
277 if(l + strlen(mptr->name) > 510)
278 break;
279 strcpy(ptr, mptr->name);
280 ptr += strlen(mptr->name);
281 *ptr++ = ',';
282 l += strlen(mptr->name) + 1;
283 }
284
285 if(l)
286 ptr--;
287 *ptr = '\0';
288
289 phandler->sasl_sts(p->uid, 'M', temp);
290 #endif
291
292 phandler->sasl_sts (p->uid, 'D', "F");
293 destroy_session (p);
294 return;
295 }
296
297 rc = p->mechptr->mech_start (p, &out, &out_len);
298 }
299 else
300 {
301 if (b64dec.decode (buf, len, &temp, &tlen))
302 {
303 rc = p->mechptr->mech_step (p, temp, tlen, &out, &out_len);
304 delete[] temp;
305 }
306 else
307 rc = ASASL_FAIL;
308 }
309
310 if (rc == ASASL_DONE)
311 {
312 myuser_t *mu = myuser_t::find (p->username);
313 if (mu && login_user (p))
314 {
315 if ((md = mu->find_metadata ("private:usercloak")))
316 cloak = md->value;
317 else
318 cloak = "*";
319
320 phandler->svslogin_sts (p->uid, "*", "*", cloak, mu->name);
321 phandler->sasl_sts (p->uid, 'D', "S");
322 }
323 else
324 phandler->sasl_sts (p->uid, 'D', "F");
325 /* Will destroy session on introduction of user to net. */
326 return;
327 }
328 else if (rc == ASASL_MORE)
329 {
330 if (out_len)
331 {
332 if (b64enc.encode (out, out_len, &temp))
333 {
334 sasl_write (p->uid, temp, strlen (temp));
335 delete[] temp;
336 sfree (out);
337 return;
338 }
339 }
340 else
341 {
342 phandler->sasl_sts (p->uid, 'C', "+");
343 sfree (out);
344 return;
345 }
346 }
347
348 sfree (out);
349 phandler->sasl_sts (p->uid, 'D', "F");
350 destroy_session (p);
351 }
352
353 /* interpret an AUTHENTICATE message */
354 static void
355 sasl_input (char const * const uid, char const mode, char const * const buf)
356 {
357 sasl_session_t *p = make_session (uid);
358 int len = strlen (buf);
359
360 /* Abort packets, or maybe some other kind of (D)one */
361 if (mode == 'D')
362 {
363 destroy_session (p);
364 return;
365 }
366
367 if (mode != 'S' && mode != 'C')
368 return;
369
370 if (p->buf == NULL)
371 {
372 p->buf = salloc<char> (len + 1);
373 p->p = p->buf;
374 p->len = len;
375 }
376 else
377 {
378 if (p->len + len + 1 > 8192) /* This is a little much... */
379 {
380 phandler->sasl_sts (p->uid, 'D', "F");
381 destroy_session (p);
382 return;
383 }
384
385 p->buf = (char *) realloc (p->buf, p->len + len + 1);
386 p->p = p->buf + p->len;
387 p->len += len;
388 }
389
390 memcpy (p->p, buf, len);
391
392 /* Messages not exactly 400 bytes are the end of a packet. */
393 if (len < 400)
394 {
395 p->buf[p->len] = '\0';
396 sasl_packet (p, p->buf, p->len);
397 sfree (p->buf);
398 p->buf = p->p = NULL;
399 p->len = 0;
400 }
401 }
402
403 /* clean up after a user who is finally on the net */
404 static void
405 sasl_newuser (user_t *u)
406 {
407 sasl_session_t *p = find_session (u->uid);
408 metadata *md_failnum;
409 char lau[BUFSIZE], lao[BUFSIZE];
410 char strfbuf[BUFSIZE];
411 struct tm tm;
412 myuser_t *mu;
413
414 /* Not concerned unless it's a SASL login. */
415 if (p == NULL)
416 return;
417
418 /* We will log it ourselves, if needed */
419 p->flags &= ~ASASL_NEED_LOG;
420
421 /* Find the account */
422 mu = p->username ? myuser_t::find (p->username) : NULL;
423 if (mu == NULL)
424 {
425 notice (saslsvs.nick, u->nick, "Account %s dropped, login cancelled", p->username ? p->username : "??");
426 destroy_session (p);
427 /* We'll remove their ircd login in handle_burstlogin() */
428 return;
429 }
430 destroy_session (p);
431
432 if (is_soper (mu))
433 {
434 snoop ("SOPER: \2%s\2 as \2%s\2", u->nick, mu->name);
435 }
436
437 mu->notice (saslsvs.nick, "%s!%s@%s has just authenticated as you (%s)", u->nick, u->user, u->vhost, mu->name);
438
439 u->myuser = mu;
440 mu->logins.insert (u);
441
442 /* keep track of login address for users */
443 strlcpy (lau, u->user, BUFSIZE);
444 strlcat (lau, "@", BUFSIZE);
445 strlcat (lau, u->vhost, BUFSIZE);
446 mu->add_metadata ("private:host:vhost", lau);
447
448 /* and for opers */
449 strlcpy (lao, u->user, BUFSIZE);
450 strlcat (lao, "@", BUFSIZE);
451 /* Hack for charybdis before 2.1: store IP instead of vhost
452 * (real host is not known at this time) -- jilles */
453 slog (LG_DEBUG, "nick %s host %s vhost %s ip %s", u->nick, u->host, u->vhost, u->ip);
454 if (!strcmp (u->host, u->vhost) && *u->ip != '\0' && mu->find_metadata ("private:usercloak"))
455 strlcat (lao, u->ip, BUFSIZE);
456 else
457 strlcat (lao, u->host, BUFSIZE);
458 mu->add_metadata ("private:host:actual", lao);
459
460 logcommand_user (saslsvs.me, u, CMDLOG_LOGIN, "LOGIN");
461
462 /* check for failed attempts and let them know */
463 if ((md_failnum = mu->find_metadata ("private:loginfail:failnum")) && (atoi (md_failnum->value) > 0))
464 {
465 metadata *md_failtime, *md_failaddr;
466 time_t ts;
467
468 tm = *localtime (&mu->lastlogin);
469 strftime (strfbuf, sizeof (strfbuf) - 1, "%b %d %H:%M:%S %Y", &tm);
470
471 notice (saslsvs.nick, u->nick, "\2%d\2 failed %s since %s.", atoi (md_failnum->value), (atoi (md_failnum->value) == 1) ? "login" : "logins", strfbuf);
472
473 md_failtime = mu->find_metadata ("private:loginfail:lastfailtime");
474 ts = atol (md_failtime->value);
475 md_failaddr = mu->find_metadata ("private:loginfail:lastfailaddr");
476
477 tm = *localtime (&ts);
478 strftime (strfbuf, sizeof (strfbuf) - 1, "%b %d %H:%M:%S %Y", &tm);
479
480 notice (saslsvs.nick, u->nick, "Last failed attempt from: \2%s\2 on %s.", md_failaddr->value, strfbuf);
481
482 mu->del_metadata ("private:loginfail:failnum"); /* md_failnum now invalid */
483 mu->del_metadata ("private:loginfail:lastfailtime");
484 mu->del_metadata ("private:loginfail:lastfailaddr");
485 }
486
487 mu->lastlogin = NOW;
488 u->callback.identify (u);
489 }
490
491 /* This function is run approximately once every 30 seconds.
492 * It looks for flagged sessions, and deletes them, while
493 * flagging all the others. This way stale sessions are deleted
494 * after no more than 60 seconds.
495 */
496 static void
497 delete_stale (void *vptr)
498 {
499 sasl_session_t *p;
500 node_t *n, *tn;
501
502 LIST_FOREACH_SAFE (n, tn, sessions.head)
503 {
504 p = static_cast<sasl_session_t *> (n->data);
505 if (p->flags & ASASL_MARKED_FOR_DELETION)
506 {
507 node_del (n, &sessions);
508 destroy_session (p);
509 node_free (n);
510 }
511 else
512 p->flags |= ASASL_MARKED_FOR_DELETION;
513 }
514 }
515
516 bool
517 _modinit (module *m)
518 {
519 ConfTable::callback.ready.attach (on_config_ready);
520
521 user_t::callback.sasl_input.attach (sasl_input);
522 user_t::callback.add.attach (sasl_newuser);
523 event_add ("sasl_delete_stale", delete_stale, NULL, 30);
524
525 if (!cold_start)
526 saslsvs.me = add_service (saslsvs.nick, saslsvs.user, saslsvs.host, saslsvs.real, saslserv);
527 authservice_loaded++;
528
529 return true;
530 }
531
532 void
533 _moddeinit ()
534 {
535 node_t *n, *tn;
536
537 user_t::callback.sasl_input.detach (sasl_input);
538 user_t::callback.add.detach (sasl_newuser);
539 event_delete (delete_stale, NULL);
540
541 if (saslsvs.me)
542 {
543 del_service (saslsvs.me);
544 saslsvs.me = NULL;
545 }
546 authservice_loaded--;
547
548 LIST_FOREACH_SAFE (n, tn, sessions.head)
549 {
550 destroy_session (static_cast<sasl_session_t *> (n->data));
551 node_del (n, &sessions);
552 node_free (n);
553 }
554
555 ConfTable::callback.ready.detach (on_config_ready);
556 }