NetBSD/usr.sbin/isdn/isdnd/monitor.c

1282 lines
33 KiB
C

/* $NetBSD: monitor.c,v 1.16 2009/04/16 05:56:32 lukem Exp $ */
/*-
* Copyright (c) 2002 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Martin Husemann <martin@NetBSD.org>.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "isdnd.h"
#ifndef I4B_EXTERNAL_MONITOR
/*
* dummy version of routines needed by config file parser
* (config files should be valid with and without external montioring
* support compiled into the daemon)
*/
void monitor_clear_rights()
{ }
int monitor_start_rights(const char *clientspec)
{ return I4BMAR_OK; }
void monitor_add_rights(int rights_mask)
{ }
void monitor_fixup_rights()
{ }
#else
#include "monitor.h"
#include <sys/socket.h>
#include <sys/un.h>
#ifndef I4B_NOTCPIP_MONITOR
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
static TAILQ_HEAD(rights_q, monitor_rights) rights = TAILQ_HEAD_INITIALIZER(rights);
static struct monitor_rights * local_rights = NULL; /* entry for local socket */
/* for each active monitor connection we have one of this: */
struct monitor_connection {
TAILQ_ENTRY(monitor_connection) connections;
int sock; /* socket for this connection */
int rights; /* active rights for this connection */
int events; /* bitmask of events client is interested in */
char source[FILENAME_MAX];
};
static TAILQ_HEAD(connections_tq, monitor_connection) connections = TAILQ_HEAD_INITIALIZER(connections);
/* local prototypes */
struct monitor_rights * monitor_next_rights(const struct monitor_rights *r);
static int cmp_rights(const struct monitor_rights *pa, const struct monitor_rights *pb);
static int monitor_command(struct monitor_connection *con, int fd, int rights);
static void cmd_dump_rights(int fd, int rights, u_int8_t *cmd, const char * source);
static void cmd_dump_mcons(int fd, int rights, u_int8_t *cmd, const char * source);
static void cmd_reread_cfg(int fd, int rights, u_int8_t *cmd, const char * source);
static void cmd_hangup(int fd, int rights, u_int8_t *cmd, const char * source);
static void monitor_broadcast(int mask, u_int8_t *pkt, size_t bytes);
static int anybody(int mask);
static void hangup_channel(int controller, int channel, const char *source);
static ssize_t sock_read(int fd, void *buf, size_t nbytes);
static ssize_t sock_write(int fd, void *buf, size_t nbytes);
/*
* Due to the way we structure config files, the rights for an external
* monitor might be stated in multiple steps. First a call to
* monitor_start_rights opens an entry. Further (optional) calls to
* montior_add_rights assemble additional rights for this "current"
* entry. When closing the sys-file section of the config file, the
* "current" entry becomes invalid.
*/
static struct monitor_rights * cur_add_entry = NULL;
/*---------------------------------------------------------------------------
* Initialize the monitor server module. This affects only active
* connections, the access rights are not modified here!
*---------------------------------------------------------------------------*/
void
monitor_init(void)
{
struct monitor_connection * con;
accepted = 0;
while ((con = TAILQ_FIRST(&connections)) != NULL)
{
TAILQ_REMOVE(&connections, con, connections);
free(con);
}
}
/*---------------------------------------------------------------------------
* Prepare for exit
*---------------------------------------------------------------------------*/
void
monitor_exit(void)
{
struct monitor_connection *c;
/* Close all open connections. */
while((c = TAILQ_FIRST(&connections)) != NULL) {
close(c->sock);
TAILQ_REMOVE(&connections, c, connections);
free(c);
}
}
/*---------------------------------------------------------------------------
* Initialize access rights. No active connections are affected!
*---------------------------------------------------------------------------*/
void
monitor_clear_rights(void)
{
struct monitor_rights *r;
while ((r = TAILQ_FIRST(&rights)) != NULL) {
TAILQ_REMOVE(&rights, r, list);
free(r);
}
cur_add_entry = NULL;
local_rights = NULL;
}
/*---------------------------------------------------------------------------
* Add an entry to the access lists. The clientspec either is
* the name of the local socket or a host- or networkname or
* numeric ip/host-bit-len spec.
*---------------------------------------------------------------------------*/
int
monitor_start_rights(const char *clientspec)
{
struct monitor_rights r;
/* initialize the new rights entry */
memset(&r, 0, sizeof r);
/* check clientspec */
if (*clientspec == '/')
{
struct sockaddr_un sa;
/* this is a local socket spec, check if we already have one */
if (local_rights != NULL)
return I4BMAR_DUP;
/* does it fit in a local socket address? */
if (strlen(clientspec) > sizeof sa.sun_path)
return I4BMAR_LENGTH;
r.local = 1;
strlcpy(r.name, clientspec, sizeof(r.name));
#ifndef I4B_NOTCPIP_MONITOR
}
else
{
/* remote entry, parse host/net and cidr */
struct monitor_rights * rp;
char hostname[FILENAME_MAX];
char *p;
p = strchr(clientspec, '/');
if (!p)
{
struct hostent *host;
u_int32_t hn;
/* must be a host spec */
r.mask = ~0;
host = gethostbyname(clientspec);
if (!host)
return I4BMAR_NOIP;
memcpy(&hn, host->h_addr_list[0], sizeof hn);
r.net = (u_int32_t)ntohl(hn);
}
else if (p[1])
{
/* must be net/cidr spec */
int l;
struct netent *net;
u_int32_t s = ~0U;
int num = strtol(p+1, NULL, 10);
if (num < 0 || num > 32)
return I4BMAR_CIDR;
s >>= num;
s ^= ~0U;
l = p - clientspec;
if (l >= (int)sizeof hostname)
return I4BMAR_LENGTH;
strncpy(hostname, clientspec, l);
hostname[l] = '\0';
net = getnetbyname(hostname);
if (net == NULL)
r.net = (u_int32_t)inet_network(hostname);
else
r.net = (u_int32_t)net->n_net;
r.mask = s;
r.net &= s;
}
else
{
return I4BMAR_CIDR;
}
/* check for duplicate entry */
for (rp = TAILQ_FIRST(&rights); rp != NULL; rp = TAILQ_NEXT(rp, list))
{
if (rp->mask == r.mask &&
rp->net == r.net &&
rp->local == r.local)
{
return I4BMAR_DUP;
}
}
#endif
}
r.rights = 0;
/* entry ok, add it to the collection */
cur_add_entry = malloc(sizeof(r));
memcpy(cur_add_entry, &r, sizeof(r));
TAILQ_INSERT_TAIL(&rights, cur_add_entry, list);
if (r.local)
local_rights = cur_add_entry;
DBGL(DL_RCCF, (logit(LL_DBG, "system: monitor = %s", clientspec)));
return I4BMAR_OK;
}
/*---------------------------------------------------------------------------
* Add rights to the currently constructed entry - if any.
*---------------------------------------------------------------------------*/
void
monitor_add_rights(int rights_mask)
{
if (cur_add_entry == NULL)
return; /* noone under construction */
cur_add_entry->rights |= rights_mask;
DBGL(DL_RCCF, (logit(LL_DBG, "system: monitor-access = 0x%x", rights_mask)));
}
/*---------------------------------------------------------------------------
* All rights have been added now. Sort the to get most specific
* host/net masks first, so we can travel the list and use the first
* match for actual rights.
*---------------------------------------------------------------------------*/
void
monitor_fixup_rights(void)
{
struct monitor_rights * cur, * test, * next;
/* no more rights may be added to the current entry */
cur_add_entry = NULL;
/* sort the rights */
for (next = NULL, cur = TAILQ_FIRST(&rights); cur != NULL; cur = next)
{
next = TAILQ_NEXT(cur, list);
for (test = TAILQ_FIRST(&rights); test != NULL && test != cur; test = TAILQ_NEXT(test, list))
{
if (cmp_rights(cur, test) > 0) {
/* move cur up the list and insert before test */
TAILQ_REMOVE(&rights, cur, list);
if (test == TAILQ_FIRST(&rights))
TAILQ_INSERT_HEAD(&rights, cur, list);
else
TAILQ_INSERT_BEFORE(test, cur, list);
break;
}
}
}
}
/*---------------------------------------------------------------------------
* comparator for rights
*---------------------------------------------------------------------------*/
static int
cmp_rights(const struct monitor_rights *pa, const struct monitor_rights *pb)
{
u_int32_t mask;
/* local sorts first */
if (pa->local)
return -1;
/* which is the less specific netmask? */
mask = pa->mask;
if ((pb->mask & mask) == 0)
mask = pb->mask;
/* are the entries disjunct? */
if ((pa->net & mask) != (pb->net & mask))
{
/* simply compare net part of address */
return ((pa->net & mask) < (pb->net & mask)) ? -1 : 1;
}
/* One entry is part of the others net. We already now "mask" is
* the netmask of the less specific (i.e. greater) one */
return (pa->mask == mask) ? 1 : -1;
}
#ifndef I4B_NOTCPIP_MONITOR
/*---------------------------------------------------------------------------
* Check if access rights for a remote socket are specified and
* create this socket. Return -1 otherwise.
*---------------------------------------------------------------------------*/
int
monitor_create_remote_socket(int portno)
{
struct sockaddr_in sa;
int val;
int remotesockfd;
remotesockfd = socket(AF_INET, SOCK_STREAM, 0);
if (remotesockfd == -1)
{
logit(LL_MER, "could not create remote monitor socket: %s", strerror(errno));
return(-1);
}
val = 1;
if (setsockopt(remotesockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val))
{
logit(LL_MER, "could not setsockopt: %s", strerror(errno));
return(-1);
}
memset(&sa, 0, sizeof sa);
sa.sin_len = sizeof sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(portno);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(remotesockfd, (struct sockaddr *)&sa, sizeof sa) == -1)
{
logit(LL_MER, "could not bind remote monitor socket to port %d: %s", portno, strerror(errno));
return(-1);
}
if (listen(remotesockfd, 0))
{
logit(LL_MER, "could not listen on monitor socket: %s", strerror(errno));
return(-1);
}
return(remotesockfd);
}
#endif
/*---------------------------------------------------------------------------
* Check if access rights for a local socket are specified and
* create this socket. Return -1 otherwise.
*---------------------------------------------------------------------------*/
int
monitor_create_local_socket(void)
{
int s;
struct sockaddr_un sa;
/* check for a local entry */
if (local_rights == NULL)
return(-1);
/* create and setup socket */
s = socket(AF_LOCAL, SOCK_STREAM, 0);
if (s == -1)
{
logit(LL_MER, "could not create local monitor socket, errno = %d", errno);
return(-1);
}
unlink(local_rights->name);
memset(&sa, 0, sizeof sa);
sa.sun_len = sizeof sa;
sa.sun_family = AF_LOCAL;
strlcpy(sa.sun_path, local_rights->name, sizeof(sa.sun_path));
if (bind(s, (struct sockaddr *)&sa, SUN_LEN(&sa)))
{
logit(LL_MER, "could not bind local monitor socket [%s], errno = %d", local_rights->name, errno);
return(-1);
}
chmod(local_rights->name, 0600);
if (listen(s, 0))
{
logit(LL_MER, "could not listen on local monitor socket, errno = %d", errno);
return(-1);
}
return(s);
}
/*---------------------------------------------------------------------------
* Prepare a fd_set for a select call. Add all our local
* filedescriptors to the set, increment max_fd if appropriate.
*---------------------------------------------------------------------------*/
void
monitor_prepselect(fd_set *selset, int *max_fd)
{
struct monitor_connection * con;
for (con = TAILQ_FIRST(&connections); con != NULL; con = TAILQ_NEXT(con, connections))
{
int fd = con->sock;
if (fd > *max_fd)
*max_fd = fd;
FD_SET(fd, selset);
}
}
/*---------------------------------------------------------------------------
* Check if the result from a select call indicates something
* to do for us.
*---------------------------------------------------------------------------*/
void
monitor_handle_input(fd_set *selset)
{
struct monitor_connection * con, * next;
for (next = NULL, con = TAILQ_FIRST(&connections); con != NULL; con = next)
{
int fd = con->sock;
next = TAILQ_NEXT(con, connections);
if (FD_ISSET(fd, selset))
{
/* handle command from this client */
if (monitor_command(con, fd, con->rights) != 0)
{
/* broken or closed connection */
char source[FILENAME_MAX];
strlcpy(source, con->source, sizeof(source));
TAILQ_REMOVE(&connections, con, connections);
free(con);
logit(LL_DMN, "monitor closed from %s", source );
}
}
}
/* all connections gone? */
if (TAILQ_FIRST(&connections) == NULL)
accepted = 0;
}
/*---------------------------------------------------------------------------
* Try new incoming connection on the given socket.
* Setup client descriptor and send initial data.
*---------------------------------------------------------------------------*/
void
monitor_handle_connect(int sockfd, int is_local)
{
struct monitor_connection *con;
struct monitor_rights *rp;
struct isdn_ctrl_state *ctrl;
struct cfg_entry *cfe;
int n;
#ifndef I4B_NOTCPIP_MONITOR
struct sockaddr_in ia;
u_int32_t ha = 0;
#endif
struct sockaddr_un ua;
socklen_t s;
u_int8_t idata[I4B_MON_IDATA_SIZE];
int fd = -1, r_mask, t_events;
char source[FILENAME_MAX];
/* accept the connection */
if (is_local)
{
s = sizeof ua;
fd = accept(sockfd, (struct sockaddr *)&ua, &s);
strlcpy(source, "local", sizeof(source));
#ifndef I4B_NOTCPIP_MONITOR
}
else
{
struct hostent *hp;
s = sizeof ia;
fd = accept(sockfd, (struct sockaddr *)&ia, &s);
hp = gethostbyaddr((char *)&ia.sin_addr, 4, AF_INET);
if (hp == NULL)
snprintf(source, sizeof source, "%s (%s)", inet_ntoa(ia.sin_addr), inet_ntoa(ia.sin_addr));
else
snprintf(source, sizeof source, "%s (%s)", hp->h_name, inet_ntoa(ia.sin_addr));
memcpy(&ha, &ia.sin_addr.s_addr, sizeof ha);
ha = ntohl(ha);
#endif
}
/* check the access rights of this connection */
r_mask = 0;
for (rp = TAILQ_FIRST(&rights); rp != NULL; rp = TAILQ_NEXT(rp, list))
{
if (rp->local)
{
if (is_local)
{
r_mask = rp->rights;
break;
}
#ifndef I4B_NOTCPIP_MONITOR
}
else
{
if ((ha & rp->mask) == rp->net)
{
r_mask = rp->rights;
break;
}
#endif
}
}
if (r_mask == 0)
{
/* no rights - go away */
logit(LL_MER, "monitor access denied from %s", source);
close(fd);
return;
}
accepted = 1;
con = malloc(sizeof(struct monitor_connection));
memset(con, 0, sizeof *con);
TAILQ_INSERT_TAIL(&connections, con, connections);
con->sock = fd;
con->rights = r_mask;
strlcpy(con->source, source, sizeof(con->source));
logit(LL_DMN, "monitor opened from %s rights 0x%x", source, r_mask);
/* send initial data */
I4B_PREP_CMD(idata, I4B_MON_IDATA_CODE);
I4B_PUT_2B(idata, I4B_MON_IDATA_VERSMAJOR, MPROT_VERSION);
I4B_PUT_2B(idata, I4B_MON_IDATA_VERSMINOR, MPROT_REL);
n = count_ctrl_states();
I4B_PUT_2B(idata, I4B_MON_IDATA_NUMCTRL, n);
n = count_cfg_entries();
I4B_PUT_2B(idata, I4B_MON_IDATA_NUMENTR, n);
I4B_PUT_4B(idata, I4B_MON_IDATA_CLACCESS, r_mask);
if ((sock_write(fd, idata, sizeof idata)) == -1)
{
logit(LL_MER, "monitor_handle_connect: sock_write 1 error - %s", strerror(errno));
}
for (ctrl = get_first_ctrl_state(); ctrl; ctrl = NEXT_CTRL(ctrl)) {
u_int8_t ictrl[I4B_MON_ICTRL_SIZE];
char ctrl_desc[100];
snprintf(ctrl_desc, sizeof(ctrl_desc), "%s: %s",
ctrl->device_name, ctrl->controller);
I4B_PREP_CMD(ictrl, I4B_MON_ICTRL_CODE);
I4B_PUT_STR(ictrl, I4B_MON_ICTRL_NAME, ctrl_desc);
I4B_PUT_2B(ictrl, I4B_MON_ICTRL_BUSID, ctrl->isdnif);
I4B_PUT_4B(ictrl, I4B_MON_ICTRL_FLAGS, 0);
I4B_PUT_2B(ictrl, I4B_MON_ICTRL_NCHAN, 2);
if ((sock_write(fd, ictrl, sizeof ictrl)) == -1)
{
logit(LL_MER, "monitor_handle_connect: sock_write 2 error - %s", strerror(errno));
}
}
/* send device names from entries */
for (cfe = get_first_cfg_entry(); cfe; cfe = NEXT_CFE(cfe)) {
u_int8_t ictrl[I4B_MON_IDEV_SIZE];
char nbuf[64];
snprintf(nbuf, sizeof(nbuf), "%s%d ", cfe->usrdevicename, cfe->usrdeviceunit);
I4B_PREP_CMD(ictrl, I4B_MON_IDEV_CODE);
/*XXX*/ I4B_PUT_2B(ictrl, I4B_MON_IDEV_STATE, 1);
I4B_PUT_STR(ictrl, I4B_MON_IDEV_NAME, nbuf);
if ((sock_write(fd, ictrl, sizeof ictrl)) == -1)
{
logit(LL_MER, "monitor_handle_connect: sock_write 3 error - %s", strerror(errno));
}
}
/*XXX*/ t_events = con->events;
/*XXX*/ con->events = -1;
/* current state of controller(s) */
for (ctrl = get_first_ctrl_state(); ctrl; ctrl = NEXT_CTRL(ctrl)) {
monitor_evnt_tei(ctrl->isdnif, ctrl->tei);
monitor_evnt_l12stat(ctrl->isdnif, LAYER_ONE, ctrl->l1stat);
monitor_evnt_l12stat(ctrl->isdnif, LAYER_TWO, ctrl->l2stat);
}
/* current state of entries */
for (cfe = get_first_cfg_entry(); cfe; cfe = NEXT_CFE(cfe)) {
if (cfe->state == ST_CONNECTED)
{
monitor_evnt_connect(cfe);
monitor_evnt_acct(cfe);
monitor_evnt_charge(cfe, cfe->charge, 1);
}
}
/*XXX*/ con->events = t_events;
}
/*---------------------------------------------------------------------------
* dump all monitor rights
*---------------------------------------------------------------------------*/
static void
cmd_dump_rights(int fd, int r_mask, u_int8_t *cmd, const char *source)
{
struct monitor_rights * r;
int num_rights;
u_int8_t drini[I4B_MON_DRINI_SIZE];
u_int8_t dr[I4B_MON_DR_SIZE];
for (num_rights = 0, r = TAILQ_FIRST(&rights); r != NULL; r = TAILQ_NEXT(r, list))
num_rights++;
I4B_PREP_EVNT(drini, I4B_MON_DRINI_CODE);
I4B_PUT_2B(drini, I4B_MON_DRINI_COUNT, num_rights);
if ((sock_write(fd, drini, sizeof drini)) == -1)
{
logit(LL_MER, "cmd_dump_rights: sock_write 1 error - %s", strerror(errno));
}
for (r = TAILQ_FIRST(&rights); r != NULL; r = TAILQ_NEXT(r, list))
{
I4B_PREP_EVNT(dr, I4B_MON_DR_CODE);
I4B_PUT_4B(dr, I4B_MON_DR_RIGHTS, r->rights);
I4B_PUT_4B(dr, I4B_MON_DR_NET, r->net);
I4B_PUT_4B(dr, I4B_MON_DR_MASK, r->mask);
I4B_PUT_1B(dr, I4B_MON_DR_LOCAL, r->local);
if ((sock_write(fd, dr, sizeof dr)) == -1)
{
logit(LL_MER, "cmd_dump_rights: sock_write 2 error - %s", strerror(errno));
}
}
}
/*---------------------------------------------------------------------------
* rescan config file
*---------------------------------------------------------------------------*/
static void
cmd_reread_cfg(int fd, int _rights, u_int8_t *cmd, const char * source)
{
rereadconfig(42);
}
/*---------------------------------------------------------------------------
* drop one connection
*---------------------------------------------------------------------------*/
static void
cmd_hangup(int fd, int _rights, u_int8_t *cmd, const char * source)
{
int channel = I4B_GET_4B(cmd, I4B_MON_HANGUP_CHANNEL);
int ctrl = I4B_GET_4B(cmd, I4B_MON_HANGUP_CTRL);
hangup_channel(ctrl, channel, source);
}
/*---------------------------------------------------------------------------
* dump all active monitor connections
*---------------------------------------------------------------------------*/
static void
cmd_dump_mcons(int fd, int _rights, u_int8_t *cmd, const char * source)
{
int num_connections;
struct monitor_connection *con;
u_int8_t dcini[I4B_MON_DCINI_SIZE];
for (num_connections = 0, con = TAILQ_FIRST(&connections); con != NULL; con = TAILQ_NEXT(con, connections))
num_connections++;
I4B_PREP_EVNT(dcini, I4B_MON_DCINI_CODE);
I4B_PUT_2B(dcini, I4B_MON_DCINI_COUNT, num_connections);
if ((sock_write(fd, dcini, sizeof dcini)) == -1)
{
logit(LL_MER, "cmd_dump_mcons: sock_write 1 error - %s", strerror(errno));
}
for (con = TAILQ_FIRST(&connections); con != NULL; con = TAILQ_NEXT(con, connections))
{
#ifndef I4B_NOTCPIP_MONITOR
socklen_t namelen;
struct sockaddr_in name;
#endif
u_int8_t dc[I4B_MON_DC_SIZE];
I4B_PREP_EVNT(dc, I4B_MON_DC_CODE);
I4B_PUT_4B(dc, I4B_MON_DC_RIGHTS, con->rights);
#ifndef I4B_NOTCPIP_MONITOR
namelen = sizeof name;
if (getpeername(con->sock, (struct sockaddr*)&name, &namelen) == 0)
memcpy(dc+I4B_MON_DC_WHO, &name.sin_addr, sizeof name.sin_addr);
#endif
if ((sock_write(fd, dc, sizeof dc)) == -1)
{
logit(LL_MER, "cmd_dump_mcons: sock_write 2 error - %s", strerror(errno));
}
}
}
/*---------------------------------------------------------------------------
* Handle a command from the given socket. The client
* has rights as specified in the rights parameter.
* Return non-zero if connection is closed.
*---------------------------------------------------------------------------*/
static int
monitor_command(struct monitor_connection * con, int fd, int mcrights)
{
char cmd[I4B_MAX_MON_CLIENT_CMD];
u_int code;
/* command dispatch table */
typedef void (*cmd_func_t)(int fd, int rights, u_int8_t *cmd, const char *source);
static struct {
cmd_func_t call; /* function to execute */
u_int rights; /* necessary rights */
} cmd_tab[] =
{
/* 0 */ { NULL, 0 },
/* 1 */ { cmd_dump_rights, I4B_CA_COMMAND_FULL },
/* 2 */ { cmd_dump_mcons, I4B_CA_COMMAND_FULL },
/* 3 */ { cmd_reread_cfg, I4B_CA_COMMAND_FULL },
/* 4 */ { cmd_hangup, I4B_CA_COMMAND_FULL },
};
#define NUMCMD (sizeof cmd_tab / sizeof cmd_tab[0])
int avail, bytes, err;
/* Network transfer may deliver two or more packets concatenated.
* Peek at the header and read only one event at a time... */
avail = 0;
err = ioctl(fd, FIONREAD, &avail);
if (err == -1 || avail < I4B_MON_CMD_HDR)
{
if (err == -1 && errno == EINTR)
return 0; /* try again later */
if (err == -1 || avail == 0)
{
/* logit(LL_MER, "monitor read 0 bytes"); */
/* socket closed by peer */
close(fd);
return 1;
}
return 0; /* not enough data there yet */
}
bytes = recv(fd, cmd, I4B_MON_CMD_HDR, MSG_PEEK);
if (bytes < I4B_MON_CMD_HDR)
{
logit(LL_MER, "monitor read only %d bytes", bytes);
return 0; /* errh? something must be wrong... */
}
bytes = I4B_GET_2B(cmd, I4B_MON_CMD_LEN);
if (bytes >= (int)sizeof cmd)
{
close(fd);
logit(LL_MER, "monitor: garbage on connection");
return 1;
}
/* now we know the size, it fits, so lets read it! */
if (sock_read(fd, cmd, bytes) <= 0)
{
logit(LL_MER, "monitor: sock_read <= 0");
close(fd);
return 1;
}
/* decode command */
code = I4B_GET_2B(cmd, I4B_MON_CMD);
/* special case: may modify our connection descriptor, is
* beyound all rights checks */
if (code == I4B_MON_CCMD_SETMASK)
{
/*XXX*/
/*
u_int major = I4B_GET_2B(cmd, I4B_MON_ICLIENT_VERMAJOR);
u_int minor = I4B_GET_2B(cmd, I4B_MON_ICLIENT_VERMINOR);
*/
int events = I4B_GET_4B(cmd, I4B_MON_ICLIENT_EVENTS);
con->events = events & mcrights;
return 0;
}
if (code >= NUMCMD)
{
logit(LL_MER, "illegal command from client, code = %d\n",
code);
return 0;
}
if (cmd_tab[code].call == NULL)
return 0;
if ((cmd_tab[code].rights & mcrights) == cmd_tab[code].rights)
cmd_tab[code].call(fd, mcrights, (u_char *)cmd, con->source);
return 0;
}
/*---------------------------------------------------------------------------
* Check if somebody would receive an event with this mask.
* We are lazy and try to avoid assembling unneccesary packets.
* Return 0 if no one interested, nonzero otherwise.
*---------------------------------------------------------------------------*/
static int
anybody(int mask)
{
struct monitor_connection * con;
for (con = TAILQ_FIRST(&connections); con != NULL; con = TAILQ_NEXT(con, connections))
{
if ((con->events & mask) == mask)
return 1;
}
return 0;
}
/*---------------------------------------------------------------------------
* exec hangup command
*---------------------------------------------------------------------------*/
static void
hangup_channel(int controller, int channel, const char *source)
{
struct cfg_entry * cep = NULL;
struct isdn_ctrl_state * ctrl = NULL;
int i;
ctrl = find_ctrl_state(controller);
if (ctrl != NULL) {
if (ctrl->state != CTRL_UP)
return;
for (i = 0; i < ctrl->nbch; i++) {
if (ctrl->stateb[i] != CHAN_IDLE) {
cep = get_cep_by_cc(controller, i);
if (cep != NULL
&& cep->isdnchannelused == channel
&& cep->isdncontrollerused == controller)
goto found;
}
}
}
/* not found */
return;
found:
logit(LL_CHD, "%05d %s manual disconnect (remote from %s)", cep->cdid, cep->name, source);
cep->hangup = 1;
return;
}
/*---------------------------------------------------------------------------
* Send an event to every connection interested in this kind of
* event
*---------------------------------------------------------------------------*/
static void
monitor_broadcast(int mask, u_int8_t *pkt, size_t bytes)
{
struct monitor_connection *con;
for (con = TAILQ_FIRST(&connections); con != NULL; con = TAILQ_NEXT(con, connections))
{
if ((con->events & mask) == mask)
{
int fd = con->sock;
if ((sock_write(fd, pkt, bytes)) == -1)
{
logit(LL_MER, "monitor_broadcast: sock_write error - %s", strerror(errno));
}
}
}
}
/*---------------------------------------------------------------------------
* Post a logfile event
*---------------------------------------------------------------------------*/
void
monitor_evnt_log(int prio, const char * what, const char * msg)
{
u_int8_t evnt[I4B_MON_LOGEVNT_SIZE];
time_t now;
if (!anybody(I4B_CA_EVNT_I4B))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_LOGEVNT_CODE);
I4B_PUT_4B(evnt, I4B_MON_LOGEVNT_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_LOGEVNT_PRIO, prio);
I4B_PUT_STR(evnt, I4B_MON_LOGEVNT_WHAT, what);
I4B_PUT_STR(evnt, I4B_MON_LOGEVNT_MSG, msg);
monitor_broadcast(I4B_CA_EVNT_I4B, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post a charging event on the connection described
* by the given config entry.
*---------------------------------------------------------------------------*/
void
monitor_evnt_charge(struct cfg_entry *cep, int units, int estimate)
{
int mask;
time_t now;
u_int8_t evnt[I4B_MON_CHRG_SIZE];
mask = (cep->direction == DIR_IN) ? I4B_CA_EVNT_CALLIN : I4B_CA_EVNT_CALLOUT;
if (!anybody(mask))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_CHRG_CODE);
I4B_PUT_4B(evnt, I4B_MON_CHRG_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_CHRG_CTRL, cep->isdncontrollerused);
I4B_PUT_4B(evnt, I4B_MON_CHRG_CHANNEL, cep->isdnchannelused);
I4B_PUT_4B(evnt, I4B_MON_CHRG_UNITS, units);
I4B_PUT_4B(evnt, I4B_MON_CHRG_ESTIMATED, estimate ? 1 : 0);
monitor_broadcast(mask, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post a connection event
*---------------------------------------------------------------------------*/
void
monitor_evnt_connect(struct cfg_entry *cep)
{
u_int8_t evnt[I4B_MON_CONNECT_SIZE];
char devnam[I4B_MAX_MON_STRING];
int mask;
time_t now;
mask = (cep->direction == DIR_IN) ? I4B_CA_EVNT_CALLIN : I4B_CA_EVNT_CALLOUT;
if (!anybody(mask))
return;
time(&now);
snprintf(devnam, sizeof devnam, "%s%d", cep->usrdevicename, cep->usrdeviceunit);
I4B_PREP_EVNT(evnt, I4B_MON_CONNECT_CODE);
I4B_PUT_4B(evnt, I4B_MON_CONNECT_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_CONNECT_DIR, cep->direction == DIR_OUT ? 1 : 0);
I4B_PUT_4B(evnt, I4B_MON_CONNECT_CTRL, cep->isdncontrollerused);
I4B_PUT_4B(evnt, I4B_MON_CONNECT_CHANNEL, cep->isdnchannelused);
I4B_PUT_STR(evnt, I4B_MON_CONNECT_CFGNAME, cep->name);
I4B_PUT_STR(evnt, I4B_MON_CONNECT_DEVNAME, devnam);
if (cep->direction == DIR_OUT)
{
I4B_PUT_STR(evnt, I4B_MON_CONNECT_REMPHONE, cep->remote_phone_dialout);
I4B_PUT_STR(evnt, I4B_MON_CONNECT_LOCPHONE, cep->local_phone_dialout);
}
else
{
I4B_PUT_STR(evnt, I4B_MON_CONNECT_REMPHONE, cep->real_phone_incoming);
I4B_PUT_STR(evnt, I4B_MON_CONNECT_LOCPHONE, cep->local_phone_incoming);
}
monitor_broadcast(mask, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post a disconnect event
*---------------------------------------------------------------------------*/
void
monitor_evnt_disconnect(struct cfg_entry *cep)
{
u_int8_t evnt[I4B_MON_DISCONNECT_SIZE];
int mask;
time_t now;
mask = (cep->direction == DIR_IN) ? I4B_CA_EVNT_CALLIN : I4B_CA_EVNT_CALLOUT;
if (!anybody(mask))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_DISCONNECT_CODE);
I4B_PUT_4B(evnt, I4B_MON_DISCONNECT_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_DISCONNECT_CTRL, cep->isdncontrollerused);
I4B_PUT_4B(evnt, I4B_MON_DISCONNECT_CHANNEL, cep->isdnchannelused);
monitor_broadcast(mask, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post an up/down event
*---------------------------------------------------------------------------*/
void
monitor_evnt_updown(struct cfg_entry *cep, int up)
{
u_int8_t evnt[I4B_MON_UPDOWN_SIZE];
int mask;
time_t now;
mask = (cep->direction == DIR_IN) ? I4B_CA_EVNT_CALLIN : I4B_CA_EVNT_CALLOUT;
if (!anybody(mask))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_UPDOWN_CODE);
I4B_PUT_4B(evnt, I4B_MON_UPDOWN_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_UPDOWN_CTRL, cep->isdncontrollerused);
I4B_PUT_4B(evnt, I4B_MON_UPDOWN_CHANNEL, cep->isdnchannelused);
I4B_PUT_4B(evnt, I4B_MON_UPDOWN_ISUP, up);
monitor_broadcast(mask, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post a Layer1/2 status change event
*---------------------------------------------------------------------------*/
void
monitor_evnt_l12stat(int controller, int layer, int state)
{
u_int8_t evnt[I4B_MON_L12STAT_SIZE];
time_t now;
if (!anybody(I4B_CA_EVNT_I4B))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_L12STAT_CODE);
I4B_PUT_4B(evnt, I4B_MON_L12STAT_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_L12STAT_CTRL, controller);
I4B_PUT_4B(evnt, I4B_MON_L12STAT_LAYER, layer);
I4B_PUT_4B(evnt, I4B_MON_L12STAT_STATE, state);
monitor_broadcast(I4B_CA_EVNT_I4B, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post a TEI change event
*---------------------------------------------------------------------------*/
void
monitor_evnt_tei(int controller, int tei)
{
u_int8_t evnt[I4B_MON_TEI_SIZE];
time_t now;
if (!anybody(I4B_CA_EVNT_I4B))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_TEI_CODE);
I4B_PUT_4B(evnt, I4B_MON_TEI_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_TEI_CTRL, controller);
I4B_PUT_4B(evnt, I4B_MON_TEI_TEI, tei);
monitor_broadcast(I4B_CA_EVNT_I4B, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* Post an accounting event
*---------------------------------------------------------------------------*/
void
monitor_evnt_acct(struct cfg_entry *cep)
{
u_int8_t evnt[I4B_MON_ACCT_SIZE];
time_t now;
if (!anybody(I4B_CA_EVNT_I4B))
return;
time(&now);
I4B_PREP_EVNT(evnt, I4B_MON_ACCT_CODE);
I4B_PUT_4B(evnt, I4B_MON_ACCT_TSTAMP, (long)now);
I4B_PUT_4B(evnt, I4B_MON_ACCT_CTRL, cep->isdncontrollerused);
I4B_PUT_4B(evnt, I4B_MON_ACCT_CHAN, cep->isdnchannelused);
I4B_PUT_4B(evnt, I4B_MON_ACCT_OBYTES, cep->outbytes);
I4B_PUT_4B(evnt, I4B_MON_ACCT_OBPS, cep->outbps);
I4B_PUT_4B(evnt, I4B_MON_ACCT_IBYTES, cep->inbytes);
I4B_PUT_4B(evnt, I4B_MON_ACCT_IBPS, cep->inbps);
monitor_broadcast(I4B_CA_EVNT_I4B, evnt, sizeof evnt);
}
/*---------------------------------------------------------------------------
* read from a socket
*---------------------------------------------------------------------------*/
static ssize_t
sock_read(int fd, void *buf, size_t nbytes)
{
size_t nleft;
ssize_t nread;
unsigned char *ptr;
ptr = buf;
nleft = nbytes;
while(nleft > 0)
{
if ((nread = read(fd, ptr, nleft)) < 0)
{
if (errno == EINTR)
{
nread = 0;
}
else
{
return(-1);
}
}
else if (nread == 0)
{
break; /* EOF */
}
nleft -= nread;
ptr += nread;
}
return(nbytes - nleft);
}
/*---------------------------------------------------------------------------
* write to a socket
*---------------------------------------------------------------------------*/
static ssize_t
sock_write(int fd, void *buf, size_t nbytes)
{
size_t nleft;
ssize_t nwritten;
unsigned char *ptr;
ptr = buf;
nleft = nbytes;
while(nleft > 0)
{
if ((nwritten = write(fd, ptr, nleft)) <= 0)
{
if (errno == EINTR)
{
nwritten = 0;
}
else
{
return(-1);
}
}
nleft -= nwritten;
ptr += nwritten;
}
return(nbytes);
}
struct monitor_rights * monitor_next_rights(const struct monitor_rights *r)
{
if (r == NULL)
return TAILQ_FIRST(&rights);
else
return TAILQ_NEXT(r, list);
}
#endif /* I4B_EXTERNAL_MONITOR */