source: code/icb.c@ cd7b81d

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

move it to the github

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