*/
#include <stdio.h>
+#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdarg.h>
typedef unsigned char byte;
typedef unsigned int uns;
-static inline int is_white(int c)
-{
- return (c == ' ' || c == '\t');
-}
-
/*** Configuration ***/
-static config_entry_t sip_cf_log = {
+static config_entry_t sip_cf_mail_to = {
.next = NULL,
+ .key = "mailto",
+ .type = CONFIG_TYPE_STRING,
+ .options = CONFIG_OPT_NONE,
+ .u.string = ""
+};
+
+static config_entry_t sip_cf_debug_log = {
+ .next = &sip_cf_mail_to,
+ .key = "debuglog",
+ .type = CONFIG_TYPE_STRING,
+ .options = CONFIG_OPT_NONE,
+ .u.string = ""
+};
+
+static config_entry_t sip_cf_log = {
+ .next = &sip_cf_debug_log,
.key = "file",
.type = CONFIG_TYPE_STRING,
.options = CONFIG_OPT_NONE,
uns src_port, dst_port;
char *sip_rr; // Decoded SIP
struct hdr *sip_hdr;
+ char *sip_method;
+ char *sip_uri;
+ int sip_status;
+ char *sip_msg;
+ struct call *sip_call;
+ char *sip_seq;
};
struct hdr {
char *name, *value;
};
+static char *find_hdr(struct pkt *pkt, char *name)
+{
+ for (struct hdr *h=pkt->sip_hdr; h; h=h->next)
+ if (!strcasecmp(h->name, name))
+ return h->value;
+ return NULL;
+}
+
+static char *find_hdr2(struct pkt *pkt, char *name, char *alt)
+{
+ return find_hdr(pkt, name) ? : find_hdr(pkt, alt);
+}
+
/*** Logging ***/
-static FILE *siplog;
-static int verbose = 1;
+static FILE *siplog, *debuglog;
static void debug(char *fmt, ...)
{
- if (!verbose)
+ if (!debuglog)
return;
va_list args;
va_start(args, fmt);
- fputc('\t', siplog);
- vfprintf(siplog, fmt, args);
- fputc('\n', siplog);
+ fputc('\t', debuglog);
+ vfprintf(debuglog, fmt, args);
+ fputc('\n', debuglog);
va_end(args);
}
{
va_list args;
va_start(args, fmt);
- fprintf(siplog, "%s ", pkt->time);
+ fprintf(siplog, "%s ERR: ", pkt->time);
vfprintf(siplog, fmt, args);
fputc('\n', siplog);
va_end(args);
return 0;
}
+/*** Auxillary routines ***/
+
+static inline int is_white(int c)
+{
+ return (c == ' ' || c == '\t');
+}
+
+static char *skip_ws(char *c)
+{
+ while (is_white(*c))
+ c++;
+ return c;
+}
+
+static char *skip_nonws(char *c)
+{
+ while (*c && !is_white(*c))
+ c++;
+ return c;
+}
+
+/*** The parser ***/
+
+#define CALL_ID_LEN 63
+#define NUM_LEN 63
+#define CSEQ_LEN 63
+
+enum call_state {
+ CALL_IDLE,
+ CALL_DIALING,
+ CALL_CONNECTED,
+ CALL_BYE,
+};
+
+enum call_flags {
+ FLAG_RING = 1,
+ FLAG_ANSWERED = 2,
+ FLAG_REDIRECT = 4,
+};
+
+struct call {
+ struct call *next;
+ char call_id[CALL_ID_LEN+1];
+ enum call_state state;
+ uns out_p;
+ char active_seq[CSEQ_LEN+1];
+ char src_party[NUM_LEN+1];
+ char dst_party[NUM_LEN+1];
+ uns flags;
+};
+static struct call *call_list;
+
+static void log_call(struct pkt *pkt, struct call *c)
+{
+ fprintf(siplog, "%s: CALL %s <%s> -> <%s>", pkt->time, (c->out_p ? "OUT" : "IN"), c->src_party, c->dst_party);
+ if (c->flags & FLAG_ANSWERED)
+ fprintf(siplog, " ANS");
+ else if (c->flags & FLAG_RING)
+ fprintf(siplog, " RING");
+ if (c->flags & FLAG_REDIRECT)
+ fprintf(siplog, " REDIR");
+ if (pkt->sip_status >= 300)
+ fprintf(siplog, " ERR:%s", pkt->sip_msg);
+ else
+ fprintf(siplog, " OK");
+ fputc('\n', siplog);
+
+ if (sip_cf_mail_to.u.string[0])
+ {
+ char cmd[256];
+ sprintf(cmd, "mail -s 'SIP CALL' %s", sip_cf_mail_to.u.string);
+ debug("Running %s", cmd);
+ FILE *m = popen(cmd, "w");
+ if (m)
+ {
+ fprintf(m, "Finished-At: %s\n", pkt->time);
+ fprintf(m, "Direction: %s\n", (c->out_p ? "OUT" : "IN"));
+ fprintf(m, "From: %s\n", c->src_party);
+ fprintf(m, "To: %s\n", c->dst_party);
+ fprintf(m, "Flags:%s%s%s\n",
+ ((c->flags & FLAG_ANSWERED) ? " ANS" : ""),
+ ((c->flags & FLAG_RING) ? " RING" : ""),
+ ((c->flags & FLAG_REDIRECT) ? " REDIR" : ""));
+ fprintf(m, "Status: %s\n", pkt->sip_msg);
+ fclose(m);
+ }
+ else
+ error(pkt, "Cannot execute `%s': %m", cmd);
+ }
+}
+
+static void extract_number(char *buf, char *h)
+{
+ if (!h)
+ h = "???";
+ char *c;
+ // Multiple addresses => use the first one
+ if (c = strchr(h, ','))
+ *c = 0;
+ // Tagged parameters => drop them
+ if (c = strchr(h, ';'))
+ *c = 0;
+ // Quoted part => prefer it
+ if (c = strchr(h, '<'))
+ {
+ h = c+1;
+ if (c = strchr(h, '>'))
+ *c = 0;
+ }
+ // Skip URL scheme
+ if (c = strchr(h, ':'))
+ h = c+1;
+ // Use only numeric characters, * and #
+ char *d = buf;
+ while (d < buf + NUM_LEN)
+ {
+ if (*h >= '0' && *h <= '9' || *h == '*')
+ *d++ = *h++;
+ else if (*h == '%' && h[1] == '2' && h[2] == '3')
+ {
+ *d++ = '#';
+ h += 3;
+ }
+ else
+ break;
+ }
+ *d = 0;
+}
+
+static int parse_request(struct pkt *pkt)
+{
+ struct call *c = pkt->sip_call;
+ if (!strcmp(pkt->sip_seq, c->active_seq))
+ {
+ debug("Retransmitted packet");
+ return 1;
+ }
+ if (!strcasecmp(pkt->sip_method, "INVITE"))
+ {
+ if (c->state != CALL_IDLE)
+ return error(pkt, "re-INVITE not supported yet");
+ c->state = CALL_DIALING;
+ c->out_p = pkt->out_p;
+ strcpy(c->active_seq, pkt->sip_seq);
+ extract_number(c->src_party, find_hdr2(pkt, "From", "f"));
+ extract_number(c->dst_party, find_hdr2(pkt, "To", "t"));
+ }
+ else if (!strcasecmp(pkt->sip_method, "BYE"))
+ {
+ if (c->state != CALL_CONNECTED)
+ {
+ c->state = CALL_IDLE;
+ return error(pkt, "Unexpected BYE");
+ }
+ c->state = CALL_BYE;
+ strcpy(c->active_seq, pkt->sip_seq);
+ }
+ return 1;
+}
+
+static int parse_reply(struct pkt *pkt)
+{
+ struct call *c = pkt->sip_call;
+ if (strcmp(pkt->sip_seq, c->active_seq))
+ {
+ debug("Reply not associated to active request %s", c->active_seq);
+ return 1;
+ }
+
+ uns stat = pkt->sip_status;
+ if (stat >= 200)
+ c->active_seq[0] = 0;
+
+ if (c->state == CALL_DIALING)
+ {
+ if (stat < 100)
+ ;
+ else if (stat < 200)
+ {
+ // Provisional responses
+ if (stat == 180)
+ c->flags |= FLAG_RING;
+ }
+ else if (stat < 300)
+ {
+ // Success
+ c->state = CALL_CONNECTED;
+ c->flags |= FLAG_ANSWERED;
+ }
+ else if (stat < 400)
+ {
+ // Redirect
+ c->flags |= FLAG_REDIRECT;
+ log_call(pkt, c);
+ c->state = CALL_IDLE;
+ }
+ else
+ {
+ // Failure of some sort
+ switch (stat)
+ {
+ case 408: // Timeout
+ case 404: // Not found
+ case 403: // Forbidden
+ case 480: // Tmp unavailable
+ case 486: // Busy here
+ case 487: // Cancelled
+ case 600: // Busy everywhere
+ case 603: // Decline
+ case 604: // Does not exist
+ case 606: // Unaccaptable
+ log_call(pkt, c);
+ }
+ c->state = CALL_IDLE;
+ // We do not reset flags on failures, because another INVITE may be sent for the same call and we wish to remember redirects etc.
+ }
+ }
+ else if (c->state == CALL_BYE)
+ {
+ if (stat >= 200)
+ {
+ log_call(pkt, c);
+ c->state = CALL_IDLE;
+ }
+ }
+ return 1;
+}
+
+static int parse_call(struct pkt *pkt)
+{
+ char *id = find_hdr2(pkt, "Call-ID", "i");
+ if (!id)
+ return error(pkt, "Missing Call-ID");
+ char *p = skip_nonws(id);
+ *p = 0;
+ if (!*id)
+ return error(pkt, "Empty Call-ID");
+ if (strlen(id) > CALL_ID_LEN)
+ return error(pkt, "Call-ID too long");
+
+ pkt->sip_seq = find_hdr(pkt, "CSeq");
+ if (!pkt->sip_seq)
+ return error(pkt, "Missing CSeq");
+ if (strlen(pkt->sip_seq) > CSEQ_LEN)
+ return error(pkt, "CSeq too long");
+
+ struct call *c;
+ for (c=call_list; c && strcasecmp(c->call_id, id); c=c->next)
+ ;
+ if (!c)
+ {
+ if (!pkt->sip_method)
+ return error(pkt, "Cannot match reply to an existing call");
+ debug("Creating new call <%s>", id);
+ c = malloc(sizeof(struct call));
+ if (!c)
+ return error(pkt, "Out of memory");
+ memset(c, 0, sizeof(*c));
+ c->next = call_list;
+ call_list = c;
+ strcpy(c->call_id, id);
+ c->state = CALL_IDLE;
+ }
+ else
+ debug("Continuing call <%s> in state %d", id, c->state);
+ pkt->sip_call = c;
+ // FIXME: We should time out the calls
+
+ if (pkt->sip_method)
+ return parse_request(pkt);
+ else
+ return parse_reply(pkt);
+}
+
static int parse_rr(struct pkt *pkt)
{
char *rr = pkt->sip_rr;
if (!strncmp(rr, "SIP/", 4))
{
// Reply
- while (*rr && !is_white(*rr))
- rr++;
- while (is_white(*rr))
- rr++;
+ rr = skip_ws(skip_nonws(rr));
+ if (!*rr)
+ return error(pkt, "Truncated SIP response");
+ pkt->sip_msg = rr;
+ pkt->sip_status = atol(rr);
+ pkt->sip_method = NULL;
+ pkt->sip_uri = NULL;
+ debug("SIP status %d (%s)", pkt->sip_status, pkt->sip_msg);
}
else
{
// Request
+ pkt->sip_msg = NULL;
+ pkt->sip_status = -1;
+ pkt->sip_method = rr;
+ rr = skip_nonws(rr);
+ if (!*rr)
+ return error(pkt, "Missing URI in SIP request");
+ *rr++ = 0;
+ rr = skip_ws(rr);
+ if (!*rr)
+ return error(pkt, "Missing URI in SIP request");
+ pkt->sip_uri = rr;
+ rr = skip_nonws(rr);
+ *rr = 0;
+ debug("SIP request %s on %s", pkt->sip_method, pkt->sip_uri);
}
- return 1;
+ return parse_call(pkt);
}
static int parse_sip(struct pkt *pkt)
{
char *p = (char*) pkt->udp;
char *end = p + pkt->udp_len;
+ pkt->sip_rr = NULL;
pkt->sip_hdr = NULL;
// Parse the request/reply line first
*p++ = 0;
debug(">>> %s", pkt->sip_rr);
- // Slurp the other header lines
+ // Slurp the other header lines and sanitize them
for (;;)
{
char *start = p;
else if (*p == '\n')
{
p++;
- if (p < end && is_white(*p))
+ if (p < end && is_white(*p) && pkt->sip_rr)
;
else
{
break;
}
}
- else
+ else if (*p > 0x20 && *p != 0x7f || *p < 0)
*w++ = *p++;
+ else if (is_white(*p))
+ *w++ = ' ', p++;
+ else
+ *w++ = '?', p++;
}
if (!start[0])
break;
// Got a line, parse it
+ if (!pkt->sip_rr)
+ {
+ pkt->sip_rr = start;
+ debug(">>> %s", pkt->sip_rr);
+ continue;
+ }
char *sep = strchr(start, ':');
if (!*sep)
{
h->name = start;
h->value = sep;
}
+ if (!pkt->sip_rr)
+ return error(pkt, "Missing SIP header");
return parse_rr(pkt);
}
return error(pkt, "Truncated UDP data (have %d, want %d)", pkt->ip_len, ulen);
pkt->udp = p + 8;
pkt->udp_len = ulen - 8;
- debug("%08x:%d -> %08x:%d (%d bytes, out_p=%d)", pkt->src_addr, pkt->src_port, pkt->dst_addr, pkt->dst_port, pkt->out_p);
+ debug("%08x:%d -> %08x:%d (%d bytes, out_p=%d)", pkt->src_addr, pkt->src_port, pkt->dst_addr, pkt->dst_port, pkt->udp_len, pkt->out_p);
return parse_sip(pkt);
}
{
struct pkt pkt;
+ if (!siplog)
+ return 0;
+
char *prefix = (KEY_OK(K_OOB_PREFIX, STRING) ? KEY_VAL(K_OOB_PREFIX).ptr : "?");
if (!strcasecmp(prefix, "phone-in"))
pkt.out_p = 0;
pkt.out_p = 1;
else
{
- if (verbose > 1)
- fprintf(siplog, "(non-phone packet received, prefix=%s)\n", prefix);
+ // fprintf(debuglog, "(non-phone packet received, prefix=%s)\n", prefix);
return 0;
}
else
sprintf(pkt.time, "%04d-%02d-%02d %02d:%02d:%02d.%06d", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec, pkt.time_usec);
- if (verbose)
- fprintf(siplog, "\t%s\n", pkt.time);
+ if (debuglog)
+ fprintf(debuglog, "%s\n", pkt.time);
parse_ip(&pkt);
fflush(siplog);
+ if (debuglog)
+ fflush(debuglog);
return 0;
}
ulogd_log(ULOGD_ERROR, "SIP: Parsing of configuration file failed\n");
return 1;
}
- ulogd_log(ULOGD_INFO, "Initializing SIP plugin, logging to %s\n", sip_cf_log.u.string);
+ ulogd_log(ULOGD_INFO, "Initializing SIP plugin, logging to %s\n", sip_cf_debug_log.u.string);
siplog = fopen(sip_cf_log.u.string, "a");
if (!siplog)
{
ulogd_log(ULOGD_ERROR, "Cannot open %s: %s", sip_cf_log.u.string, strerror(errno));
return 1;
}
+ if (sip_cf_debug_log.u.string[0])
+ {
+ debuglog = fopen(sip_cf_debug_log.u.string, "a");
+ if (!debuglog)
+ {
+ ulogd_log(ULOGD_ERROR, "Cannot open %s: %s", sip_cf_debug_log.u.string, strerror(errno));
+ return 1;
+ }
+ }
return 0;
}
// Logging does not work here
// ulogd_log(ULOGD_INFO, "siplog fini\n");
fclose(siplog);
+ fclose(debuglog);
+ siplog = debuglog = NULL;
}
static void sip_signal(int sig)
{
if (sig == SIGHUP)
{
- // FIXME: Should reopen the log file here
+ // FIXME: Should reopen the log files here
}
}