move it to the github
This commit is contained in:
parent
b47ffaa841
commit
cd7b81d00d
10
Makefile
Normal file
10
Makefile
Normal file
@ -0,0 +1,10 @@
|
||||
PROG= icbd
|
||||
SRCS= cmd.c dns.c icb.c icbd.c
|
||||
MAN= icbd.8
|
||||
|
||||
CFLAGS+=-W -Wall -Werror
|
||||
|
||||
DPADD= ${LIBEVENT}
|
||||
LDADD= -levent
|
||||
|
||||
.include <bsd.prog.mk>
|
236
cmd.c
Normal file
236
cmd.c
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2010 Mike Belopuhov
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef lint
|
||||
static const char rcsid[] = "$ABSD: cmd.c,v 1.21 2010/01/03 20:54:18 kmerz Exp $";
|
||||
#endif /* not lint */
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/queue.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include <event.h>
|
||||
|
||||
#include "icb.h"
|
||||
|
||||
extern int creategroups;
|
||||
|
||||
void icb_cmd_boot(struct icb_session *, char *);
|
||||
void icb_cmd_change(struct icb_session *, char *);
|
||||
void icb_cmd_name(struct icb_session *, char *);
|
||||
void icb_cmd_personal(struct icb_session *, char *);
|
||||
void icb_cmd_pass(struct icb_session *, char *);
|
||||
void icb_cmd_topic(struct icb_session *, char *);
|
||||
void icb_cmd_who(struct icb_session *, char *);
|
||||
|
||||
void *
|
||||
icb_cmd_lookup(char *cmd)
|
||||
{
|
||||
struct {
|
||||
const char *cmd;
|
||||
void (*handler)(struct icb_session *, char *);
|
||||
} cmdtab[] = {
|
||||
{ "boot", icb_cmd_boot },
|
||||
{ "g", icb_cmd_change },
|
||||
{ "m", icb_cmd_personal },
|
||||
{ "msg", icb_cmd_personal },
|
||||
{ "name", icb_cmd_name },
|
||||
{ "pass", icb_cmd_pass },
|
||||
{ "topic", icb_cmd_topic },
|
||||
{ "w", icb_cmd_who },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
int i;
|
||||
|
||||
for (i = 0; cmdtab[i].cmd != NULL; i++)
|
||||
if (strcasecmp(cmdtab[i].cmd, cmd) == 0)
|
||||
return (cmdtab[i].handler);
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_boot(struct icb_session *is, char *arg)
|
||||
{
|
||||
struct icb_group *ig;
|
||||
struct icb_session *s;
|
||||
|
||||
/* to boot or not to boot, that is the question */
|
||||
ig = is->group;
|
||||
if (!icb_ismoder(ig, is)) {
|
||||
icb_status(is, STATUS_NOTIFY, "Sorry, booting is a privilege "
|
||||
"you don't possess");
|
||||
return;
|
||||
}
|
||||
|
||||
/* who would be a target then? */
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (strcmp(s->nick, arg) == 0)
|
||||
break;
|
||||
}
|
||||
if (s == NULL) {
|
||||
icb_status(is, STATUS_NOTIFY, "No such user");
|
||||
return;
|
||||
}
|
||||
|
||||
/* okay, here we go, but first, be polite and notify a user */
|
||||
icb_status(s, STATUS_BOOT, "%s booted you", is->nick);
|
||||
icb_status_group(s->group, s, STATUS_BOOT, "%s was booted", s->nick);
|
||||
icb_drop(s, "booted");
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_change(struct icb_session *is, char *arg)
|
||||
{
|
||||
struct icb_group *ig;
|
||||
struct icb_session *s;
|
||||
int changing = 0;
|
||||
|
||||
if (strlen(arg) == 0) {
|
||||
icb_error(is, "Invalid group");
|
||||
return;
|
||||
}
|
||||
|
||||
LIST_FOREACH(ig, &groups, entry) {
|
||||
if (strcmp(ig->name, arg) == 0)
|
||||
break;
|
||||
}
|
||||
if (ig == NULL) {
|
||||
if (!creategroups) {
|
||||
icb_error(is, "Invalid group");
|
||||
return;
|
||||
} else {
|
||||
if ((ig = icb_addgroup(is, arg, NULL)) == NULL) {
|
||||
icb_error(is, "Can't create group");
|
||||
return;
|
||||
}
|
||||
icb_log(NULL, LOG_DEBUG, "%s created group %s",
|
||||
is->nick, arg);
|
||||
}
|
||||
}
|
||||
|
||||
/* changing to the same group is strictly prohibited */
|
||||
if (is->group && is->group == ig) {
|
||||
icb_error(is, "Huh?");
|
||||
return;
|
||||
}
|
||||
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (strcmp(s->nick, is->nick) == 0) {
|
||||
icb_error(is, "Nick is already in use");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (is->group) {
|
||||
changing = 1;
|
||||
if (icb_ismoder(is->group, is))
|
||||
(void)icb_pass(is->group, is, NULL);
|
||||
LIST_REMOVE(is, entry);
|
||||
icb_status_group(is->group, NULL, STATUS_DEPART,
|
||||
"%s (%s@%s) just left", is->nick, is->client, is->host);
|
||||
}
|
||||
|
||||
is->group = ig;
|
||||
LIST_INSERT_HEAD(&ig->sess, is, entry);
|
||||
|
||||
/* notify group */
|
||||
icb_status_group(ig, is, changing ? STATUS_ARRIVE : STATUS_SIGNON,
|
||||
"%s (%s@%s) entered group", is->nick, is->client, is->host);
|
||||
|
||||
/* acknowledge successful join */
|
||||
icb_status(is, STATUS_STATUS, "You are now in group %s%s", ig->name,
|
||||
icb_ismoder(ig, is) ? " as moderator" : "");
|
||||
|
||||
/* send user a topic name */
|
||||
if (strlen(ig->topic) > 0)
|
||||
icb_status(is, STATUS_TOPIC, "The topic is: %s", ig->topic);
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_name(struct icb_session *is, char *arg)
|
||||
{
|
||||
struct icb_group *ig = is->group;
|
||||
struct icb_session *s;
|
||||
|
||||
if (strlen(arg) == 0) {
|
||||
icb_status(is, STATUS_NAME, "Your nickname is %s",
|
||||
is->nick);
|
||||
return;
|
||||
}
|
||||
if (strcasecmp(arg, "admin") == 0) {
|
||||
icb_error(is, "Wuff wuff!");
|
||||
return;
|
||||
}
|
||||
/* sanitize user input */
|
||||
if (strlen(arg) > ICB_MAXNICKLEN)
|
||||
arg[ICB_MAXNICKLEN - 1] = '\0';
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (strcmp(s->nick, arg) == 0) {
|
||||
icb_error(is, "Nick is already in use");
|
||||
return;
|
||||
}
|
||||
}
|
||||
icb_status_group(ig, NULL, STATUS_NAME,
|
||||
"%s changed nickname to %s", is->nick, arg);
|
||||
strlcpy(is->nick, arg, sizeof is->nick);
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_personal(struct icb_session *is, char *arg)
|
||||
{
|
||||
char *p;
|
||||
|
||||
if ((p = strchr(arg, ' ')) == 0) {
|
||||
icb_error(is, "Empty message");
|
||||
return;
|
||||
}
|
||||
*p = '\0';
|
||||
icb_privmsg(is, arg, ++p);
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_pass(struct icb_session *is, char *arg __attribute__((unused)))
|
||||
{
|
||||
if (!icb_ismoder(is->group, is))
|
||||
(void)icb_pass(is->group, is->group->moder, is);
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_topic(struct icb_session *is, char *arg)
|
||||
{
|
||||
struct icb_group *ig = is->group;
|
||||
|
||||
if (strlen(arg) == 0) { /* querying the topic */
|
||||
if (strlen(ig->topic) > 0)
|
||||
icb_status(is, STATUS_TOPIC, "The topic is: %s",
|
||||
ig->topic);
|
||||
else
|
||||
icb_status(is, STATUS_TOPIC, "The topic is not set.");
|
||||
} else { /* setting the topic */
|
||||
strlcpy(ig->topic, arg, sizeof ig->topic);
|
||||
icb_status_group(ig, NULL, STATUS_TOPIC,
|
||||
"%s changed the topic to \"%s\"", is->nick, ig->topic);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
icb_cmd_who(struct icb_session *is, char *arg __attribute__((unused)))
|
||||
{
|
||||
icb_who(is, NULL);
|
||||
}
|
158
dns.c
Normal file
158
dns.c
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Michael Shalayeff
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||
* OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef lint
|
||||
static const char rcsid[] = "$ABSD: dns.c,v 1.2 2010/01/03 01:30:00 kmerz Exp $";
|
||||
#endif /* not lint */
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <syslog.h>
|
||||
#include <sysexits.h>
|
||||
#include <login_cap.h>
|
||||
#include <event.h>
|
||||
#include <pwd.h>
|
||||
#include <netdb.h>
|
||||
|
||||
#include "icb.h"
|
||||
#include "icbd.h"
|
||||
|
||||
void dns_dispatch(int, short, void *);
|
||||
int dns_pipe;
|
||||
|
||||
int
|
||||
icbd_dns_init(void)
|
||||
{
|
||||
struct event ev;
|
||||
int pipe[2];
|
||||
struct passwd *pw;
|
||||
|
||||
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) {
|
||||
syslog(LOG_ERR, "socketpair: %m");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
|
||||
switch (fork()) {
|
||||
case -1:
|
||||
syslog(LOG_ERR, "fork: %m");
|
||||
exit(EX_OSERR);
|
||||
case 0:
|
||||
break;
|
||||
|
||||
default:
|
||||
close(pipe[1]);
|
||||
dns_pipe = pipe[0];
|
||||
return (0);
|
||||
}
|
||||
|
||||
setproctitle("dns resolver");
|
||||
close(pipe[0]);
|
||||
|
||||
if ((pw = getpwnam(ICBD_USER)) == NULL) {
|
||||
syslog(LOG_ERR, "No passwd entry for %s", ICBD_USER);
|
||||
exit(EX_NOUSER);
|
||||
}
|
||||
if (setusercontext(NULL, pw, pw->pw_uid,
|
||||
LOGIN_SETALL & ~LOGIN_SETUSER) < 0) {
|
||||
syslog(LOG_ERR, "%s:%m", pw->pw_name);
|
||||
exit(EX_NOPERM);
|
||||
}
|
||||
if (setuid(pw->pw_uid) < 0) {
|
||||
syslog(LOG_ERR, "%d:%m", pw->pw_uid);
|
||||
exit(EX_NOPERM);
|
||||
}
|
||||
if (chdir("/") < 0) {
|
||||
syslog(LOG_ERR, "chdir: %m");
|
||||
exit(EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
event_init();
|
||||
|
||||
event_set(&ev, pipe[1], EV_READ | EV_PERSIST, dns_dispatch, NULL);
|
||||
if (event_add(&ev, NULL) < 0) {
|
||||
syslog(LOG_ERR, "event_add: %m");
|
||||
exit (EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
return event_dispatch();
|
||||
}
|
||||
|
||||
void
|
||||
dns_dispatch(int fd, short event, void *arg)
|
||||
{
|
||||
char host[NI_MAXHOST];
|
||||
struct sockaddr_storage ss;
|
||||
struct sockaddr *sa = (struct sockaddr *)&ss;
|
||||
int gerr, ss_len = sizeof ss;
|
||||
|
||||
arg = NULL;
|
||||
if (event != EV_READ)
|
||||
return;
|
||||
|
||||
if (verbose)
|
||||
syslog(LOG_DEBUG, "dns_dispatch");
|
||||
|
||||
if (read(fd, &ss, ss_len) != ss_len) {
|
||||
syslog(LOG_ERR, "dns read: %m");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if ((gerr = getnameinfo(sa, sa->sa_len,
|
||||
host, sizeof host, NULL, 0, NI_NOFQDN))) {
|
||||
syslog(LOG_ERR, "getnameinfo: %s", gai_strerror(gerr));
|
||||
write(fd, host, sizeof host);
|
||||
return;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
syslog(LOG_DEBUG, "dns_dispatch: resolved %s", host);
|
||||
|
||||
if (write(fd, host, sizeof host) != sizeof host)
|
||||
syslog(LOG_ERR, "dns write: %m");
|
||||
}
|
||||
|
||||
int
|
||||
dns_rresolv(struct icb_session *is, struct sockaddr_storage *ss)
|
||||
{
|
||||
/* one-shot event for the reply */
|
||||
event_set(&is->ev, dns_pipe, EV_READ, icbd_dns, is);
|
||||
if (event_add(&is->ev, NULL) < 0) {
|
||||
syslog(LOG_ERR, "event_add: %m");
|
||||
exit (EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
inet_ntop(ss->ss_family, ss->ss_family == AF_INET ?
|
||||
(void *)&((struct sockaddr_in *)ss)->sin_addr :
|
||||
(void *)&((struct sockaddr_in6 *)ss)->sin6_addr,
|
||||
is->host, sizeof is->host);
|
||||
|
||||
if (verbose)
|
||||
syslog(LOG_DEBUG, "resolving: %s", is->host);
|
||||
|
||||
if (write(dns_pipe, ss, sizeof *ss) != sizeof *ss) {
|
||||
syslog(LOG_ERR, "write: %m");
|
||||
exit (EX_OSERR);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
524
icb.c
Normal file
524
icb.c
Normal file
@ -0,0 +1,524 @@
|
||||
/*
|
||||
* Copyright (c) 2009, 2010 Mike Belopuhov
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef lint
|
||||
static const char rcsid[] = "$ABSD: icb.c,v 1.23 2010/01/03 20:54:18 kmerz Exp $";
|
||||
#endif /* not lint */
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/queue.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
#include <unistd.h>
|
||||
#include <event.h>
|
||||
|
||||
#include "icb.h"
|
||||
#include "icbd.h"
|
||||
|
||||
extern int creategroups;
|
||||
|
||||
void icb_command(struct icb_session *, char *, char *);
|
||||
void icb_groupmsg(struct icb_session *, char *);
|
||||
void icb_login(struct icb_session *, char *, char *, char *);
|
||||
char *icb_nextfield(char **);
|
||||
|
||||
/*
|
||||
* icb_init: initializes pointers to callbacks
|
||||
*/
|
||||
void
|
||||
icb_init(struct icbd_callbacks *ic)
|
||||
{
|
||||
icb_drop = ic->drop;
|
||||
icb_log = ic->log;
|
||||
icb_send = ic->send;
|
||||
|
||||
LIST_INIT(&groups);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_start: called upon accepting a new connection, greets new client
|
||||
*/
|
||||
void
|
||||
icb_start(struct icb_session *is)
|
||||
{
|
||||
char hname[MAXHOSTNAMELEN];
|
||||
|
||||
bzero(hname, sizeof hname);
|
||||
(void)gethostname(hname, sizeof hname);
|
||||
icb_sendfmt(is, "%c%c%c%s%c%s", ICB_M_PROTO, '1', ICB_M_SEP, hname,
|
||||
ICB_M_SEP, "icbd");
|
||||
SETF(is->flags, ICB_SF_PROTOSENT);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_input: main input processing routine
|
||||
*/
|
||||
void
|
||||
icb_input(struct icb_session *is)
|
||||
{
|
||||
char *msg = is->buffer;
|
||||
char type;
|
||||
|
||||
is->last = getmonotime();
|
||||
type = msg[1];
|
||||
msg += 2;
|
||||
if (!ISSETF(is->flags, ICB_SF_LOGGEDIN) && type != ICB_M_LOGIN) {
|
||||
icb_error(is, "Not logged in");
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case ICB_M_LOGIN: {
|
||||
char *nick, *group, *client, *cmd;
|
||||
|
||||
client = icb_nextfield(&msg);
|
||||
nick = icb_nextfield(&msg);
|
||||
group = icb_nextfield(&msg);
|
||||
cmd = icb_nextfield(&msg);
|
||||
if (strlen(cmd) > 0 && cmd[0] == 'w') {
|
||||
icb_error(is, "Command not implemented");
|
||||
icb_drop(is, NULL);
|
||||
return;
|
||||
}
|
||||
if (strlen(cmd) == 0 || strcmp(cmd, "login") != 0)
|
||||
goto inputerr;
|
||||
icb_login(is, group, nick, client);
|
||||
break;
|
||||
}
|
||||
case ICB_M_OPEN: {
|
||||
char *grpmsg;
|
||||
|
||||
grpmsg = icb_nextfield(&msg);
|
||||
icb_groupmsg(is, grpmsg);
|
||||
break;
|
||||
}
|
||||
case ICB_M_COMMAND: {
|
||||
char *cmd, *arg;
|
||||
|
||||
cmd = icb_nextfield(&msg);
|
||||
arg = icb_nextfield(&msg);
|
||||
icb_command(is, cmd, arg);
|
||||
break;
|
||||
}
|
||||
case ICB_M_PROTO:
|
||||
case ICB_M_NOOP:
|
||||
/* ignore */
|
||||
break;
|
||||
default:
|
||||
/* everything else is not valid */
|
||||
icb_error(is, "Bummer. This is a bummer, man.");
|
||||
}
|
||||
return;
|
||||
inputerr:
|
||||
icb_error(is, "Malformed packet");
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_login: handles login ('a') packets
|
||||
*/
|
||||
void
|
||||
icb_login(struct icb_session *is, char *group, char *nick, char *client)
|
||||
{
|
||||
char *defgrp = "1";
|
||||
struct icb_group *ig;
|
||||
struct icb_session *s;
|
||||
|
||||
if (!nick || strlen(nick) == 0) {
|
||||
icb_error(is, "Invalid nick");
|
||||
icb_drop(is, NULL);
|
||||
return;
|
||||
}
|
||||
if (!group || strlen(group) == 0)
|
||||
group = defgrp;
|
||||
LIST_FOREACH(ig, &groups, entry) {
|
||||
if (strcmp(ig->name, group) == 0)
|
||||
break;
|
||||
}
|
||||
if (ig == NULL) {
|
||||
if (!creategroups) {
|
||||
icb_error(is, "Invalid group %s", group);
|
||||
icb_drop(is, NULL);
|
||||
return;
|
||||
} else {
|
||||
if ((ig = icb_addgroup(is, group, NULL)) == NULL) {
|
||||
icb_error(is, "Can't create group %s", group);
|
||||
return;
|
||||
}
|
||||
icb_log(NULL, LOG_DEBUG, "%s created group %s",
|
||||
nick, group);
|
||||
}
|
||||
}
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (strcmp(s->nick, nick) == 0) {
|
||||
icb_error(is, "Nick is already in use");
|
||||
icb_drop(is, NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (client && strlen(client) > 0)
|
||||
strlcpy(is->client, client, sizeof is->client);
|
||||
strlcpy(is->nick, nick, sizeof is->nick);
|
||||
is->group = ig;
|
||||
is->login = time(NULL);
|
||||
is->last = getmonotime();
|
||||
|
||||
/* notify group */
|
||||
icb_status_group(ig, NULL, STATUS_SIGNON, "%s (%s@%s) entered group",
|
||||
is->nick, is->client, is->host);
|
||||
|
||||
CLRF(is->flags, ICB_SF_PROTOSENT);
|
||||
SETF(is->flags, ICB_SF_LOGGEDIN);
|
||||
|
||||
LIST_INSERT_HEAD(&ig->sess, is, entry);
|
||||
|
||||
/* acknowledge successful login */
|
||||
icb_sendfmt(is, "%c", ICB_M_LOGIN);
|
||||
|
||||
/* notify user */
|
||||
icb_status(is, STATUS_STATUS, "You are now in group %s%s", ig->name,
|
||||
icb_ismoder(ig, is) ? " as moderator" : "");
|
||||
|
||||
/* send user a topic name */
|
||||
if (strlen(ig->topic) > 0)
|
||||
icb_status(is, STATUS_TOPIC, "Topic for %s is \"%s\"",
|
||||
ig->name, ig->topic);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_groupmsg: handles open message ('b') packets
|
||||
*/
|
||||
void
|
||||
icb_groupmsg(struct icb_session *is, char *msg)
|
||||
{
|
||||
char buf[ICB_MSGSIZE];
|
||||
struct icb_group *ig = is->group;
|
||||
struct icb_session *s;
|
||||
int buflen = 1;
|
||||
|
||||
if (strlen(msg) == 0) {
|
||||
icb_error(is, "Empty message");
|
||||
return;
|
||||
}
|
||||
|
||||
buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c%s", ICB_M_OPEN,
|
||||
is->nick, ICB_M_SEP, msg);
|
||||
buf[0] = buflen;
|
||||
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (s == is)
|
||||
continue;
|
||||
icb_send(s, buf, buflen + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_privmsg: handles personal message ('c') packets
|
||||
*/
|
||||
void
|
||||
icb_privmsg(struct icb_session *is, char *whom, char *msg)
|
||||
{
|
||||
struct icb_group *ig = is->group;
|
||||
struct icb_session *s;
|
||||
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (strcmp(s->nick, whom) == 0)
|
||||
break;
|
||||
}
|
||||
if (!s) {
|
||||
icb_error(is, "No such user %s", whom);
|
||||
return;
|
||||
}
|
||||
icb_sendfmt(s, "%c%s%c%s", ICB_M_PERSONAL, is->nick, ICB_M_SEP, msg);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_command: handles command ('h') packets
|
||||
*/
|
||||
void
|
||||
icb_command(struct icb_session *is, char *cmd, char *arg)
|
||||
{
|
||||
void (*handler)(struct icb_session *, char *);
|
||||
|
||||
if ((handler = icb_cmd_lookup(cmd)) == NULL) {
|
||||
icb_error(is, "Unsupported command: %s", cmd);
|
||||
return;
|
||||
}
|
||||
handler(is, arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_cmdout: sends out command output ('i') packets, called from the
|
||||
* command handlers
|
||||
*/
|
||||
void
|
||||
icb_cmdout(struct icb_session *is, int type, char *outmsg)
|
||||
{
|
||||
char *otype = NULL;
|
||||
|
||||
switch (type) {
|
||||
case CMDOUT_CO:
|
||||
otype = "co";
|
||||
break;
|
||||
case CMDOUT_EC:
|
||||
otype = "ec";
|
||||
break;
|
||||
case CMDOUT_WL:
|
||||
otype = "wl";
|
||||
break;
|
||||
case CMDOUT_WG:
|
||||
otype = "wg";
|
||||
break;
|
||||
default:
|
||||
icb_log(is, LOG_ERR, "unknown cmdout type");
|
||||
return;
|
||||
}
|
||||
icb_sendfmt(is, "%c%s%c%s", ICB_M_CMDOUT, otype, ICB_M_SEP, outmsg);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_status: sends a status message ('d') to the client
|
||||
*/
|
||||
void
|
||||
icb_status(struct icb_session *is, int type, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char buf[ICB_MSGSIZE];
|
||||
int buflen = 1;
|
||||
static const struct {
|
||||
int type;
|
||||
const char *msg;
|
||||
} msgtab[] = {
|
||||
{ STATUS_ARRIVE, "Arrive" },
|
||||
{ STATUS_BOOT, "Boot" },
|
||||
{ STATUS_DEPART, "Depart" },
|
||||
{ STATUS_NAME, "Name" },
|
||||
{ STATUS_NOTIFY, "Notify" },
|
||||
{ STATUS_SIGNON, "Sign-on" },
|
||||
{ STATUS_SIGNOFF, "Sign-off" },
|
||||
{ STATUS_STATUS, "Status" },
|
||||
{ STATUS_TOPIC, "Topic" },
|
||||
{ STATUS_WARNING, "Warning" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
if (type < 0 || type > (int)nitems(msgtab) - 1)
|
||||
return;
|
||||
va_start(ap, fmt);
|
||||
buflen += snprintf(&buf[1], sizeof buf - 1, "%c%s%c", ICB_M_STATUS,
|
||||
msgtab[type].msg, ICB_M_SEP);
|
||||
buflen += vsnprintf(&buf[buflen], sizeof buf - buflen, fmt, ap);
|
||||
buf[0] = buflen;
|
||||
va_end(ap);
|
||||
icb_send(is, buf, buflen + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_status: sends a status message ('d') to the group except of the
|
||||
* "ex" if it's not NULL
|
||||
*/
|
||||
void
|
||||
icb_status_group(struct icb_group *ig, struct icb_session *ex, int type,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
char buf[ICB_MSGSIZE];
|
||||
va_list ap;
|
||||
struct icb_session *s;
|
||||
|
||||
va_start(ap, fmt);
|
||||
(void)vsnprintf(buf, sizeof buf, fmt, ap);
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
if (ex && s == ex)
|
||||
continue;
|
||||
icb_status(s, type, buf);
|
||||
}
|
||||
icb_log(NULL, LOG_DEBUG, "%s", buf);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_error: sends an error message ('e') to the client
|
||||
*/
|
||||
void
|
||||
icb_error(struct icb_session *is, const char *fmt, ...)
|
||||
{
|
||||
char buf[ICB_MSGSIZE];
|
||||
va_list ap;
|
||||
int buflen = 1;
|
||||
|
||||
va_start(ap, fmt);
|
||||
buflen += vsnprintf(&buf[2], sizeof buf - 2, fmt, ap);
|
||||
va_end(ap);
|
||||
buf[0] = ++buflen; /* account for ICB_M_ERROR */
|
||||
buf[1] = ICB_M_ERROR;
|
||||
icb_send(is, buf, buflen + 1);
|
||||
icb_log(is, LOG_DEBUG, "%s", buf + 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_remove: removes a session from the associated group
|
||||
*/
|
||||
void
|
||||
icb_remove(struct icb_session *is, char *reason)
|
||||
{
|
||||
if (is->group) {
|
||||
if (icb_ismoder(is->group, is))
|
||||
(void)icb_pass(is->group, is, NULL);
|
||||
LIST_REMOVE(is, entry);
|
||||
if (reason)
|
||||
icb_status_group(is->group, NULL, STATUS_SIGNOFF,
|
||||
"%s (%s@%s) just left: %s", is->nick, is->client,
|
||||
is->host, reason);
|
||||
else
|
||||
icb_status_group(is->group, NULL, STATUS_SIGNOFF,
|
||||
"%s (%s@%s) just left", is->nick, is->client,
|
||||
is->host);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_addgroup: adds a new group to the list
|
||||
*/
|
||||
struct icb_group *
|
||||
icb_addgroup(struct icb_session *is, char *name, char *mpass)
|
||||
{
|
||||
struct icb_group *ig;
|
||||
|
||||
if ((ig = calloc(1, sizeof *ig)) == NULL)
|
||||
return (NULL);
|
||||
strlcpy(ig->name, name, sizeof ig->name);
|
||||
if (mpass)
|
||||
strlcpy(ig->mpass, mpass, sizeof ig->mpass);
|
||||
if (is)
|
||||
ig->moder = is;
|
||||
LIST_INIT(&ig->sess);
|
||||
LIST_INSERT_HEAD(&groups, ig, entry);
|
||||
return (ig);
|
||||
}
|
||||
|
||||
#ifdef notused
|
||||
/*
|
||||
* icb_delgroup: removes a group from the list
|
||||
*/
|
||||
void
|
||||
icb_delgroup(struct icb_group *ig)
|
||||
{
|
||||
struct icb_session *s;
|
||||
|
||||
/* well, i guess we should kick out participants! ;-) */
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
icb_status(s, STATUS_WARNING, "Group dismissed");
|
||||
s->group = NULL;
|
||||
}
|
||||
LIST_REMOVE(ig, entry);
|
||||
bzero(ig, sizeof ig); /* paranoic thing, obviously */
|
||||
free(ig);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* icb_who: sends a list of users of the specified group (or the current
|
||||
* one otherwise) in the "wl" format
|
||||
*/
|
||||
void
|
||||
icb_who(struct icb_session *is, struct icb_group *ig)
|
||||
{
|
||||
char buf[ICB_MSGSIZE];
|
||||
struct icb_session *s;
|
||||
|
||||
if (!ig)
|
||||
ig = is->group;
|
||||
LIST_FOREACH(s, &ig->sess, entry) {
|
||||
(void)snprintf(buf, sizeof buf,
|
||||
"%c%c%s%c%d%c0%c%d%c%s%c%s%c%s",
|
||||
icb_ismoder(ig, s) ? '*' : ' ', ICB_M_SEP,
|
||||
s->nick, ICB_M_SEP, getmonotime() - s->last,
|
||||
ICB_M_SEP, ICB_M_SEP, s->login, ICB_M_SEP,
|
||||
s->client, ICB_M_SEP, s->host, ICB_M_SEP, " ");
|
||||
icb_cmdout(is, CMDOUT_WL, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_ismoder: checks whether group is moderated by "is"
|
||||
*/
|
||||
int
|
||||
icb_ismoder(struct icb_group *ig, struct icb_session *is)
|
||||
{
|
||||
if (ig->moder && ig->moder == is)
|
||||
return (1);
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_pass: passes moderation of group "ig" from "from" to "to",
|
||||
* returns -1 if "from" is not a moderator, 1 if passed
|
||||
* to "to" and 0 otherwise (no moderator or passed to the
|
||||
* internal bot)
|
||||
*/
|
||||
int
|
||||
icb_pass(struct icb_group *ig, struct icb_session *from,
|
||||
struct icb_session *to)
|
||||
{
|
||||
if (ig->moder && ig->moder != from)
|
||||
return (-1);
|
||||
if (!from && !to)
|
||||
return (-1);
|
||||
ig->moder = to;
|
||||
if (to)
|
||||
icb_status(to, STATUS_NOTIFY, "%s just passed you moderation"
|
||||
" of %s", from ? from->nick : "server", ig->name);
|
||||
icb_status_group(ig, to, STATUS_NOTIFY, "%s has passed moderation "
|
||||
"to %s", from ? from->nick : "server", to ? to->nick : "server");
|
||||
return (1);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_nextfield: advances through a given buffer returning pointer to
|
||||
* the beginning of the icb field or an empty string otherwise
|
||||
*/
|
||||
char *
|
||||
icb_nextfield(char **buf)
|
||||
{
|
||||
char *start = *buf;
|
||||
|
||||
while (*buf && **buf != '\0' && **buf != ICB_M_SEP)
|
||||
(*buf)++;
|
||||
if (*buf && **buf == ICB_M_SEP) {
|
||||
**buf = '\0';
|
||||
(*buf)++;
|
||||
}
|
||||
return (start);
|
||||
}
|
||||
|
||||
/*
|
||||
* icb_sendfmt: formats a string and sends it over
|
||||
*/
|
||||
void
|
||||
icb_sendfmt(struct icb_session *is, const char *fmt, ...)
|
||||
{
|
||||
char buf[ICB_MSGSIZE];
|
||||
va_list ap;
|
||||
int buflen = 1;
|
||||
|
||||
va_start(ap, fmt);
|
||||
buflen += vsnprintf(&buf[1], sizeof buf - 1, fmt, ap);
|
||||
va_end(ap);
|
||||
buf[0] = buflen;
|
||||
icb_send(is, buf, buflen + 1);
|
||||
}
|
132
icb.h
Normal file
132
icb.h
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mike Belopuhov
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <sys/queue.h>
|
||||
|
||||
#define ICB_MSGSIZE 256
|
||||
|
||||
#define ICB_MAXGRPLEN 32
|
||||
#define ICB_MAXNICKLEN 32
|
||||
#define ICB_MAXPASSLEN 32
|
||||
#define ICB_MAXTOPICLEN 160
|
||||
|
||||
#define ICB_M_LOGIN 'a'
|
||||
#define ICB_M_OPEN 'b'
|
||||
#define ICB_M_PERSONAL 'c'
|
||||
#define ICB_M_STATUS 'd'
|
||||
enum {
|
||||
STATUS_ARRIVE,
|
||||
STATUS_BOOT,
|
||||
STATUS_DEPART,
|
||||
STATUS_NAME,
|
||||
STATUS_NOTIFY,
|
||||
STATUS_SIGNON,
|
||||
STATUS_SIGNOFF,
|
||||
STATUS_STATUS,
|
||||
STATUS_TOPIC,
|
||||
STATUS_WARNING
|
||||
};
|
||||
#define ICB_M_ERROR 'e'
|
||||
#define ICB_M_IMPORTANT 'f'
|
||||
#define ICB_M_EXIT 'g'
|
||||
#define ICB_M_COMMAND 'h'
|
||||
#define ICB_M_CMDOUT 'i'
|
||||
enum {
|
||||
CMDOUT_CO,
|
||||
CMDOUT_EC,
|
||||
CMDOUT_WL,
|
||||
CMDOUT_WG,
|
||||
};
|
||||
#define ICB_M_PROTO 'j'
|
||||
#define ICB_M_BEEP 'k'
|
||||
#define ICB_M_PING 'l'
|
||||
#define ICB_M_PONG 'm'
|
||||
#define ICB_M_NOOP 'n'
|
||||
|
||||
#define ICB_M_SEP '\001'
|
||||
|
||||
struct icb_group;
|
||||
|
||||
struct icb_session {
|
||||
char nick[ICB_MAXNICKLEN];
|
||||
char client[ICB_MAXNICKLEN];
|
||||
char host[MAXHOSTNAMELEN];
|
||||
char buffer[ICB_MSGSIZE+1];
|
||||
struct event ev;
|
||||
LIST_ENTRY(icb_session) entry;
|
||||
struct bufferevent *bev;
|
||||
struct icb_group *group;
|
||||
size_t length;
|
||||
time_t login;
|
||||
time_t last;
|
||||
int port;
|
||||
uint32_t flags;
|
||||
#define SETF(t, f) ((t) |= (f))
|
||||
#define CLRF(t, f) ((t) &= ~(f))
|
||||
#define ISSETF(t, f) ((t) & (f))
|
||||
#define ICB_SF_UNKNOWN 0x00
|
||||
#define ICB_SF_PROTOSENT 0x01
|
||||
#define ICB_SF_LOGGEDIN 0x02
|
||||
#define ICB_SF_NOGROUP 0x08
|
||||
#define ICB_SF_MODERATOR 0x10
|
||||
};
|
||||
|
||||
struct icb_group {
|
||||
char name[ICB_MAXGRPLEN];
|
||||
char mpass[ICB_MAXPASSLEN];
|
||||
char topic[ICB_MAXTOPICLEN];
|
||||
LIST_ENTRY(icb_group) entry;
|
||||
LIST_HEAD(, icb_session) sess;
|
||||
struct icb_session *moder;
|
||||
};
|
||||
|
||||
LIST_HEAD(icb_grlist, icb_group) groups;
|
||||
|
||||
struct icbd_callbacks {
|
||||
void (*drop)(struct icb_session *, char *);
|
||||
void (*log)(struct icb_session *, int, const char *, ...);
|
||||
void (*send)(struct icb_session *, char *, ssize_t);
|
||||
};
|
||||
|
||||
#ifndef nitems
|
||||
#define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
|
||||
#endif
|
||||
|
||||
/* cmd.c */
|
||||
void *icb_cmd_lookup(char *);
|
||||
|
||||
/* icb.c */
|
||||
struct icb_group *icb_addgroup(struct icb_session *, char *, char *);
|
||||
void icb_cmdout(struct icb_session *, int, char *);
|
||||
void icb_delgroup(struct icb_group *);
|
||||
void icb_error(struct icb_session *, const char *, ...);
|
||||
void icb_init(struct icbd_callbacks *);
|
||||
void icb_input(struct icb_session *);
|
||||
int icb_ismoder(struct icb_group *, struct icb_session *);
|
||||
int icb_pass(struct icb_group *, struct icb_session *, struct icb_session *);
|
||||
void icb_privmsg(struct icb_session *, char *, char *);
|
||||
void icb_remove(struct icb_session *, char *);
|
||||
void icb_sendfmt(struct icb_session *, const char *, ...);
|
||||
void icb_start(struct icb_session *);
|
||||
void icb_status(struct icb_session *, int, const char *, ...);
|
||||
void icb_status_group(struct icb_group *, struct icb_session *, int ,
|
||||
const char *, ...);
|
||||
void icb_who(struct icb_session *, struct icb_group *);
|
||||
|
||||
/* callbacks from icbd.c */
|
||||
void (*icb_drop)(struct icb_session *, char *);
|
||||
void (*icb_log)(struct icb_session *, int, const char *, ...);
|
||||
void (*icb_send)(struct icb_session *, char *, ssize_t);
|
84
icbd.8
Normal file
84
icbd.8
Normal file
@ -0,0 +1,84 @@
|
||||
.\"
|
||||
.\" Copyright (c) 2009 Mike Belopuhov
|
||||
.\"
|
||||
.\" Permission to use, copy, modify, and distribute this software for any
|
||||
.\" purpose with or without fee is hereby granted, provided that the above
|
||||
.\" copyright notice and this permission notice appear in all copies.
|
||||
.\"
|
||||
.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
.\"
|
||||
.Dd $Mdocdate: April 27 2009 $
|
||||
.Dt ICBD 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm icbd
|
||||
.Nd "Internet Citizen's Band protocol daemon"
|
||||
.Sh SYNOPSIS
|
||||
.Nm icbd
|
||||
.Bk -words
|
||||
.Op Fl 46Cdv
|
||||
.Oo Fl G Ar group1
|
||||
.Ns Oo , Ns Ar ... Oc Oc
|
||||
.Sm off
|
||||
.Oo Ar addr
|
||||
.Ns Op : Ns Ar port
|
||||
.Ns Oo \ Ns Ar ... Oc Oc
|
||||
.Sm on
|
||||
.Ek
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
is a Internet Citizen's Band
|
||||
.Pq ICB
|
||||
protocol daemon which allows peoples to chat online.
|
||||
.Pp
|
||||
Please refer to the document specified in the
|
||||
.Sx SEE ALSO
|
||||
section for more information about ICB protocol.
|
||||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width "-G group"
|
||||
.It Fl 4
|
||||
Bind to IPv4 addresses only.
|
||||
.It Fl 6
|
||||
Bind to IPv6 addresses only.
|
||||
.It Fl C
|
||||
Allow users to create groups.
|
||||
Please note, that everyone creating a new group becomes its moderator.
|
||||
.It Fl d
|
||||
Do not daemonize.
|
||||
If this option is specified,
|
||||
.Nm
|
||||
will run in the foreground and log to
|
||||
.Em stderr .
|
||||
.It Fl G Xo
|
||||
.Ar group1 Ns Op , Ns Ar ...
|
||||
.Xc
|
||||
Instruct
|
||||
.Nm
|
||||
to create specified groups at startup.
|
||||
.It Fl v
|
||||
Produce more verbose output.
|
||||
.El
|
||||
.Pp
|
||||
A list of addresses and/or ports to bind to is specified after all arguments.
|
||||
By default,
|
||||
.Nm
|
||||
will try to listen on all interfaces using
|
||||
.Em icb
|
||||
protocol port number specified in
|
||||
.Em /etc/services .
|
||||
.Sh SEE ALSO
|
||||
.Rs
|
||||
.%R ftp://ftp.icb.net/pub/icb/src/icbd/Protocol.html
|
||||
.%T ICB Protocol
|
||||
.Re
|
||||
.Sh AUTHORS
|
||||
The
|
||||
.Nm
|
||||
program was written by Mike Belopuhov.
|
492
icbd.c
Normal file
492
icbd.c
Normal file
@ -0,0 +1,492 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mike Belopuhov
|
||||
* Copyright (c) 2007 Oleg Safiullin
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef lint
|
||||
static const char rcsid[] = "$ABSD: icbd.c,v 1.22 2010/01/03 20:54:18 kmerz Exp $";
|
||||
#endif /* not lint */
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/queue.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <sysexits.h>
|
||||
#include <syslog.h>
|
||||
#include <pwd.h>
|
||||
#include <login_cap.h>
|
||||
#include <locale.h>
|
||||
#include <netdb.h>
|
||||
#include <event.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
|
||||
#include "icb.h"
|
||||
#include "icbd.h"
|
||||
|
||||
extern char *__progname;
|
||||
|
||||
int creategroups;
|
||||
int foreground;
|
||||
int verbose;
|
||||
|
||||
void usage(void);
|
||||
void getpeerinfo(struct icb_session *);
|
||||
void icbd_accept(int, short, void *);
|
||||
void icbd_drop(struct icb_session *, char *);
|
||||
void icbd_ioerr(struct bufferevent *, short, void *);
|
||||
void icbd_dispatch(struct bufferevent *, void *);
|
||||
void icbd_log(struct icb_session *, int, const char *, ...);
|
||||
void icbd_grplist(char *);
|
||||
void icbd_restrict(void);
|
||||
void icbd_write(struct icb_session *, char *, ssize_t);
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
struct icbd_callbacks ic = { icbd_drop, icbd_log, icbd_write };
|
||||
const char *cause = NULL;
|
||||
int ch, nsocks = 0, save_errno = 0;
|
||||
int inet4 = 0, inet6 = 0;
|
||||
|
||||
/* init group lists before calling icb_addgroup */
|
||||
icb_init(&ic);
|
||||
|
||||
while ((ch = getopt(argc, argv, "46CdG:v")) != -1)
|
||||
switch (ch) {
|
||||
case '4':
|
||||
inet4++;
|
||||
break;
|
||||
case '6':
|
||||
inet6++;
|
||||
break;
|
||||
case 'C':
|
||||
creategroups++;
|
||||
break;
|
||||
case 'd':
|
||||
foreground++;
|
||||
break;
|
||||
case 'G':
|
||||
icbd_grplist(optarg);
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
/* add group "1" as it's a login group for most of the clients */
|
||||
if (icb_addgroup(NULL, "1", NULL) == NULL)
|
||||
err(EX_UNAVAILABLE, NULL);
|
||||
|
||||
if (argc == 0)
|
||||
argc++;
|
||||
|
||||
if (inet4 && inet6)
|
||||
errx(EX_CONFIG, "Can't specify both -4 and -6");
|
||||
|
||||
tzset();
|
||||
(void)setlocale(LC_ALL, "C");
|
||||
|
||||
if (foreground)
|
||||
openlog("icbd", LOG_PID | LOG_PERROR, LOG_DAEMON);
|
||||
else
|
||||
openlog("icbd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
||||
|
||||
if (!foreground && daemon(0, 0) < 0)
|
||||
err(EX_OSERR, NULL);
|
||||
|
||||
(void)event_init();
|
||||
|
||||
for (ch = 0; ch < argc; ch++) {
|
||||
struct addrinfo hints, *res, *res0;
|
||||
struct event *ev;
|
||||
char *addr, *port;
|
||||
int error, s, on = 1;
|
||||
|
||||
addr = port = NULL;
|
||||
if (argv[ch] != NULL) {
|
||||
if (argv[ch][0] != ':')
|
||||
addr = argv[ch];
|
||||
if ((port = strrchr(argv[ch], ':')) != NULL)
|
||||
*port++ = '\0';
|
||||
}
|
||||
|
||||
bzero(&hints, sizeof hints);
|
||||
if (inet4 || inet6)
|
||||
hints.ai_family = inet4 ? PF_INET : PF_INET6;
|
||||
else
|
||||
hints.ai_family = PF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_flags = AI_PASSIVE;
|
||||
if ((error = getaddrinfo(addr, port ? port : "icb", &hints,
|
||||
&res0)) != 0) {
|
||||
syslog(LOG_ERR, "%s", gai_strerror(error));
|
||||
return (EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
for (res = res0; res != NULL; res = res->ai_next) {
|
||||
if ((s = socket(res->ai_family, res->ai_socktype,
|
||||
res->ai_protocol)) < 0) {
|
||||
cause = "socket";
|
||||
save_errno = errno;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
|
||||
sizeof on) < 0) {
|
||||
cause = "SO_REUSEADDR";
|
||||
save_errno = errno;
|
||||
(void)close(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bind(s, res->ai_addr, res->ai_addrlen) < 0) {
|
||||
cause = "bind";
|
||||
save_errno = errno;
|
||||
(void)close(s);
|
||||
continue;
|
||||
}
|
||||
|
||||
(void)listen(s, TCP_BACKLOG);
|
||||
|
||||
if ((ev = calloc(1, sizeof *ev)) == NULL)
|
||||
err(EX_UNAVAILABLE, NULL);
|
||||
event_set(ev, s, EV_READ | EV_PERSIST, icbd_accept, ev);
|
||||
if (event_add(ev, NULL) < 0) {
|
||||
syslog(LOG_ERR, "event_add: %m");
|
||||
return (EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
nsocks++;
|
||||
}
|
||||
|
||||
freeaddrinfo(res0);
|
||||
}
|
||||
|
||||
if (nsocks == 0) {
|
||||
errno = save_errno;
|
||||
syslog(LOG_ERR, "%s: %m", cause);
|
||||
return (EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
/* start a dns resolver thread */
|
||||
icbd_dns_init();
|
||||
|
||||
if (!foreground)
|
||||
icbd_restrict();
|
||||
|
||||
(void)signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
(void)event_dispatch();
|
||||
|
||||
syslog(LOG_ERR, "event_dispatch: %m");
|
||||
|
||||
return (EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
void
|
||||
icbd_dns(int fd, short event, void *arg)
|
||||
{
|
||||
struct icb_session *is = arg;
|
||||
|
||||
if (event != EV_READ)
|
||||
return;
|
||||
|
||||
if (verbose)
|
||||
syslog(LOG_DEBUG, "icbd_dns");
|
||||
|
||||
if (read(fd, is->host, sizeof is->host) < 0)
|
||||
syslog(LOG_ERR, "read: %m");
|
||||
|
||||
is->host[sizeof is->host - 1] = '\0';
|
||||
|
||||
if (verbose)
|
||||
syslog(LOG_DEBUG, "icbd_dns: resolved %s", is->host);
|
||||
}
|
||||
|
||||
void
|
||||
icbd_accept(int fd, short event __attribute__((__unused__)),
|
||||
void *arg __attribute__((__unused__)))
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
struct icb_session *is;
|
||||
socklen_t ss_len = sizeof ss;
|
||||
int s;
|
||||
|
||||
ss.ss_len = ss_len;
|
||||
if ((s = accept(fd, (struct sockaddr *)&ss, &ss_len)) < 0) {
|
||||
syslog(LOG_ERR, "accept: %m");
|
||||
return;
|
||||
}
|
||||
if ((is = calloc(1, sizeof *is)) == NULL) {
|
||||
syslog(LOG_ERR, "calloc: %m");
|
||||
(void)close(s);
|
||||
return;
|
||||
}
|
||||
if ((is->bev = bufferevent_new(s, icbd_dispatch, NULL, icbd_ioerr,
|
||||
is)) == NULL) {
|
||||
syslog(LOG_ERR, "bufferevent_new: %m");
|
||||
(void)close(s);
|
||||
free(is);
|
||||
return;
|
||||
}
|
||||
if (bufferevent_enable(is->bev, EV_READ)) {
|
||||
syslog(LOG_ERR, "bufferevent_enable: %m");
|
||||
(void)close(s);
|
||||
bufferevent_free(is->bev);
|
||||
free(is);
|
||||
return;
|
||||
}
|
||||
|
||||
/* save host information */
|
||||
getpeerinfo(is);
|
||||
|
||||
/* start icb conversation */
|
||||
icb_start(is);
|
||||
}
|
||||
|
||||
__dead void
|
||||
usage(void)
|
||||
{
|
||||
(void)fprintf(stderr, "usage: %s [-46Cdv] [-G group1[,group2,...]] "
|
||||
"[[addr][:port] ...]\n", __progname);
|
||||
exit(EX_USAGE);
|
||||
}
|
||||
|
||||
/*
|
||||
* bufferevent functions
|
||||
*/
|
||||
|
||||
void
|
||||
icbd_dispatch(struct bufferevent *bev, void *arg)
|
||||
{
|
||||
struct icb_session *is = (struct icb_session *)arg;
|
||||
|
||||
if (is->length == 0) {
|
||||
bzero(is->buffer, sizeof is->buffer);
|
||||
/* read length */
|
||||
(void)bufferevent_read(bev, is->buffer, 1);
|
||||
/* we're about to read the whole packet */
|
||||
is->length = (size_t)(unsigned char)is->buffer[0];
|
||||
if (is->length == 0) {
|
||||
icbd_drop(is, "invalid packet");
|
||||
return;
|
||||
}
|
||||
if (EVBUFFER_LENGTH(EVBUFFER_INPUT(bev)) < is->length) {
|
||||
/* set watermark to the expected length */
|
||||
bufferevent_setwatermark(bev, EV_READ, is->length, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
(void)bufferevent_read(bev, &is->buffer[1], is->length);
|
||||
#ifdef DEBUG
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("-> read from %s:%d:\n", is->host, is->port);
|
||||
for (i = 0; i < (int)is->length + 1; i++)
|
||||
printf(" %02x", (unsigned char)is->buffer[i]);
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
||||
icb_input(is);
|
||||
is->length = 0;
|
||||
}
|
||||
|
||||
void
|
||||
icbd_write(struct icb_session *is, char *buf, ssize_t size)
|
||||
{
|
||||
if (bufferevent_write(is->bev, buf, size) == -1)
|
||||
syslog(LOG_ERR, "bufferevent_write: %m");
|
||||
#ifdef DEBUG
|
||||
{
|
||||
int i;
|
||||
|
||||
printf("-> wrote to %s:%d:\n", is->host, is->port);
|
||||
for (i = 0; i < size; i++)
|
||||
printf(" %02x", (unsigned char)buf[i]);
|
||||
printf("\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
icbd_drop(struct icb_session *is, char *reason)
|
||||
{
|
||||
if (reason) {
|
||||
icb_remove(is, reason);
|
||||
icbd_log(is, LOG_DEBUG, reason);
|
||||
} else
|
||||
icb_remove(is, NULL);
|
||||
(void)evbuffer_write(EVBUFFER_OUTPUT(is->bev), EVBUFFER_FD(is->bev));
|
||||
(void)close(EVBUFFER_FD(is->bev));
|
||||
bufferevent_free(is->bev);
|
||||
free(is);
|
||||
}
|
||||
|
||||
void
|
||||
icbd_ioerr(struct bufferevent *bev __attribute__((__unused__)), short what,
|
||||
void *arg)
|
||||
{
|
||||
struct icb_session *is = (struct icb_session *)arg;
|
||||
|
||||
if (what & EVBUFFER_TIMEOUT)
|
||||
icbd_drop(is, "timeout");
|
||||
else if (what & EVBUFFER_EOF)
|
||||
icbd_drop(is, NULL);
|
||||
else if (what & EVBUFFER_ERROR)
|
||||
icbd_drop(is, (what & EVBUFFER_READ) ? "read error" :
|
||||
"write error");
|
||||
}
|
||||
|
||||
void
|
||||
icbd_log(struct icb_session *is, int level, const char *fmt, ...)
|
||||
{
|
||||
char buf[512];
|
||||
va_list ap;
|
||||
|
||||
if (!verbose && level == LOG_DEBUG)
|
||||
return;
|
||||
|
||||
va_start(ap, fmt);
|
||||
(void)vsnprintf(buf, sizeof buf, fmt, ap);
|
||||
va_end(ap);
|
||||
if (is)
|
||||
syslog(level, "%s:%u: %s", is->host, is->port, buf);
|
||||
else
|
||||
syslog(level, "%s", buf);
|
||||
}
|
||||
|
||||
void
|
||||
icbd_restrict(void)
|
||||
{
|
||||
struct stat sb;
|
||||
struct passwd *pw;
|
||||
|
||||
if ((pw = getpwnam(ICBD_USER)) == NULL) {
|
||||
syslog(LOG_ERR, "No passwd entry for %s", ICBD_USER);
|
||||
exit(EX_NOUSER);
|
||||
}
|
||||
|
||||
if (setusercontext(NULL, pw, pw->pw_uid,
|
||||
LOGIN_SETALL & ~LOGIN_SETUSER) < 0) {
|
||||
syslog(LOG_ERR, "%s: %m", pw->pw_name);
|
||||
exit(EX_NOPERM);
|
||||
}
|
||||
|
||||
if (stat(pw->pw_dir, &sb) == -1) {
|
||||
syslog(LOG_ERR, "%s: %m", pw->pw_name);
|
||||
exit(EX_NOPERM);
|
||||
}
|
||||
|
||||
if (sb.st_uid != 0 || (sb.st_mode & (S_IWGRP|S_IWOTH)) != 0) {
|
||||
syslog(LOG_ERR, "bad directory permissions");
|
||||
exit(EX_NOPERM);
|
||||
}
|
||||
|
||||
if (chroot(pw->pw_dir) < 0) {
|
||||
syslog(LOG_ERR, "%s: %m", pw->pw_dir);
|
||||
exit(EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
if (setuid(pw->pw_uid) < 0) {
|
||||
syslog(LOG_ERR, "%d: %m", pw->pw_uid);
|
||||
exit(EX_NOPERM);
|
||||
}
|
||||
|
||||
if (chdir("/") < 0) {
|
||||
syslog(LOG_ERR, "/: %m");
|
||||
exit(EX_UNAVAILABLE);
|
||||
}
|
||||
|
||||
(void)setproctitle("icbd");
|
||||
}
|
||||
|
||||
void
|
||||
icbd_grplist(char *list)
|
||||
{
|
||||
char *s, *s1, *s2;
|
||||
int last = 0;
|
||||
|
||||
if (!list || strlen(list) == 0)
|
||||
return;
|
||||
|
||||
/* "group1[:pass1][,group2[:pass2],...]" */
|
||||
s = list;
|
||||
s1 = s2 = NULL;
|
||||
while (!last && s) {
|
||||
if ((s1 = strchr(s, ',')) != NULL)
|
||||
*s1 = '\0';
|
||||
else {
|
||||
last = 1;
|
||||
s1 = s;
|
||||
}
|
||||
if ((s2 = strchr(s, ':')) != NULL)
|
||||
*s2 = '\0';
|
||||
if (icb_addgroup(NULL, s, s2 ? ++s2 : NULL) == NULL)
|
||||
err(EX_UNAVAILABLE, NULL);
|
||||
s = ++s1;
|
||||
s1 = s2 = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
time_t
|
||||
getmonotime(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) {
|
||||
syslog(LOG_ERR, "%m");
|
||||
exit(EX_OSERR);
|
||||
}
|
||||
return (ts.tv_sec);
|
||||
}
|
||||
|
||||
void
|
||||
getpeerinfo(struct icb_session *is)
|
||||
{
|
||||
struct sockaddr_storage ss;
|
||||
socklen_t ss_len = sizeof ss;
|
||||
|
||||
bzero(&ss, sizeof ss);
|
||||
if (getpeername(EVBUFFER_FD(is->bev), (struct sockaddr *)&ss,
|
||||
&ss_len) != 0)
|
||||
return;
|
||||
|
||||
is->port = 0;
|
||||
switch (ss.ss_family) {
|
||||
case AF_INET:
|
||||
is->port = ntohs(((struct sockaddr_in *)&ss)->sin_port);
|
||||
break;
|
||||
|
||||
case AF_INET6:
|
||||
is->port = ntohs(((struct sockaddr_in6 *)&ss)->sin6_port);
|
||||
break;
|
||||
}
|
||||
|
||||
dns_rresolv(is, &ss);
|
||||
}
|
32
icbd.h
Normal file
32
icbd.h
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2009 Mike Belopuhov
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#define ICBD_USER "_icbd"
|
||||
|
||||
#define TCP_BACKLOG 5
|
||||
|
||||
#define EVBUFFER_FD(x) (EVENT_FD(&(x)->ev_read))
|
||||
|
||||
extern int verbose;
|
||||
|
||||
/* icbd.c */
|
||||
time_t getmonotime(void);
|
||||
|
||||
/* dns.c */
|
||||
struct sockaddr_storage;
|
||||
int icbd_dns_init(void);
|
||||
void icbd_dns(int, short, void *);
|
||||
int dns_rresolv(struct icb_session *, struct sockaddr_storage *);
|
Loading…
x
Reference in New Issue
Block a user