source: code/icb.c@ 0b802fa

Last change on this file since 0b802fa was a785c27, checked in by Mike Belopuhov <mike.belopuhov@…>, 15 years ago

allow specifying a server name; do hostname evaluation only once

  • Property mode set to 100644
File size: 11.8 KB
RevLine 
[cd7b81d]1/*
2 * Copyright (c) 2009, 2010 Mike Belopuhov
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17#include <sys/param.h>
18#include <sys/queue.h>
19#include <stdarg.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <syslog.h>
24#include <unistd.h>
25#include <event.h>
26
27#include "icb.h"
28#include "icbd.h"
29
30extern int creategroups;
[a785c27]31extern char srvname[MAXHOSTNAMELEN];
[cd7b81d]32
33void icb_command(struct icb_session *, char *, char *);
34void icb_groupmsg(struct icb_session *, char *);
35void icb_login(struct icb_session *, char *, char *, char *);
36char *icb_nextfield(char **);
37
38/*
39 * icb_init: initializes pointers to callbacks
40 */
41void
42icb_init(struct icbd_callbacks *ic)
43{
44 icb_drop = ic->drop;
45 icb_log = ic->log;
46 icb_send = ic->send;
47
48 LIST_INIT(&groups);
[a785c27]49
50 if (strlen(srvname) == 0)
51 (void)gethostname(srvname, sizeof srvname);
52 /*
53 * MAXHOSTNAMELEN is usually greater than what we
54 * can actually send, hence truncation:
55 */
56 if (strlen(srvname) > 200)
57 srvname[200] = '\0';
[cd7b81d]58}
59
60/*
61 * icb_start: called upon accepting a new connection, greets new client
62 */
63void
64icb_start(struct icb_session *is)
65{
[a785c27]66 icb_sendfmt(is, "%c%c%c%s%c%s", ICB_M_PROTO, '1', ICB_M_SEP, srvname,
[cd7b81d]67 ICB_M_SEP, "icbd");
68 SETF(is->flags, ICB_SF_PROTOSENT);
69}
70
71/*
72 * icb_input: main input processing routine
73 */
74void
75icb_input(struct icb_session *is)
76{
77 char *msg = is->buffer;
78 char type;
79
80 is->last = getmonotime();
81 type = msg[1];
82 msg += 2;
83 if (!ISSETF(is->flags, ICB_SF_LOGGEDIN) && type != ICB_M_LOGIN) {
84 icb_error(is, "Not logged in");
85 return;
86 }
87 switch (type) {
88 case ICB_M_LOGIN: {
89 char *nick, *group, *client, *cmd;
90
91 client = icb_nextfield(&msg);
92 nick = icb_nextfield(&msg);
93 group = icb_nextfield(&msg);
94 cmd = icb_nextfield(&msg);
95 if (strlen(cmd) > 0 && cmd[0] == 'w') {
96 icb_error(is, "Command not implemented");
97 icb_drop(is, NULL);
98 return;
99 }
[1da9ee5]100 if (strlen(cmd) == 0 || strcmp(cmd, "login") != 0) {
101 icb_error(is, "Malformed login packet");
102 icb_drop(is, NULL);
103 return;
104 }
[cd7b81d]105 icb_login(is, group, nick, client);
106 break;
107 }
108 case ICB_M_OPEN: {
109 char *grpmsg;
110
111 grpmsg = icb_nextfield(&msg);
112 icb_groupmsg(is, grpmsg);
113 break;
114 }
115 case ICB_M_COMMAND: {
116 char *cmd, *arg;
117
118 cmd = icb_nextfield(&msg);
119 arg = icb_nextfield(&msg);
120 icb_command(is, cmd, arg);
121 break;
122 }
123 case ICB_M_PROTO:
124 case ICB_M_NOOP:
125 /* ignore */
126 break;
127 default:
128 /* everything else is not valid */
129 icb_error(is, "Bummer. This is a bummer, man.");
130 }
131}
132
133/*
134 * icb_login: handles login ('a') packets
135 */
136void
137icb_login(struct icb_session *is, char *group, char *nick, char *client)
138{
139 char *defgrp = "1";
140 struct icb_group *ig;
141 struct icb_session *s;
142
143 if (!nick || strlen(nick) == 0) {
144 icb_error(is, "Invalid nick");
145 icb_drop(is, NULL);
146 return;
147 }
148 if (!group || strlen(group) == 0)
149 group = defgrp;
150 LIST_FOREACH(ig, &groups, entry) {
151 if (strcmp(ig->name, group) == 0)
152 break;
153 }
154 if (ig == NULL) {
155 if (!creategroups) {
156 icb_error(is, "Invalid group %s", group);
157 icb_drop(is, NULL);
158 return;
159 } else {
160 if ((ig = icb_addgroup(is, group, NULL)) == NULL) {
161 icb_error(is, "Can't create group %s", group);
162 return;
163 }
164 icb_log(NULL, LOG_DEBUG, "%s created group %s",
165 nick, group);
166 }
167 }
168 LIST_FOREACH(s, &ig->sess, entry) {
169 if (strcmp(s->nick, nick) == 0) {
170 icb_error(is, "Nick is already in use");
171 icb_drop(is, NULL);
172 return;
173 }
174 }
175
176 if (client && strlen(client) > 0)
177 strlcpy(is->client, client, sizeof is->client);
178 strlcpy(is->nick, nick, sizeof is->nick);
179 is->group = ig;
180 is->login = time(NULL);
181 is->last = getmonotime();
182
183 /* notify group */
184 icb_status_group(ig, NULL, STATUS_SIGNON, "%s (%s@%s) entered group",
185 is->nick, is->client, is->host);
186
187 CLRF(is->flags, ICB_SF_PROTOSENT);
188 SETF(is->flags, ICB_SF_LOGGEDIN);
189
190 LIST_INSERT_HEAD(&ig->sess, is, entry);
191
192 /* acknowledge successful login */
193 icb_sendfmt(is, "%c", ICB_M_LOGIN);
194
195 /* notify user */
196 icb_status(is, STATUS_STATUS, "You are now in group %s%s", ig->name,
197 icb_ismoder(ig, is) ? " as moderator" : "");
198
199 /* send user a topic name */
200 if (strlen(ig->topic) > 0)
201 icb_status(is, STATUS_TOPIC, "Topic for %s is \"%s\"",
202 ig->name, ig->topic);
203}
204
205/*
206 * icb_groupmsg: handles open message ('b') packets
207 */
208void
209icb_groupmsg(struct icb_session *is, char *msg)
210{
211 char buf[ICB_MSGSIZE];
212 struct icb_group *ig = is->group;
213 struct icb_session *s;
214 int buflen = 1;
215
216 if (strlen(msg) == 0) {
217 icb_error(is, "Empty message");
218 return;
219 }
220
221 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c%s", ICB_M_OPEN,
222 is->nick, ICB_M_SEP, msg);
223 buf[0] = buflen;
224
225 LIST_FOREACH(s, &ig->sess, entry) {
226 if (s == is)
227 continue;
228 icb_send(s, buf, buflen + 1);
229 }
230}
231
232/*
233 * icb_privmsg: handles personal message ('c') packets
234 */
235void
236icb_privmsg(struct icb_session *is, char *whom, char *msg)
237{
238 struct icb_group *ig = is->group;
239 struct icb_session *s;
240
241 LIST_FOREACH(s, &ig->sess, entry) {
242 if (strcmp(s->nick, whom) == 0)
243 break;
244 }
245 if (!s) {
246 icb_error(is, "No such user %s", whom);
247 return;
248 }
249 icb_sendfmt(s, "%c%s%c%s", ICB_M_PERSONAL, is->nick, ICB_M_SEP, msg);
250}
251
252/*
253 * icb_command: handles command ('h') packets
254 */
255void
256icb_command(struct icb_session *is, char *cmd, char *arg)
257{
258 void (*handler)(struct icb_session *, char *);
259
260 if ((handler = icb_cmd_lookup(cmd)) == NULL) {
261 icb_error(is, "Unsupported command: %s", cmd);
262 return;
263 }
264 handler(is, arg);
265}
266
267/*
268 * icb_cmdout: sends out command output ('i') packets, called from the
269 * command handlers
270 */
271void
272icb_cmdout(struct icb_session *is, int type, char *outmsg)
273{
274 char *otype = NULL;
275
276 switch (type) {
277 case CMDOUT_CO:
278 otype = "co";
279 break;
280 case CMDOUT_EC:
281 otype = "ec";
282 break;
283 case CMDOUT_WL:
284 otype = "wl";
285 break;
286 case CMDOUT_WG:
287 otype = "wg";
288 break;
289 default:
290 icb_log(is, LOG_ERR, "unknown cmdout type");
291 return;
292 }
293 icb_sendfmt(is, "%c%s%c%s", ICB_M_CMDOUT, otype, ICB_M_SEP, outmsg);
294}
295
296/*
297 * icb_status: sends a status message ('d') to the client
298 */
299void
300icb_status(struct icb_session *is, int type, const char *fmt, ...)
301{
302 va_list ap;
303 char buf[ICB_MSGSIZE];
304 int buflen = 1;
305 static const struct {
306 int type;
307 const char *msg;
308 } msgtab[] = {
309 { STATUS_ARRIVE, "Arrive" },
310 { STATUS_BOOT, "Boot" },
311 { STATUS_DEPART, "Depart" },
312 { STATUS_NAME, "Name" },
313 { STATUS_NOTIFY, "Notify" },
314 { STATUS_SIGNON, "Sign-on" },
315 { STATUS_SIGNOFF, "Sign-off" },
316 { STATUS_STATUS, "Status" },
317 { STATUS_TOPIC, "Topic" },
318 { STATUS_WARNING, "Warning" },
319 { NULL, NULL }
320 };
321
322 if (type < 0 || type > (int)nitems(msgtab) - 1)
323 return;
324 va_start(ap, fmt);
325 buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c", ICB_M_STATUS,
326 msgtab[type].msg, ICB_M_SEP);
327 buflen += vsnprintf(&buf[buflen], sizeof buf - buflen, fmt, ap);
328 buf[0] = buflen;
329 va_end(ap);
330 icb_send(is, buf, buflen + 1);
331}
332
333/*
334 * icb_status: sends a status message ('d') to the group except of the
335 * "ex" if it's not NULL
336 */
337void
338icb_status_group(struct icb_group *ig, struct icb_session *ex, int type,
339 const char *fmt, ...)
340{
341 char buf[ICB_MSGSIZE];
342 va_list ap;
343 struct icb_session *s;
344
345 va_start(ap, fmt);
346 (void)vsnprintf(buf, sizeof buf, fmt, ap);
347 LIST_FOREACH(s, &ig->sess, entry) {
348 if (ex && s == ex)
349 continue;
350 icb_status(s, type, buf);
351 }
352 icb_log(NULL, LOG_DEBUG, "%s", buf);
353 va_end(ap);
354}
355
356/*
357 * icb_error: sends an error message ('e') to the client
358 */
359void
360icb_error(struct icb_session *is, const char *fmt, ...)
361{
362 char buf[ICB_MSGSIZE];
363 va_list ap;
364 int buflen = 1;
365
366 va_start(ap, fmt);
367 buflen += vsnprintf(&buf[2], sizeof buf - 2, fmt, ap);
368 va_end(ap);
369 buf[0] = ++buflen; /* account for ICB_M_ERROR */
370 buf[1] = ICB_M_ERROR;
371 icb_send(is, buf, buflen + 1);
372 icb_log(is, LOG_DEBUG, "%s", buf + 2);
373}
374
375/*
376 * icb_remove: removes a session from the associated group
377 */
378void
379icb_remove(struct icb_session *is, char *reason)
380{
381 if (is->group) {
382 if (icb_ismoder(is->group, is))
383 (void)icb_pass(is->group, is, NULL);
384 LIST_REMOVE(is, entry);
385 if (reason)
386 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
387 "%s (%s@%s) just left: %s", is->nick, is->client,
388 is->host, reason);
389 else
390 icb_status_group(is->group, NULL, STATUS_SIGNOFF,
391 "%s (%s@%s) just left", is->nick, is->client,
392 is->host);
393 }
394}
395
396/*
397 * icb_addgroup: adds a new group to the list
398 */
399struct icb_group *
400icb_addgroup(struct icb_session *is, char *name, char *mpass)
401{
402 struct icb_group *ig;
403
404 if ((ig = calloc(1, sizeof *ig)) == NULL)
405 return (NULL);
406 strlcpy(ig->name, name, sizeof ig->name);
407 if (mpass)
408 strlcpy(ig->mpass, mpass, sizeof ig->mpass);
409 if (is)
410 ig->moder = is;
411 LIST_INIT(&ig->sess);
412 LIST_INSERT_HEAD(&groups, ig, entry);
413 return (ig);
414}
415
416#ifdef notused
417/*
418 * icb_delgroup: removes a group from the list
419 */
420void
421icb_delgroup(struct icb_group *ig)
422{
423 struct icb_session *s;
424
425 /* well, i guess we should kick out participants! ;-) */
426 LIST_FOREACH(s, &ig->sess, entry) {
427 icb_status(s, STATUS_WARNING, "Group dismissed");
428 s->group = NULL;
429 }
430 LIST_REMOVE(ig, entry);
431 bzero(ig, sizeof ig); /* paranoic thing, obviously */
432 free(ig);
433}
434#endif
435
436/*
437 * icb_who: sends a list of users of the specified group (or the current
438 * one otherwise) in the "wl" format
439 */
440void
441icb_who(struct icb_session *is, struct icb_group *ig)
442{
443 char buf[ICB_MSGSIZE];
444 struct icb_session *s;
445
446 if (!ig)
447 ig = is->group;
448 LIST_FOREACH(s, &ig->sess, entry) {
449 (void)snprintf(buf, sizeof buf,
450 "%c%c%s%c%d%c0%c%d%c%s%c%s%c%s",
451 icb_ismoder(ig, s) ? '*' : ' ', ICB_M_SEP,
452 s->nick, ICB_M_SEP, getmonotime() - s->last,
453 ICB_M_SEP, ICB_M_SEP, s->login, ICB_M_SEP,
454 s->client, ICB_M_SEP, s->host, ICB_M_SEP, " ");
455 icb_cmdout(is, CMDOUT_WL, buf);
456 }
457}
458
459/*
460 * icb_ismoder: checks whether group is moderated by "is"
461 */
462int
463icb_ismoder(struct icb_group *ig, struct icb_session *is)
464{
465 if (ig->moder && ig->moder == is)
466 return (1);
467 return (0);
468}
469
470/*
471 * icb_pass: passes moderation of group "ig" from "from" to "to",
472 * returns -1 if "from" is not a moderator, 1 if passed
473 * to "to" and 0 otherwise (no moderator or passed to the
474 * internal bot)
475 */
476int
477icb_pass(struct icb_group *ig, struct icb_session *from,
478 struct icb_session *to)
479{
480 if (ig->moder && ig->moder != from)
481 return (-1);
482 if (!from && !to)
483 return (-1);
484 ig->moder = to;
485 if (to)
486 icb_status(to, STATUS_NOTIFY, "%s just passed you moderation"
487 " of %s", from ? from->nick : "server", ig->name);
488 icb_status_group(ig, to, STATUS_NOTIFY, "%s has passed moderation "
489 "to %s", from ? from->nick : "server", to ? to->nick : "server");
490 return (1);
491}
492
493/*
494 * icb_nextfield: advances through a given buffer returning pointer to
495 * the beginning of the icb field or an empty string otherwise
496 */
497char *
498icb_nextfield(char **buf)
499{
500 char *start = *buf;
501
502 while (*buf && **buf != '\0' && **buf != ICB_M_SEP)
503 (*buf)++;
504 if (*buf && **buf == ICB_M_SEP) {
505 **buf = '\0';
506 (*buf)++;
507 }
508 return (start);
509}
510
511/*
512 * icb_sendfmt: formats a string and sends it over
513 */
514void
515icb_sendfmt(struct icb_session *is, const char *fmt, ...)
516{
517 char buf[ICB_MSGSIZE];
518 va_list ap;
519 int buflen = 1;
520
521 va_start(ap, fmt);
522 buflen += vsnprintf(&buf[1], sizeof buf - 1, fmt, ap);
523 va_end(ap);
524 buf[0] = buflen;
525 icb_send(is, buf, buflen + 1);
526}
Note: See TracBrowser for help on using the repository browser.