aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorSven Moritz Hallberg <pesco@khjk.org>2008-07-17 01:22:52 +0200
committerSven Moritz Hallberg <pesco@khjk.org>2008-07-17 01:22:52 +0200
commit6738a676c7a3895988de4bd9eacfe8fa0ef73cc3 (patch)
treed95d913484cf79ff4a3c6d920a4d9b92ecd66de9 /lib
parent9730d7250bb9e938ca00b72efdd8e8b3c03b2753 (diff)
parent6a78c0eed44820a2fefe1e96516e335eddc9c70b (diff)
merge in latest trunk
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile2
-rw-r--r--lib/arc.c27
-rw-r--r--lib/arc.h8
-rw-r--r--lib/base64.c2
-rw-r--r--lib/events_glib.c5
-rw-r--r--lib/misc.c64
-rw-r--r--lib/misc.h3
-rw-r--r--lib/proxy.c15
-rw-r--r--lib/ssl_bogus.c5
-rw-r--r--lib/ssl_client.h3
-rw-r--r--lib/ssl_gnutls.c6
-rw-r--r--lib/ssl_nss.c6
-rw-r--r--lib/ssl_openssl.c23
-rw-r--r--lib/ssl_sspi.c278
-rw-r--r--lib/url.c24
-rw-r--r--lib/url.h18
-rw-r--r--lib/xmltree.c590
-rw-r--r--lib/xmltree.h97
18 files changed, 1116 insertions, 60 deletions
diff --git a/lib/Makefile b/lib/Makefile
index 975deceb..03fef1ab 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -9,7 +9,7 @@
-include ../Makefile.settings
# [SH] Program variables
-objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o
+objects = arc.o base64.o $(EVENT_HANDLER) http_client.o ini.o md5.o misc.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o
CFLAGS += -Wall
LFLAGS += -r
diff --git a/lib/arc.c b/lib/arc.c
index 617f6b96..fd498454 100644
--- a/lib/arc.c
+++ b/lib/arc.c
@@ -130,18 +130,40 @@ unsigned char arc_getbyte( struct arc_state *st )
don't need it anymore.
Both functions return the number of bytes in the result string.
+
+ Note that if you use the pad_to argument, you will need zero-termi-
+ nation to find back the original string length after decryption. So
+ it shouldn't be used if your string contains \0s by itself!
*/
-int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password )
+int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password, int pad_to )
{
struct arc_state *st;
unsigned char *key;
- int key_len, i;
+ char *padded = NULL;
+ int key_len, i, padded_len;
key_len = strlen( password ) + ARC_IV_LEN;
if( clear_len <= 0 )
clear_len = strlen( clear );
+ /* Pad the string to the closest multiple of pad_to. This makes it
+ impossible to see the exact length of the password. */
+ if( pad_to > 0 && ( clear_len % pad_to ) > 0 )
+ {
+ padded_len = clear_len + pad_to - ( clear_len % pad_to );
+ padded = g_malloc( padded_len );
+ memcpy( padded, clear, clear_len );
+
+ /* First a \0 and then random data, so we don't have to do
+ anything special when decrypting. */
+ padded[clear_len] = 0;
+ random_bytes( (unsigned char*) padded + clear_len + 1, padded_len - clear_len - 1 );
+
+ clear = padded;
+ clear_len = padded_len;
+ }
+
/* Prepare buffers and the key + IV */
*crypt = g_malloc( clear_len + ARC_IV_LEN );
key = g_malloc( key_len );
@@ -160,6 +182,7 @@ int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *passwor
crypt[0][i+ARC_IV_LEN] = clear[i] ^ arc_getbyte( st );
g_free( st );
+ g_free( padded );
return clear_len + ARC_IV_LEN;
}
diff --git a/lib/arc.h b/lib/arc.h
index 882372ed..816fa612 100644
--- a/lib/arc.h
+++ b/lib/arc.h
@@ -30,7 +30,11 @@ struct arc_state
unsigned char i, j;
};
-struct arc_state *arc_keymaker( unsigned char *key, int kl, int cycles );
+#ifndef G_GNUC_MALLOC
+#define G_GNUC_MALLOC
+#endif
+
+G_GNUC_MALLOC struct arc_state *arc_keymaker( unsigned char *key, int kl, int cycles );
unsigned char arc_getbyte( struct arc_state *st );
-int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password );
+int arc_encode( char *clear, int clear_len, unsigned char **crypt, char *password, int pad_to );
int arc_decode( unsigned char *crypt, int crypt_len, char **clear, char *password );
diff --git a/lib/base64.c b/lib/base64.c
index 64e9692a..ea0db6b9 100644
--- a/lib/base64.c
+++ b/lib/base64.c
@@ -117,7 +117,7 @@ int base64_decode_real(const unsigned char *in, unsigned char *out, char *b64rev
{
int i, outlen = 0;
- for( i = 0; in[i]; i += 4 )
+ for( i = 0; in[i] && in[i+1] && in[i+2] && in[i+3]; i += 4 )
{
int sx;
diff --git a/lib/events_glib.c b/lib/events_glib.c
index 1198dba6..3e194e98 100644
--- a/lib/events_glib.c
+++ b/lib/events_glib.c
@@ -50,11 +50,12 @@ typedef struct _GaimIOClosure {
gpointer data;
} GaimIOClosure;
-static GMainLoop *loop;
+static GMainLoop *loop = NULL;
void b_main_init()
{
- loop = g_main_new( FALSE );
+ if( loop == NULL )
+ loop = g_main_new( FALSE );
}
void b_main_run()
diff --git a/lib/misc.c b/lib/misc.c
index ccb04d4c..a00f2c2f 100644
--- a/lib/misc.c
+++ b/lib/misc.c
@@ -32,6 +32,7 @@
#define BITLBEE_CORE
#include "nogaim.h"
+#include "base64.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -60,31 +61,6 @@ void strip_linefeed(gchar *text)
g_free(text2);
}
-char *normalize(const char *s)
-{
- static char buf[BUF_LEN];
- char *t, *u;
- int x = 0;
-
- g_return_val_if_fail((s != NULL), NULL);
-
- u = t = g_strdup(s);
-
- strcpy(t, s);
- g_strdown(t);
-
- while (*t && (x < BUF_LEN - 1)) {
- if (*t != ' ') {
- buf[x] = *t;
- x++;
- }
- t++;
- }
- buf[x] = '\0';
- g_free(u);
- return buf;
-}
-
time_t get_time(int year, int month, int day, int hour, int min, int sec)
{
struct tm tm;
@@ -407,6 +383,7 @@ signed int do_iconv( char *from_cs, char *to_cs, char *src, char *dst, size_t si
lack of entropy won't halt BitlBee. */
void random_bytes( unsigned char *buf, int count )
{
+#ifndef _WIN32
static int use_dev = -1;
/* Actually this probing code isn't really necessary, is it? */
@@ -456,6 +433,7 @@ void random_bytes( unsigned char *buf, int count )
}
if( !use_dev )
+#endif
{
int i;
@@ -607,3 +585,39 @@ gboolean ssl_sockerr_again( void *ssl )
else
return sockerr_again();
}
+
+/* Returns values: -1 == Failure (base64-decoded to something unexpected)
+ 0 == Okay
+ 1 == Password doesn't match the hash. */
+int md5_verify_password( char *password, char *hash )
+{
+ md5_byte_t *pass_dec = NULL;
+ md5_byte_t pass_md5[16];
+ md5_state_t md5_state;
+ int ret = -1, i;
+
+ if( base64_decode( hash, &pass_dec ) == 21 )
+ {
+ md5_init( &md5_state );
+ md5_append( &md5_state, (md5_byte_t*) password, strlen( password ) );
+ md5_append( &md5_state, (md5_byte_t*) pass_dec + 16, 5 ); /* Hmmm, salt! */
+ md5_finish( &md5_state, pass_md5 );
+
+ for( i = 0; i < 16; i ++ )
+ {
+ if( pass_dec[i] != pass_md5[i] )
+ {
+ ret = 1;
+ break;
+ }
+ }
+
+ /* If we reached the end of the loop, it was a match! */
+ if( i == 16 )
+ ret = 0;
+ }
+
+ g_free( pass_dec );
+
+ return ret;
+}
diff --git a/lib/misc.h b/lib/misc.h
index 7804bfb1..5376cd22 100644
--- a/lib/misc.h
+++ b/lib/misc.h
@@ -40,7 +40,6 @@ struct ns_srv_reply
G_MODULE_EXPORT void strip_linefeed( gchar *text );
G_MODULE_EXPORT char *add_cr( char *text );
G_MODULE_EXPORT char *strip_newlines(char *source);
-G_MODULE_EXPORT char *normalize( const char *s );
G_MODULE_EXPORT time_t get_time( int year, int month, int day, int hour, int min, int sec );
double gettime( void );
@@ -66,4 +65,6 @@ G_MODULE_EXPORT char *word_wrap( char *msg, int line_len );
G_MODULE_EXPORT gboolean ssl_sockerr_again( void *ssl );
+G_MODULE_EXPORT int md5_verify_password( char *password, char *hash );
+
#endif
diff --git a/lib/proxy.c b/lib/proxy.c
index 0e1c8f07..91493557 100644
--- a/lib/proxy.c
+++ b/lib/proxy.c
@@ -113,6 +113,7 @@ static gboolean gaim_io_connected(gpointer data, gint source, b_input_condition
static int proxy_connect_none(const char *host, unsigned short port, struct PHB *phb)
{
struct sockaddr_in *sin;
+ struct sockaddr_in me;
int fd = -1;
if (!(sin = gaim_gethostbyname(host, port))) {
@@ -127,6 +128,16 @@ static int proxy_connect_none(const char *host, unsigned short port, struct PHB
sock_make_nonblocking(fd);
+ if( global.conf->iface_out )
+ {
+ me.sin_family = AF_INET;
+ me.sin_port = 0;
+ me.sin_addr.s_addr = inet_addr( global.conf->iface_out );
+
+ if( bind( fd, (struct sockaddr *) &me, sizeof( me ) ) != 0 )
+ event_debug( "bind( %d, \"%s\" ) failure\n", fd, global.conf->iface_out );
+ }
+
event_debug("proxy_connect_none( \"%s\", %d ) = %d\n", host, port, fd);
if (connect(fd, (struct sockaddr *)sin, sizeof(*sin)) < 0 && !sockerr_again()) {
@@ -529,7 +540,7 @@ int proxy_connect(const char *host, int port, b_event_handler func, gpointer dat
{
struct PHB *phb;
- if (!host || !port || (port == -1) || !func || strlen(host) > 128) {
+ if (!host || port <= 0 || !func || strlen(host) > 128) {
return -1;
}
@@ -537,7 +548,7 @@ int proxy_connect(const char *host, int port, b_event_handler func, gpointer dat
phb->func = func;
phb->data = data;
- if ((proxytype == PROXY_NONE) || strlen(proxyhost) > 0 || !proxyport || (proxyport == -1))
+ if (proxytype == PROXY_NONE || !proxyhost[0] || proxyport <= 0)
return proxy_connect_none(host, port, phb);
else if (proxytype == PROXY_HTTP)
return proxy_connect_http(host, port, phb);
diff --git a/lib/ssl_bogus.c b/lib/ssl_bogus.c
index 391e634a..6eea18c7 100644
--- a/lib/ssl_bogus.c
+++ b/lib/ssl_bogus.c
@@ -64,3 +64,8 @@ b_input_condition ssl_getdirection( void *conn )
{
return GAIM_INPUT_READ;
}
+
+int ssl_pending( void *conn )
+{
+ return 0;
+}
diff --git a/lib/ssl_client.h b/lib/ssl_client.h
index 44fd658c..ef0b280c 100644
--- a/lib/ssl_client.h
+++ b/lib/ssl_client.h
@@ -62,6 +62,9 @@ G_MODULE_EXPORT void *ssl_starttls( int fd, ssl_input_function func, gpointer da
G_MODULE_EXPORT int ssl_read( void *conn, char *buf, int len );
G_MODULE_EXPORT int ssl_write( void *conn, const char *buf, int len );
+/* See ssl_openssl.c for an explanation. */
+G_MODULE_EXPORT int ssl_pending( void *conn );
+
/* Abort the SSL connection and disconnect the socket. Do not use close()
directly, both the SSL library and the peer will be unhappy! */
G_MODULE_EXPORT void ssl_disconnect( void *conn_ );
diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c
index ae6f46a4..0d87b7ad 100644
--- a/lib/ssl_gnutls.c
+++ b/lib/ssl_gnutls.c
@@ -215,6 +215,12 @@ int ssl_write( void *conn, const char *buf, int len )
return st;
}
+/* See ssl_openssl.c for an explanation. */
+int ssl_pending( void *conn )
+{
+ return 0;
+}
+
void ssl_disconnect( void *conn_ )
{
struct scd *conn = conn_;
diff --git a/lib/ssl_nss.c b/lib/ssl_nss.c
index 16560e63..c8bfd744 100644
--- a/lib/ssl_nss.c
+++ b/lib/ssl_nss.c
@@ -174,6 +174,12 @@ int ssl_write( void *conn, const char *buf, int len )
return( PR_Write ( ((struct scd*)conn)->prfd, buf, len ) );
}
+/* See ssl_openssl.c for an explanation. */
+int ssl_pending( void *conn )
+{
+ return 0;
+}
+
void ssl_disconnect( void *conn_ )
{
struct scd *conn = conn_;
diff --git a/lib/ssl_openssl.c b/lib/ssl_openssl.c
index 0ec9865f..cf81fb02 100644
--- a/lib/ssl_openssl.c
+++ b/lib/ssl_openssl.c
@@ -67,16 +67,16 @@ void *ssl_connect( char *host, int port, ssl_input_function func, gpointer data
struct scd *conn = g_new0( struct scd, 1 );
conn->fd = proxy_connect( host, port, ssl_connected, conn );
- conn->func = func;
- conn->data = data;
- conn->inpa = -1;
-
if( conn->fd < 0 )
{
g_free( conn );
return NULL;
}
+ conn->func = func;
+ conn->data = data;
+ conn->inpa = -1;
+
return conn;
}
@@ -235,6 +235,21 @@ int ssl_write( void *conn, const char *buf, int len )
return st;
}
+/* Only OpenSSL *really* needs this (and well, maybe NSS). See for more info:
+ http://www.gnu.org/software/gnutls/manual/gnutls.html#index-gnutls_005frecord_005fcheck_005fpending-209
+ http://www.openssl.org/docs/ssl/SSL_pending.html
+
+ Required because OpenSSL empties the TCP buffer completely but doesn't
+ necessarily give us all the unencrypted data.
+
+ Returns 0 if there's nothing left or if we don't have to care (GnuTLS),
+ 1 if there's more data. */
+int ssl_pending( void *conn )
+{
+ return ( ((struct scd*)conn) && ((struct scd*)conn)->established ) ?
+ SSL_pending( ((struct scd*)conn)->ssl ) > 0 : 0;
+}
+
void ssl_disconnect( void *conn_ )
{
struct scd *conn = conn_;
diff --git a/lib/ssl_sspi.c b/lib/ssl_sspi.c
new file mode 100644
index 00000000..a16423b1
--- /dev/null
+++ b/lib/ssl_sspi.c
@@ -0,0 +1,278 @@
+ /********************************************************************\
+ * BitlBee -- An IRC to other IM-networks gateway *
+ * *
+ * Copyright 2002-2004 Wilmer van der Gaast and others *
+ \********************************************************************/
+
+/* SSL module - SSPI backend */
+
+/* Copyright (C) 2005 Jelmer Vernooij <jelmer@samba.org> */
+
+/*
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License with
+ the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
+ if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+ Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "ssl_client.h"
+#include <windows.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <sspi.h>
+#include <schannel.h>
+#include "sock.h"
+
+static gboolean initialized = FALSE;
+int ssl_errno;
+
+struct scd
+{
+ int fd;
+ ssl_input_function func;
+ gpointer data;
+ gboolean established;
+ CredHandle cred; /* SSL credentials */
+ CtxtHandle context; /* SSL context */
+ SecPkgContext_StreamSizes sizes;
+
+ char *host;
+
+ char *pending_raw_data;
+ gsize pending_raw_data_len;
+ char *pending_data;
+ gsize pending_data_len;
+};
+
+static void ssl_connected(gpointer, gint, GaimInputCondition);
+
+void sspi_global_init(void)
+{
+ /* FIXME */
+}
+
+void sspi_global_deinit(void)
+{
+ /* FIXME */
+}
+
+void *ssl_connect(char *host, int port, ssl_input_function func, gpointer data)
+{
+ struct scd *conn = g_new0(struct scd, 1);
+
+ conn->fd = proxy_connect(host, port, ssl_connected, conn);
+ sock_make_nonblocking(conn->fd);
+ conn->func = func;
+ conn->data = data;
+ conn->host = g_strdup(host);
+
+ if (conn->fd < 0)
+ {
+ g_free(conn);
+ return NULL;
+ }
+
+ if (!initialized)
+ {
+ sspi_global_init();
+ initialized = TRUE;
+ atexit(sspi_global_deinit);
+ }
+
+ return conn;
+}
+
+static void ssl_connected(gpointer _conn, gint fd, GaimInputCondition cond)
+{
+ struct scd *conn = _conn;
+ SCHANNEL_CRED ssl_cred;
+ TimeStamp timestamp;
+ SecBuffer ibuf[2],obuf[1];
+ SecBufferDesc ibufs,obufs;
+ ULONG req = ISC_REQ_REPLAY_DETECT | ISC_REQ_SEQUENCE_DETECT |
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_USE_SESSION_KEY |
+ ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_STREAM | ISC_REQ_EXTENDED_ERROR |
+ ISC_REQ_MANUAL_CRED_VALIDATION;
+ ULONG a;
+ gsize size = 0;
+ gchar *data = NULL;
+
+ memset(&ssl_cred, 0, sizeof(SCHANNEL_CRED));
+ ssl_cred.dwVersion = SCHANNEL_CRED_VERSION;
+ ssl_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
+
+ SECURITY_STATUS st = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, &ssl_cred, NULL, NULL, &conn->cred, &timestamp);
+
+ if (st != SEC_E_OK) {
+ conn->func(conn->data, NULL, cond);
+ return;
+ }
+
+ do {
+ /* initialize buffers */
+ ibuf[0].cbBuffer = size; ibuf[0].pvBuffer = data;
+ ibuf[1].cbBuffer = 0; ibuf[1].pvBuffer = NULL;
+ obuf[0].cbBuffer = 0; obuf[0].pvBuffer = NULL;
+ ibuf[0].BufferType = obuf[0].BufferType = SECBUFFER_TOKEN;
+ ibuf[1].BufferType = SECBUFFER_EMPTY;
+
+ /* initialize buffer descriptors */
+ ibufs.ulVersion = obufs.ulVersion = SECBUFFER_VERSION;
+ ibufs.cBuffers = 2; obufs.cBuffers = 1;
+ ibufs.pBuffers = ibuf; obufs.pBuffers = obuf;
+
+ st = InitializeSecurityContext(&conn->cred, size?&conn->context:NULL, conn->host, req, 0, SECURITY_NETWORK_DREP, size?&ibufs:NULL, 0, &conn->context, &obufs, &a, &timestamp);
+ if (obuf[0].pvBuffer && obuf[0].cbBuffer) {
+ /* FIXME: Check return value */
+ send(conn->fd, obuf[0].pvBuffer, obuf[0].cbBuffer, 0);
+ }
+
+ switch (st) {
+ case SEC_I_INCOMPLETE_CREDENTIALS:
+ break;
+ case SEC_I_CONTINUE_NEEDED:
+ break;
+ case SEC_E_INCOMPLETE_MESSAGE:
+ break;
+ case SEC_E_OK:
+ break;
+ }
+
+ QueryContextAttributes(&conn->context, SECPKG_ATTR_STREAM_SIZES, &conn->sizes);
+ } while (1);
+
+ conn->func(conn->data, conn, cond);
+}
+
+int ssl_read(void *conn, char *retdata, int len)
+{
+ struct scd *scd = conn;
+ SecBufferDesc msg;
+ SecBuffer buf[4];
+ int ret = -1, i;
+ char *data = g_malloc(scd->sizes.cbHeader + scd->sizes.cbMaximumMessage + scd->sizes.cbTrailer);
+
+ /* FIXME: Try to read some data */
+
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4;
+ msg.pBuffers = buf;
+
+ buf[0].BufferType = SECBUFFER_DATA;
+ buf[0].cbBuffer = len;
+ buf[0].pvBuffer = data;
+
+ buf[1].BufferType = SECBUFFER_EMPTY;
+ buf[2].BufferType = SECBUFFER_EMPTY;
+ buf[3].BufferType = SECBUFFER_EMPTY;
+
+ SECURITY_STATUS st = DecryptMessage(&scd->context, &msg, 0, NULL);
+
+ if (st != SEC_E_OK) {
+ /* FIXME */
+ return -1;
+ }
+
+ for (i = 0; i < 4; i++) {
+ if (buf[i].BufferType == SECBUFFER_DATA) {
+ memcpy(retdata, buf[i].pvBuffer, len);
+ ret = len;
+ }
+ }
+
+ g_free(data);
+ return -1;
+}
+
+int ssl_write(void *conn, const char *userdata, int len)
+{
+ struct scd *scd = conn;
+ SecBuffer buf[4];
+ SecBufferDesc msg;
+ char *data;
+ int ret;
+
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 4;
+ msg.pBuffers = buf;
+
+ data = g_malloc(scd->sizes.cbHeader + scd->sizes.cbMaximumMessage + scd->sizes.cbTrailer);
+ memcpy(data + scd->sizes.cbHeader, userdata, len);
+
+ buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+ buf[0].cbBuffer = scd->sizes.cbHeader;
+ buf[0].pvBuffer = data;
+
+ buf[1].BufferType = SECBUFFER_DATA;
+ buf[1].cbBuffer = len;
+ buf[1].pvBuffer = data + scd->sizes.cbHeader;
+
+ buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ buf[2].cbBuffer = scd->sizes.cbTrailer;
+ buf[2].pvBuffer = data + scd->sizes.cbHeader + len;
+ buf[3].BufferType = SECBUFFER_EMPTY;
+
+ SECURITY_STATUS st = EncryptMessage(&scd->context, 0, &msg, 0);
+
+ ret = send(scd->fd, data,
+ buf[0].cbBuffer + buf[1].cbBuffer + buf[2].cbBuffer, 0);
+
+ g_free(data);
+
+ return ret;
+}
+
+void ssl_disconnect(void *conn)
+{
+ struct scd *scd = conn;
+
+ SecBufferDesc msg;
+ SecBuffer buf;
+ DWORD dw;
+
+ dw = SCHANNEL_SHUTDOWN;
+ buf.cbBuffer = sizeof(dw);
+ buf.BufferType = SECBUFFER_TOKEN;
+ buf.pvBuffer = &dw;
+
+ msg.ulVersion = SECBUFFER_VERSION;
+ msg.cBuffers = 1;
+ msg.pBuffers = &buf;
+
+ SECURITY_STATUS st = ApplyControlToken(&scd->context, &msg);
+
+ if (st != SEC_E_OK) {
+ /* FIXME */
+ }
+
+ /* FIXME: call InitializeSecurityContext(Schannel), passing
+ * in empty buffers*/
+
+ DeleteSecurityContext(&scd->context);
+
+ FreeCredentialsHandle(&scd->cred);
+
+ closesocket(scd->fd);
+ g_free(scd->host);
+ g_free(scd);
+}
+
+int ssl_getfd(void *conn)
+{
+ return ((struct scd*)conn)->fd;
+}
+
+GaimInputCondition ssl_getdirection( void *conn )
+{
+ return GAIM_INPUT_WRITE; /* FIXME: or GAIM_INPUT_READ */
+}
diff --git a/lib/url.c b/lib/url.c
index c6fdc4c8..de9966b4 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -25,13 +25,16 @@
#include "url.h"
-/* Convert an URL to a url_t structure */
+/* Convert an URL to a url_t structure */
int url_set( url_t *url, char *set_url )
{
- char s[MAX_STRING];
+ char s[MAX_STRING+1];
char *i;
- /* protocol:// */
+ memset( url, 0, sizeof( url_t ) );
+ memset( s, 0, sizeof( s ) );
+
+ /* protocol:// */
if( ( i = strstr( set_url, "://" ) ) == NULL )
{
url->proto = PROTO_DEFAULT;
@@ -48,13 +51,12 @@ int url_set( url_t *url, char *set_url )
else if( g_strncasecmp( set_url, "socks5", i - set_url ) == 0 )
url->proto = PROTO_SOCKS5;
else
- {
- return( 0 );
- }
+ return 0;
+
strncpy( s, i + 3, MAX_STRING );
}
- /* Split */
+ /* Split */
if( ( i = strchr( s, '/' ) ) == NULL )
{
strcpy( url->file, "/" );
@@ -66,7 +68,7 @@ int url_set( url_t *url, char *set_url )
}
strncpy( url->host, s, MAX_STRING );
- /* Check for username in host field */
+ /* Check for username in host field */
if( strrchr( url->host, '@' ) != NULL )
{
strncpy( url->user, url->host, MAX_STRING );
@@ -75,19 +77,19 @@ int url_set( url_t *url, char *set_url )
strcpy( url->host, i + 1 );
*url->pass = 0;
}
- /* If not: Fill in defaults */
+ /* If not: Fill in defaults */
else
{
*url->user = *url->pass = 0;
}
- /* Password? */
+ /* Password? */
if( ( i = strchr( url->user, ':' ) ) != NULL )
{
*i = 0;
strcpy( url->pass, i + 1 );
}
- /* Port number? */
+ /* Port number? */
if( ( i = strchr( url->host, ':' ) ) != NULL )
{
*i = 0;
diff --git a/lib/url.h b/lib/url.h
index e9e1ecfe..8c038c91 100644
--- a/lib/url.h
+++ b/lib/url.h
@@ -25,20 +25,20 @@
#include "bitlbee.h"
-#define PROTO_HTTP 2
-#define PROTO_HTTPS 5
-#define PROTO_SOCKS4 3
-#define PROTO_SOCKS5 4
-#define PROTO_DEFAULT PROTO_HTTP
+#define PROTO_HTTP 2
+#define PROTO_HTTPS 5
+#define PROTO_SOCKS4 3
+#define PROTO_SOCKS5 4
+#define PROTO_DEFAULT PROTO_HTTP
typedef struct url
{
int proto;
int port;
- char host[MAX_STRING];
- char file[MAX_STRING];
- char user[MAX_STRING];
- char pass[MAX_STRING];
+ char host[MAX_STRING+1];
+ char file[MAX_STRING+1];
+ char user[MAX_STRING+1];
+ char pass[MAX_STRING+1];
} url_t;
int url_set( url_t *url, char *set_url );
diff --git a/lib/xmltree.c b/lib/xmltree.c
new file mode 100644
index 00000000..e65b4f41
--- /dev/null
+++ b/lib/xmltree.c
@@ -0,0 +1,590 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) *
+* *
+* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+#include <glib.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#include "xmltree.h"
+
+static void xt_start_element( GMarkupParseContext *ctx, const gchar *element_name, const gchar **attr_names, const gchar **attr_values, gpointer data, GError **error )
+{
+ struct xt_parser *xt = data;
+ struct xt_node *node = g_new0( struct xt_node, 1 ), *nt;
+ int i;
+
+ node->parent = xt->cur;
+ node->name = g_strdup( element_name );
+
+ /* First count the number of attributes */
+ for( i = 0; attr_names[i]; i ++ );
+
+ /* Then allocate a NULL-terminated array. */
+ node->attr = g_new0( struct xt_attr, i + 1 );
+
+ /* And fill it, saving one variable by starting at the end. */
+ for( i --; i >= 0; i -- )
+ {
+ node->attr[i].key = g_strdup( attr_names[i] );
+ node->attr[i].value = g_strdup( attr_values[i] );
+ }
+
+ /* Add it to the linked list of children nodes, if we have a current
+ node yet. */
+ if( xt->cur )
+ {
+ if( xt->cur->children )
+ {
+ for( nt = xt->cur->children; nt->next; nt = nt->next );
+ nt->next = node;
+ }
+ else
+ {
+ xt->cur->children = node;
+ }
+ }
+ else if( xt->root )
+ {
+ /* ERROR situation: A second root-element??? */
+ }
+
+ /* Now this node will be the new current node. */
+ xt->cur = node;
+ /* And maybe this is the root? */
+ if( xt->root == NULL )
+ xt->root = node;
+}
+
+static void xt_text( GMarkupParseContext *ctx, const gchar *text, gsize text_len, gpointer data, GError **error )
+{
+ struct xt_parser *xt = data;
+ struct xt_node *node = xt->cur;
+
+ if( node == NULL )
+ return;
+
+ /* FIXME: Does g_renew also OFFICIALLY accept NULL arguments? */
+ node->text = g_renew( char, node->text, node->text_len + text_len + 1 );
+ memcpy( node->text + node->text_len, text, text_len );
+ node->text_len += text_len;
+ /* Zero termination is always nice to have. */
+ node->text[node->text_len] = 0;
+}
+
+static void xt_end_element( GMarkupParseContext *ctx, const gchar *element_name, gpointer data, GError **error )
+{
+ struct xt_parser *xt = data;
+
+ xt->cur->flags |= XT_COMPLETE;
+ xt->cur = xt->cur->parent;
+}
+
+GMarkupParser xt_parser_funcs =
+{
+ xt_start_element,
+ xt_end_element,
+ xt_text,
+ NULL,
+ NULL
+};
+
+struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data )
+{
+ struct xt_parser *xt = g_new0( struct xt_parser, 1 );
+
+ xt->data = data;
+ xt->handlers = handlers;
+ xt_reset( xt );
+
+ return xt;
+}
+
+/* Reset the parser, flush everything we have so far. For example, we need
+ this for XMPP when doing TLS/SASL to restart the stream. */
+void xt_reset( struct xt_parser *xt )
+{
+ if( xt->parser )
+ g_markup_parse_context_free( xt->parser );
+
+ xt->parser = g_markup_parse_context_new( &xt_parser_funcs, 0, xt, NULL );
+
+ if( xt->root )
+ {
+ xt_free_node( xt->root );
+ xt->root = NULL;
+ xt->cur = NULL;
+ }
+}
+
+/* Feed the parser, don't execute any handler. Returns -1 on errors, 0 on
+ end-of-stream and 1 otherwise. */
+int xt_feed( struct xt_parser *xt, char *text, int text_len )
+{
+ if( !g_markup_parse_context_parse( xt->parser, text, text_len, &xt->gerr ) )
+ {
+ return -1;
+ }
+
+ return !( xt->root && xt->root->flags & XT_COMPLETE );
+}
+
+/* Find completed nodes and see if a handler has to be called. Passing
+ a node isn't necessary if you want to start at the root, just pass
+ NULL. This second argument is needed for recursive calls. */
+int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth )
+{
+ struct xt_node *c;
+ xt_status st;
+ int i;
+
+ /* Just in case someone likes infinite loops... */
+ if( xt->root == NULL )
+ return 0;
+
+ if( node == NULL )
+ return xt_handle( xt, xt->root, depth );
+
+ if( depth != 0 )
+ for( c = node->children; c; c = c->next )
+ if( !xt_handle( xt, c, depth > 0 ? depth - 1 : depth ) )
+ return 0;
+
+ if( node->flags & XT_COMPLETE && !( node->flags & XT_SEEN ) )
+ {
+ for( i = 0; xt->handlers[i].func; i ++ )
+ {
+ /* This one is fun! \o/ */
+
+ /* If handler.name == NULL it means it should always match. */
+ if( ( xt->handlers[i].name == NULL ||
+ /* If it's not, compare. There should always be a name. */
+ g_strcasecmp( xt->handlers[i].name, node->name ) == 0 ) &&
+ /* If handler.parent == NULL, it's a match. */
+ ( xt->handlers[i].parent == NULL ||
+ /* If there's a parent node, see if the name matches. */
+ ( node->parent ? g_strcasecmp( xt->handlers[i].parent, node->parent->name ) == 0 :
+ /* If there's no parent, the handler should mention <root> as a parent. */
+ g_strcasecmp( xt->handlers[i].parent, "<root>" ) == 0 ) ) )
+ {
+ st = xt->handlers[i].func( node, xt->data );
+
+ if( st == XT_ABORT )
+ return 0;
+ else if( st != XT_NEXT )
+ break;
+ }
+ }
+
+ node->flags |= XT_SEEN;
+ }
+
+ return 1;
+}
+
+/* Garbage collection: Cleans up all nodes that are handled. Useful for
+ streams because there's no reason to keep a complete packet history
+ in memory. */
+void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth )
+{
+ struct xt_node *c, *prev;
+
+ if( !xt || !xt->root )
+ return;
+
+ if( node == NULL )
+ return xt_cleanup( xt, xt->root, depth );
+
+ if( node->flags & XT_SEEN && node == xt->root )
+ {
+ xt_free_node( xt->root );
+ xt->root = xt->cur = NULL;
+ /* xt->cur should be NULL already, BTW... */
+
+ return;
+ }
+
+ /* c contains the current node, prev the previous node (or NULL).
+ I admit, this one's pretty horrible. */
+ for( c = node->children, prev = NULL; c; prev = c, c = c ? c->next : node->children )
+ {
+ if( c->flags & XT_SEEN )
+ {
+ /* Remove the node from the linked list. */
+ if( prev )
+ prev->next = c->next;
+ else
+ node->children = c->next;
+
+ xt_free_node( c );
+
+ /* Since the for loop wants to get c->next, make sure
+ c points at something that exists (and that c->next
+ will actually be the next item we should check). c
+ can be NULL now, if we just removed the first item.
+ That explains the ? thing in for(). */
+ c = prev;
+ }
+ else
+ {
+ /* This node can't be cleaned up yet, but maybe a
+ subnode can. */
+ if( depth != 0 )
+ xt_cleanup( xt, c, depth > 0 ? depth - 1 : depth );
+ }
+ }
+}
+
+static void xt_to_string_real( struct xt_node *node, GString *str )
+{
+ char *buf;
+ struct xt_node *c;
+ int i;
+
+ g_string_append_printf( str, "<%s", node->name );
+
+ for( i = 0; node->attr[i].key; i ++ )
+ {
+ buf = g_markup_printf_escaped( " %s=\"%s\"", node->attr[i].key, node->attr[i].value );
+ g_string_append( str, buf );
+ g_free( buf );
+ }
+
+ if( node->text == NULL && node->children == NULL )
+ {
+ g_string_append( str, "/>" );
+ return;
+ }
+
+ g_string_append( str, ">" );
+ if( node->text_len > 0 )
+ {
+ buf = g_markup_escape_text( node->text, node->text_len );
+ g_string_append( str, buf );
+ g_free( buf );
+ }
+
+ for( c = node->children; c; c = c->next )
+ xt_to_string_real( c, str );
+
+ g_string_append_printf( str, "</%s>", node->name );
+}
+
+char *xt_to_string( struct xt_node *node )
+{
+ GString *ret;
+ char *real;
+
+ ret = g_string_new( "" );
+ xt_to_string_real( node, ret );
+
+ real = ret->str;
+ g_string_free( ret, FALSE );
+
+ return real;
+}
+
+#ifdef DEBUG
+void xt_print( struct xt_node *node )
+{
+ int i;
+ struct xt_node *c;
+
+ /* Indentation */
+ for( c = node; c->parent; c = c->parent )
+ printf( "\t" );
+
+ /* Start the tag */
+ printf( "<%s", node->name );
+
+ /* Print the attributes */
+ for( i = 0; node->attr[i].key; i ++ )
+ printf( " %s=\"%s\"", node->attr[i].key, g_markup_escape_text( node->attr[i].value, -1 ) );
+
+ /* /> in case there's really *nothing* inside this tag, otherwise
+ just >. */
+ /* If this tag doesn't have any content at all... */
+ if( node->text == NULL && node->children == NULL )
+ {
+ printf( "/>\n" );
+ return;
+ /* Then we're finished! */
+ }
+
+ /* Otherwise... */
+ printf( ">" );
+
+ /* Only print the text if it contains more than whitespace (TEST). */
+ if( node->text_len > 0 )
+ {
+ for( i = 0; node->text[i] && isspace( node->text[i] ); i ++ );
+ if( node->text[i] )
+ printf( "%s", g_markup_escape_text( node->text, -1 ) );
+ }
+
+ if( node->children )
+ printf( "\n" );
+
+ for( c = node->children; c; c = c->next )
+ xt_print( c );
+
+ if( node->children )
+ for( c = node; c->parent; c = c->parent )
+ printf( "\t" );
+
+ /* Non-empty tag is now finished. */
+ printf( "</%s>\n", node->name );
+}
+#endif
+
+struct xt_node *xt_dup( struct xt_node *node )
+{
+ struct xt_node *dup = g_new0( struct xt_node, 1 );
+ struct xt_node *c, *dc = NULL;
+ int i;
+
+ /* Let's NOT copy the parent element here BTW! Only do it for children. */
+
+ dup->name = g_strdup( node->name );
+ dup->flags = node->flags;
+ if( node->text )
+ {
+ dup->text = g_memdup( node->text, node->text_len + 1 );
+ dup->text_len = node->text_len;
+ }
+
+ /* Count the number of attributes and allocate the new array. */
+ for( i = 0; node->attr[i].key; i ++ );
+ dup->attr = g_new0( struct xt_attr, i + 1 );
+
+ /* Copy them all! */
+ for( i --; i >= 0; i -- )
+ {
+ dup->attr[i].key = g_strdup( node->attr[i].key );
+ dup->attr[i].value = g_strdup( node->attr[i].value );
+ }
+
+ /* This nice mysterious loop takes care of the children. */
+ for( c = node->children; c; c = c->next )
+ {
+ if( dc == NULL )
+ dc = dup->children = xt_dup( c );
+ else
+ dc = ( dc->next = xt_dup( c ) );
+
+ dc->parent = dup;
+ }
+
+ return dup;
+}
+
+/* Frees a node. This doesn't clean up references to itself from parents! */
+void xt_free_node( struct xt_node *node )
+{
+ int i;
+
+ if( !node )
+ return;
+
+ g_free( node->name );
+ g_free( node->text );
+
+ for( i = 0; node->attr[i].key; i ++ )
+ {
+ g_free( node->attr[i].key );
+ g_free( node->attr[i].value );
+ }
+ g_free( node->attr );
+
+ while( node->children )
+ {
+ struct xt_node *next = node->children->next;
+
+ xt_free_node( node->children );
+ node->children = next;
+ }
+
+ g_free( node );
+}
+
+void xt_free( struct xt_parser *xt )
+{
+ if( !xt )
+ return;
+
+ if( xt->root )
+ xt_free_node( xt->root );
+
+ g_markup_parse_context_free( xt->parser );
+
+ g_free( xt );
+}
+
+/* To find a node's child with a specific name, pass the node's children
+ list, not the node itself! The reason you have to do this by hand: So
+ that you can also use this function as a find-next. */
+struct xt_node *xt_find_node( struct xt_node *node, const char *name )
+{
+ while( node )
+ {
+ if( g_strcasecmp( node->name, name ) == 0 )
+ break;
+
+ node = node->next;
+ }
+
+ return node;
+}
+
+char *xt_find_attr( struct xt_node *node, const char *key )
+{
+ int i;
+
+ if( !node )
+ return NULL;
+
+ for( i = 0; node->attr[i].key; i ++ )
+ if( g_strcasecmp( node->attr[i].key, key ) == 0 )
+ break;
+
+ return node->attr[i].value;
+}
+
+struct xt_node *xt_new_node( char *name, char *text, struct xt_node *children )
+{
+ struct xt_node *node, *c;
+
+ node = g_new0( struct xt_node, 1 );
+ node->name = g_strdup( name );
+ node->children = children;
+ node->attr = g_new0( struct xt_attr, 1 );
+
+ if( text )
+ {
+ node->text_len = strlen( text );
+ node->text = g_memdup( text, node->text_len + 1 );
+ }
+
+ for( c = children; c; c = c->next )
+ {
+ if( c->parent != NULL )
+ {
+ /* ERROR CONDITION: They seem to have a parent already??? */
+ }
+
+ c->parent = node;
+ }
+
+ return node;
+}
+
+void xt_add_child( struct xt_node *parent, struct xt_node *child )
+{
+ struct xt_node *node;
+
+ /* This function can actually be used to add more than one child, so
+ do handle this properly. */
+ for( node = child; node; node = node->next )
+ {
+ if( node->parent != NULL )
+ {
+ /* ERROR CONDITION: They seem to have a parent already??? */
+ }
+
+ node->parent = parent;
+ }
+
+ if( parent->children == NULL )
+ {
+ parent->children = child;
+ }
+ else
+ {
+ for( node = parent->children; node->next; node = node->next );
+ node->next = child;
+ }
+}
+
+void xt_add_attr( struct xt_node *node, const char *key, const char *value )
+{
+ int i;
+
+ /* Now actually it'd be nice if we can also change existing attributes
+ (which actually means this function doesn't have the right name).
+ So let's find out if we have this attribute already... */
+ for( i = 0; node->attr[i].key; i ++ )
+ if( strcmp( node->attr[i].key, key ) == 0 )
+ break;
+
+ if( node->attr[i].key == NULL )
+ {
+ /* If not, allocate space for a new attribute. */
+ node->attr = g_renew( struct xt_attr, node->attr, i + 2 );
+ node->attr[i].key = g_strdup( key );
+ node->attr[i+1].key = NULL;
+ }
+ else
+ {
+ /* Otherwise, free the old value before setting the new one. */
+ g_free( node->attr[i].value );
+ }
+
+ node->attr[i].value = g_strdup( value );
+}
+
+int xt_remove_attr( struct xt_node *node, const char *key )
+{
+ int i, last;
+
+ for( i = 0; node->attr[i].key; i ++ )
+ if( strcmp( node->attr[i].key, key ) == 0 )
+ break;
+
+ /* If we didn't find the attribute... */
+ if( node->attr[i].key == NULL )
+ return 0;
+
+ g_free( node->attr[i].key );
+ g_free( node->attr[i].value );
+
+ /* If it's the last, this is easy: */
+ if( node->attr[i+1].key == NULL )
+ {
+ node->attr[i].key = node->attr[i].value = NULL;
+ }
+ else /* It's also pretty easy, actually. */
+ {
+ /* Find the last item. */
+ for( last = i + 1; node->attr[last+1].key; last ++ );
+
+ node->attr[i] = node->attr[last];
+ node->attr[last].key = NULL;
+ node->attr[last].value = NULL;
+ }
+
+ /* Let's not bother with reallocating memory here. It takes time and
+ most packets don't stay in memory for long anyway. */
+
+ return 1;
+}
diff --git a/lib/xmltree.h b/lib/xmltree.h
new file mode 100644
index 00000000..10677412
--- /dev/null
+++ b/lib/xmltree.h
@@ -0,0 +1,97 @@
+/***************************************************************************\
+* *
+* BitlBee - An IRC to IM gateway *
+* Simple XML (stream) parse tree handling code (Jabber/XMPP, mainly) *
+* *
+* Copyright 2006 Wilmer van der Gaast <wilmer@gaast.net> *
+* *
+* This library is free software; you can redistribute it and/or *
+* modify it under the terms of the GNU Lesser General Public *
+* License as published by the Free Software Foundation, version *
+* 2.1. *
+* *
+* This library is distributed in the hope that it will be useful, *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
+* Lesser General Public License for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this library; if not, write to the Free Software Foundation, *
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA *
+* *
+****************************************************************************/
+
+#ifndef _XMLTREE_H
+#define _XMLTREE_H
+
+typedef enum
+{
+ XT_COMPLETE = 1, /* </tag> reached */
+ XT_SEEN = 2, /* Handler called (or not defined) */
+} xt_flags;
+
+typedef enum
+{
+ XT_ABORT, /* Abort, don't handle the rest anymore */
+ XT_HANDLED, /* Handled this tag properly, go to the next one */
+ XT_NEXT /* Try if there's another matching handler */
+} xt_status;
+
+struct xt_attr
+{
+ char *key, *value;
+};
+
+struct xt_node
+{
+ struct xt_node *parent;
+ struct xt_node *children;
+
+ char *name;
+ struct xt_attr *attr;
+ char *text;
+ int text_len;
+
+ struct xt_node *next;
+ xt_flags flags;
+};
+
+typedef xt_status (*xt_handler_func) ( struct xt_node *node, gpointer data );
+
+struct xt_handler_entry
+{
+ char *name, *parent;
+ xt_handler_func func;
+};
+
+struct xt_parser
+{
+ GMarkupParseContext *parser;
+ struct xt_node *root;
+ struct xt_node *cur;
+
+ const struct xt_handler_entry *handlers;
+ gpointer data;
+
+ GError *gerr;
+};
+
+struct xt_parser *xt_new( const struct xt_handler_entry *handlers, gpointer data );
+void xt_reset( struct xt_parser *xt );
+int xt_feed( struct xt_parser *xt, char *text, int text_len );
+int xt_handle( struct xt_parser *xt, struct xt_node *node, int depth );
+void xt_cleanup( struct xt_parser *xt, struct xt_node *node, int depth );
+char *xt_to_string( struct xt_node *node );
+void xt_print( struct xt_node *node );
+struct xt_node *xt_dup( struct xt_node *node );
+void xt_free_node( struct xt_node *node );
+void xt_free( struct xt_parser *xt );
+struct xt_node *xt_find_node( struct xt_node *node, const char *name );
+char *xt_find_attr( struct xt_node *node, const char *key );
+
+struct xt_node *xt_new_node( char *name, char *text, struct xt_node *children );
+void xt_add_child( struct xt_node *parent, struct xt_node *child );
+void xt_add_attr( struct xt_node *node, const char *key, const char *value );
+int xt_remove_attr( struct xt_node *node, const char *key );
+
+#endif