Commit 3c853dd4 authored by Victor Seva's avatar Victor Seva

Imported Upstream version 0.4.1

parent 9d1a9ce4
......@@ -4,8 +4,11 @@
# Build files
src/sngrep
src/*.o
src/.deps
tests/test-*.log
tests/test-*.trs
tests/test-???
*/*.o
*/.deps
src/config.h*
src/stamp-h1
......@@ -22,6 +25,7 @@ configure
missing
install-sh
depcomp
test-driver
# Ignore Doxygen generated files
doc/html
2015-07-08 Ivan Alonso <kaian@irontec.com>
* sngrep 0.4.1 released
* Added an option to capture RTP packets
* Allow RTP packets to be saved with their calls
* Improved Save panel default options
* Added testing files
* Improved SIP message parsed process
* Improved SIP message payload memory usage
* Fixed a bug with timestamp diff overflows
* Fixed multiple memory leaks
* Fixed compatibility with BSD systems
2015-06-24 Ivan Alonso <kaian@irontec.com>
* sngrep 0.4.0 released
......
SUBDIRS=src config doc
SUBDIRS=src config doc tests
EXTRA_DIST=bootstrap.sh
......@@ -13,12 +13,6 @@ capture:
to make sngrep store this packets for saving them will be an
option.
sip:
* Improve the way payload is stored
Payload is included two times. One in the msg->payload pointer
and also in msg->pcap_packet content. This doubles the required
memory.
ui:
* Change panels initialization
Right now, all panels are initializated at the same, because
......@@ -34,14 +28,6 @@ ui:
It would be nice to handle KEY_RESIZE event and change all displayed
panels.
* Improve Save panel
Make save panel smarter. If you're displaying raw text, TEXT should
be automatically selected on the save dialog. If you have selected
multiple dialogs, 'save selected' should be the default option.
If you're filtering dialogs, 'save filtering' should be the default
option. If you're saving pcap and you enter .pcap in the filename,
we should avoid adding the file prefix.
* Improve colors for white background terminals
The best approach for colors should be use terminal defaults.
Right now, white background terminals must set background dark option
......
AC_PREREQ([2.59])
AC_INIT([sngrep], [0.4.0], [kaian@irontec.com], [sngrep], [http://www.irontec.com/])
AC_INIT([sngrep], [0.4.1], [kaian@irontec.com], [sngrep], [http://www.irontec.com/])
AM_INIT_AUTOMAKE([1.9])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
AC_CONFIG_HEADERS([src/config.h])
......@@ -198,4 +198,5 @@ AC_CONFIG_FILES([Makefile])
AC_CONFIG_FILES([src/Makefile])
AC_CONFIG_FILES([config/Makefile])
AC_CONFIG_FILES([doc/Makefile])
AC_CONFIG_FILES([tests/Makefile])
AC_OUTPUT
......@@ -3,7 +3,7 @@
.\" Copyright (c) 2013-2015 Ivan Alonso <kaian@irontec.com>
.\" Copyright (c) 2013-2015 Irontec S.L.
.TH SNGREP 8 "June 2015" "sngrep 0.4.0"
.TH SNGREP 8 "June 2015" "sngrep 0.4.1"
.SH NAME
......
bin_PROGRAMS=sngrep
sngrep_SOURCES=capture.c capture_ws.c sip.c sip_attr.c main.c option.c
sngrep_SOURCES=capture.c capture_ws.c sip.c sip_call.c sip_msg.c sip_attr.c main.c option.c
sngrep_SOURCES+=group.c filter.c keybinding.c media.c setting.c rtp.c util.c vector.c
sngrep_SOURCES+=ui_manager.c ui_call_list.c ui_call_flow.c ui_call_raw.c
sngrep_SOURCES+=ui_filter.c ui_save.c ui_msg_diff.c ui_column_select.c ui_settings.c
......
......@@ -38,6 +38,7 @@
#include "capture_tls.h"
#endif
#include "sip.h"
#include "rtp.h"
#include "setting.h"
#include "ui_manager.h"
#ifdef WITH_IPV6
......@@ -179,11 +180,13 @@ parse_packet(u_char *mode, const struct pcap_pkthdr *header, const u_char *packe
// Total packet size
uint32_t size_packet;
// SIP message transport
int transport; /* 0 UDP, 1 TCP, 2 TLS */
int transport;
// Source and Destination Ports
u_short sport, dport;
// Media structure for RTP packets
rtp_stream_t *stream;
// Captured packet info
capture_packet_t *pkt;
// Ignore packets while capture is paused
if (capture_is_paused())
......@@ -240,7 +243,7 @@ parse_packet(u_char *mode, const struct pcap_pkthdr *header, const u_char *packe
// Only interested in UDP packets
if (ip_proto == IPPROTO_UDP) {
// Set transport UDP
transport = 0;
transport = CAPTURE_PACKET_SIP_UDP;
// Get UDP header
udp = (struct udphdr*) (packet + size_link + size_ip);
......@@ -263,7 +266,7 @@ parse_packet(u_char *mode, const struct pcap_pkthdr *header, const u_char *packe
} else if (ip_proto == IPPROTO_TCP) {
// Set transport TCP
transport = 1;
transport = CAPTURE_PACKET_SIP_TCP;
tcp = (struct tcphdr*) (packet + size_link + size_ip);
tcp_size = (ip_frag_off) ? 0 : (tcp->th_off * 4);
......@@ -300,13 +303,13 @@ parse_packet(u_char *mode, const struct pcap_pkthdr *header, const u_char *packe
}
// Set Transport TLS
transport = 2;
transport = CAPTURE_PACKET_SIP_TLS;
}
}
#endif
// Check if packet is Websocket
if (msg_payload && capture_ws_check_packet(msg_payload, &size_payload)) {
transport = 3;
transport = CAPTURE_PACKET_SIP_WS;
}
} else {
// Not handled protocol
......@@ -317,34 +320,39 @@ parse_packet(u_char *mode, const struct pcap_pkthdr *header, const u_char *packe
if ((int32_t) size_payload <= 0)
return;
// Parse this header and payload
if ((msg = sip_load_message(header, ip_src, sport, ip_dst, dport, msg_payload))) {
// Store Transport attribute
if (transport == 0) {
msg_set_attribute(msg, SIP_ATTR_TRANSPORT, "UDP");
} else if (transport == 1) {
msg_set_attribute(msg, SIP_ATTR_TRANSPORT, "TCP");
} else if (transport == 2) {
msg_set_attribute(msg, SIP_ATTR_TRANSPORT, "TLS");
} else if (transport == 3) {
msg_set_attribute(msg, SIP_ATTR_TRANSPORT, "WS");
}
// Create a structure for this captured packet
pkt = capture_packet_create(header, packet, size_packet, size_payload);
capture_packet_set_type(pkt, transport);
// Set message PCAP data
msg->pcap_packet = malloc(size_packet);
memcpy(msg->pcap_packet, packet, size_packet);
// Parse this header and payload
if ((msg = sip_load_message(pkt, ip_src, sport, ip_dst, dport, msg_payload))) {
// Store this packets in output file
dump_packet(capinfo.pd, header, packet);
// Deallocate packet duplicated payload
free(msg_payload);
return;
}
// Check if this packet belongs to a RTP stream
// TODO Store this packet in the stream
if ((stream = rtp_check_stream(header, ip_src, sport, ip_dst, dport, msg_payload))) {
// We have an RTP packet!
capture_packet_set_type(pkt, CAPTURE_PACKET_RTP);
// Store this pacekt if capture rtp is enabled
if (capinfo.rtp_capture)
call_add_rtp_packet(stream_get_call(stream), pkt);
// Store this packets in output file
dump_packet(capinfo.pd, header, packet);
} else {
if ((stream = rtp_check_stream(header, ip_src, sport, ip_dst, dport, msg_payload))) {
// Store this packets in output file
dump_packet(capinfo.pd, header, packet);
}
// Deallocate packet duplicated payload
free(msg_payload);
return;
}
// Deallocate packet duplicated payload
free(msg_payload);
// Not an interesting packet ...
capture_packet_destroy(pkt);
}
void
......@@ -369,7 +377,6 @@ capture_launch_thread()
//! capture thread attributes
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (pthread_create(&capinfo.capture_t, &attr, (void *) capture_thread, NULL)) {
return 1;
}
......@@ -408,19 +415,17 @@ capture_set_bpf_filter(const char *filter)
}
void
capture_set_limit(int limit)
capture_set_opts(int limit, int rtp_capture)
{
capinfo.limit = limit;
capinfo.rtp_capture = rtp_capture;
}
void
capture_set_paused(int pause)
{
if (capture_is_online()) {
if (pause)
capinfo.status = CAPTURE_ONLINE_PAUSED;
else
capinfo.status = CAPTURE_ONLINE;
capinfo.status = (pause) ? CAPTURE_ONLINE_PAUSED : CAPTURE_ONLINE;
}
}
......@@ -476,6 +481,73 @@ capture_last_error()
return pcap_geterr(capinfo.handle);
}
capture_packet_t *
capture_packet_create(const struct pcap_pkthdr *header, const u_char *packet, int size, int payload_len)
{
capture_packet_t *pkt;
pkt = malloc(sizeof(capture_packet_t));
pkt->size = size;
pkt->header = malloc(sizeof(struct pcap_pkthdr));
memcpy(pkt->header, header, sizeof(struct pcap_pkthdr));
pkt->data = malloc(size);
memcpy(pkt->data, packet, size);
pkt->payload_len = payload_len;
pkt->payload_start = size - pkt->payload_len;
return pkt;
}
void
capture_packet_destroy(capture_packet_t *packet)
{
free(packet->header);
free(packet->data);
free(packet);
}
void
capture_packet_destroyer(void *packet)
{
capture_packet_destroy((capture_packet_t*) packet);
}
void
capture_packet_set_type(capture_packet_t *packet, int type)
{
packet->type = type;
}
void
capture_packet_time_sorter(vector_t *vector, void *item)
{
capture_packet_t *prev, *cur;
int count = vector_count(vector);
int i;
// Get current item
cur = (capture_packet_t *) item;
prev = vector_item(vector, count - 2);
// Check if the item is already sorted
if (prev && timeval_is_older(cur->header->ts, prev->header->ts)) {
return;
}
for (i = count - 2 ; i >= 0; i--) {
// Get previous packet
prev = vector_item(vector, i);
// Check if the item is already in a sorted position
if (timeval_is_older(cur->header->ts, prev->header->ts)) {
vector_insert(vector, item, i + 1);
return;
}
}
// Put this item at the begining of the vector
vector_insert(vector, item, 0);
}
int
datalink_size(int datalink)
{
......
......@@ -47,13 +47,16 @@
#define _BSD_SOURCE 1
#endif
#if defined(BSD) || defined (__OpenBSD__) || defined(__FreeBSD__)
#if defined (__OpenBSD__)
#define timeval bpf_timeval
#endif
#if defined(BSD) || defined (__OpenBSD__) || defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <net/if.h>
#endif
#include <arpa/inet.h>
......@@ -61,6 +64,7 @@
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include "vector.h"
#ifdef INET6_ADDRSTRLEN
#define ADDRESSLEN INET6_ADDRSTRLEN + 1
......@@ -68,7 +72,6 @@
#define ADDRESSLEN 47
#endif
//! Capture modes
enum capture_status {
CAPTURE_ONLINE = 0,
......@@ -81,6 +84,19 @@ enum capture_status {
typedef struct capture_info capture_info_t;
//! Shorter declaration of dns_cache structure
typedef struct dns_cache dns_cache_t;
//! Shorter declaration of capture_packet structure
typedef struct capture_packet capture_packet_t;
//! Stored packet types
enum capture_packet_type
{
CAPTURE_PACKET_SIP_UDP = 0,
CAPTURE_PACKET_SIP_TCP,
CAPTURE_PACKET_SIP_TLS,
CAPTURE_PACKET_SIP_WS,
CAPTURE_PACKET_SIP_WSS,
CAPTURE_PACKET_RTP,
};
/**
* @brief Storage for DNS resolved ips
......@@ -105,6 +121,8 @@ struct capture_info {
int status;
//! Calls capture limit. 0 for disabling
int limit;
//! Also capture RTP packets
int rtp_capture;
//! Input file in Offline capture
const char *infile;
//! Key file for TLS decrypt
......@@ -129,6 +147,21 @@ struct capture_info {
pthread_t capture_t;
};
struct capture_packet {
// Packet type as defined in capture_packet_type
int type;
//! PCAP Packet Header data
struct pcap_pkthdr *header;
//! PCAP Packet content
u_char *data;
//! PPCAP Packet content len
int size;
//! Byte number where payload starts
int payload_start;
//! Payload length
int payload_len;
};
/**
* @brief Online capture function
*
......@@ -200,13 +233,13 @@ int
capture_set_bpf_filter(const char *filter);
/**
* @brief Limit the number of calls will be captured
* @brief Set capture options
*
* @param limit Numbers of calls >0
* @return void
* @param rtp_catpure Enable rtp capture
*/
void
capture_set_limit(int limit);
capture_set_opts(int limit, int rtp_capture);
/**
* @brief Pause/Resume capture
......@@ -267,6 +300,36 @@ capture_set_keyfile(const char *keyfile);
char *
capture_last_error();
/**
* @brief Allocate memory to store new packet data
*/
capture_packet_t *
capture_packet_create(const struct pcap_pkthdr *header, const u_char *packet, int size, int payload_len);
/**
* @brief Deallocate a packet structure memory
*/
void
capture_packet_destroy(capture_packet_t *packet);
/**
* @brief Destroyer function for packet vectors
*/
void
capture_packet_destroyer(void *packet);
/**
* @brief Set packet type
*/
void
capture_packet_set_type(capture_packet_t *packet, int type);
/**
* @brief Sorter by time for captured packets
*/
void
capture_packet_time_sorter(vector_t *vector, void *item);
/**
* @brief Close pcap handler
*/
......
......@@ -45,6 +45,7 @@ call_group_create()
void
call_group_destroy(sip_call_group_t *group)
{
vector_destroy(group->calls);
free(group);
}
......@@ -142,7 +143,7 @@ call_group_msg_number(sip_call_group_t *group, sip_msg_t *msg)
int number = 0;
sip_msg_t *cur = NULL;
while ((cur = call_group_get_next_msg(group, cur))) {
if (group->sdp_only && !msg->sdp)
if (group->sdp_only && !msg_has_sdp(msg))
continue;
if (cur == msg)
......@@ -161,17 +162,6 @@ call_group_get_next_msg(sip_call_group_t *group, sip_msg_t *msg)
sip_call_t *call;
int i;
// FIXME Performance hack for huge dialogs
if (vector_count(group->calls) == 1) {
call = vector_first(group->calls);
msgs = vector_iterator(call->msgs);
vector_iterator_set_current(&msgs, vector_index(call->msgs, msg));
if (group->sdp_only)
vector_iterator_set_filter(&msgs, msg_has_sdp);
return vector_iterator_next(&msgs);
}
for (i = 0; i < vector_count(group->calls); i++) {
call = vector_item(group->calls, i);
......@@ -188,7 +178,7 @@ call_group_get_next_msg(sip_call_group_t *group, sip_msg_t *msg)
}
}
return next;
return sip_parse_msg(next);
}
sip_msg_t *
......@@ -236,10 +226,12 @@ call_group_get_next_stream(sip_call_group_t *group, rtp_stream_t *stream)
int
timeval_is_older(struct timeval t1, struct timeval t2)
{
long diff;
diff = t2.tv_sec * 1000000 + t2.tv_usec;
diff -= t1.tv_sec * 1000000 + t1.tv_usec;
return (diff < 0);
long long int t1sec, t2sec;
t1sec = t1.tv_sec;
t1sec = t1sec * 1000000;
t2sec = t2.tv_sec;
t2sec = t2sec * 1000000;
return ((t2sec + t2.tv_usec) - (t1sec + t1.tv_usec) < 0);
}
int
......@@ -250,7 +242,7 @@ sip_msg_is_older(sip_msg_t *one, sip_msg_t *two)
return 1;
// Otherwise
return timeval_is_older(one->pcap_header->ts, two->pcap_header->ts);
return timeval_is_older(msg_get_time(one), msg_get_time(two));
}
int
......
......@@ -58,6 +58,7 @@ usage()
" -I --input\t\t Read captured data from pcap file\n"
" -O --output\t\t Write captured data to pcap file\n"
" -c --calls\t\t Only display dialogs starting with INVITE\n"
" -r --rtp\t\t Capture full RTP packets\n"
" -l --limit\t\t Set capture limit to N dialogs\n"
" -i --icase\t\t Make <match expression> case insensitive\n"
" -v --invert\t\t Invert <match expression>\n"
......@@ -108,7 +109,7 @@ main(int argc, char* argv[])
const char *keyfile;
const char *match_expr;
int match_insensitive = 0, match_invert = 0;
int no_interface = 0, quiet = 0;
int no_interface = 0, quiet = 0, rtp_capture = 0;
// Program otptions
static struct option long_options[] = {
......@@ -119,6 +120,7 @@ main(int argc, char* argv[])
{ "output", required_argument, 0, 'O' },
{ "keyfile", required_argument, 0, 'k' },
{ "calls", no_argument, 0, 'c' },
{ "rtp", no_argument, 0, 'r' },
{ "limit", no_argument, 0, 'l' },
{ "icase", no_argument, 0, 'i' },
{ "invert", no_argument, 0, 'v' },
......@@ -137,10 +139,11 @@ main(int argc, char* argv[])
limit = setting_get_intvalue(SETTING_CAPTURE_LIMIT);
only_calls = setting_enabled(SETTING_SIP_CALLS);
no_incomplete = setting_enabled(SETTING_SIP_NOINCOMPLETE);
rtp_capture = setting_enabled(SETTING_SIP_NOINCOMPLETE);
// Parse command line arguments
opterr = 0;
char *options = "hVd:I:O:pqtW:k:cl:ivNq";
char *options = "hVd:I:O:pqtW:k:crl:ivNq";
while ((opt = getopt_long(argc, argv, options, long_options, &idx)) != -1) {
switch (opt) {
case 'h':
......@@ -170,6 +173,9 @@ main(int argc, char* argv[])
case 'c':
only_calls = 1;
break;
case 'r':
rtp_capture = 1;
break;
case 'i':
match_insensitive++;
break;
......@@ -221,8 +227,8 @@ main(int argc, char* argv[])
// Initialize SIP Messages Storage
sip_init(limit, only_calls, no_incomplete);
// Set capture Calls limit
capture_set_limit(limit);
// Set capture options
capture_set_opts(limit, rtp_capture);
// If we have an input file, load it
if (infile) {
......@@ -273,14 +279,14 @@ main(int argc, char* argv[])
// Start a capture thread
if (capture_launch_thread() != 0) {
deinit_interface();
ncurses_deinit();
fprintf(stderr, "Failed to launch capture thread.\n");
return 1;
}
if (!no_interface) {
// Initialize interface
init_interface();
ncurses_init();
// This is a blocking call.
// Create the first panel and wait for user input
ui_create_panel(PANEL_CALL_LIST);
......@@ -300,13 +306,13 @@ main(int argc, char* argv[])
capture_close();
// Deinitialize interface
deinit_interface();
ncurses_deinit();
// Deinitialize configuration options
deinit_options();
// Deallocate sip stored messages
sip_calls_clear();
sip_deinit();
// Leaving!
return 0;
......
......@@ -20,7 +20,7 @@
**
****************************************************************************/
/**
* @file capture_rtp.c
* @file rtp.c
* @author Ivan Alonso [aka Kaian] <kaian@irontec.com>
*
* @brief Functions to manage RTP captured packets
......@@ -109,7 +109,7 @@ stream_add_packet(rtp_stream_t *stream, const char *ip_src, u_short sport, const
if (!(reverse = rtp_find_call_stream(stream->media->msg->call, stream->ip_dst, stream->dport, stream->ip_src, stream->sport))) {
reverse = stream_create(stream->media, stream->ip_src, stream->sport);
stream_complete(reverse, stream->ip_dst, stream->dport);
vector_append(stream->media->msg->call->streams, reverse);
call_add_stream(msg_get_call(stream->media->msg), reverse);
}
}
......@@ -119,6 +119,14 @@ stream_get_count(rtp_stream_t *stream)
return stream->pktcnt;
}
struct sip_call *
stream_get_call(rtp_stream_t *stream)
{
if (stream && stream->media && stream->media->msg)
return stream->media->msg->call;
return NULL;
}
const char *
rtp_get_codec(int code, const char *format)
{
......
......@@ -20,7 +20,7 @@
**
****************************************************************************/
/**
* @file capture_rtp.h
* @file rtp.h
* @author Ivan Alonso [aka Kaian] <kaian@irontec.com>
*
* @brief Functions to manage rtp captured packets
......@@ -29,6 +29,8 @@
#ifndef __SNGREP_RTP_H
#define __SNGREP_RTP_H
#include "config.h"
#include "capture.h"
#include "media.h"
#define RTP_FORMAT_MASK 0x7F
......@@ -80,6 +82,9 @@ stream_add_packet(rtp_stream_t *stream, const char *ip_src, u_short sport, const
int
stream_get_count(rtp_stream_t *stream);
struct sip_call *
stream_get_call(rtp_stream_t *stream);
const char *
rtp_get_codec(int code, const char *format);
......
......@@ -43,12 +43,13 @@ setting_t settings[SETTING_COUNT] =
{ SETTING_SYNTAX_BRANCH, "syntax.branch", SETTING_FMT_ENUM, "off", SETTING_ENUM_ONOFF },
{ SETTING_ALTKEY_HINT, "hintkeyalt", SETTING_FMT_ENUM, "off", SETTING_ENUM_ONOFF },
{ SETTING_EXITPROMPT, "exitprompt", SETTING_FMT_ENUM, "on", SETTING_ENUM_ONOFF },
{ SETTING_CAPTURE_LIMIT, "capture.limit", SETTING_FMT_NUMBER, "50000", NULL },
{ SETTING_CAPTURE_LIMIT, "capture.limit", SETTING_FMT_NUMBER, "20000", NULL },
{ SETTING_CAPTURE_LOOKUP, "capture.lookup", SETTING_FMT_ENUM, "off", SETTING_ENUM_ONOFF },
{ SETTING_CAPTURE_DEVICE, "capture.device", SETTING_FMT_STRING, "any", NULL },
{ SETTING_CAPTURE_INFILE, "capture.infile", SETTING_FMT_STRING, NULL, NULL },
{ SETTING_CAPTURE_OUTFILE, "capture.outfile", SETTING_FMT_STRING, NULL, NULL },
{ SETTING_CAPTURE_KEYFILE, "capture.keyfile", SETTING_FMT_STRING, NULL, NULL },
{ SETTING_CAPTURE_RTP, "capture.rtp", SETTING_FMT_ENUM, "on", SETTING_ENUM_ONOFF },
{ SETTING_SIP_NOINCOMPLETE, "sip.noincomplete", SETTING_FMT_ENUM, "on", SETTING_ENUM_ONOFF },
{ SETTING_SIP_CALLS, "sip.calls", SETTING_FMT_ENUM, "off", SETTING_ENUM_ONOFF },
{ SETTING_SAVEPATH, "savepath", SETTING_FMT_STRING, NULL, NULL },
......@@ -139,7 +140,9 @@ setting_set_value(int id, const char *value)
{
setting_t *sett = setting_by_id(id);
if (sett) {
if (sett->user) free(sett->value);
sett->value = (value) ? strdup(value) : NULL;
sett->user = 1;
}
}
......
......@@ -68,6 +68,7 @@ enum setting_id
SETTING_CAPTURE_INFILE,
SETTING_CAPTURE_OUTFILE,
SETTING_CAPTURE_KEYFILE,
SETTING_CAPTURE_RTP,
SETTING_SIP_NOINCOMPLETE,
SETTING_SIP_CALLS,
SETTING_SAVEPATH,
......@@ -112,9 +113,10 @@ struct setting_option {
char *value;
//! Compa separated valid values
const char **valuelist;
//! Setting set by user config
int user;
};
setting_t *
setting_by_id(int id);
......
This diff is collapsed.
This diff is collapsed.
......@@ -115,26 +115,58 @@ sip_attr_from_name(const char *name) {
return -1;
}
sip_attr_t *
sip_attr_create(enum sip_attr_id id, const char *value)
{
sip_attr_t *attr;
// Create a new attribute struct and store it
if (!(attr = malloc(sizeof(sip_attr_t))))
return NULL;
attr->id = id;
attr->value = strdup(value);
return attr;
}
void
sip_attr_list_destroy(char *attrs[])
sip_attr_destroy(sip_attr_t *attr)
{
int i;
for (i = 0; i < SIP_ATTR_COUNT; i++) {
if (attrs[i])
free(attrs[i]);
}
free(attr->value);
free(attr);
}
void
sip_attr_set(char *attrs[], enum sip_attr_id id, const char *value)