git.strcat.st

/strcat/barevc.git/ - summarytreelogarchive

subject
init
commit
871c0aff899dc6b6c7e5bf73d9c070d92b454f49
date
2026-05-03T17:58:06Z
message
diff
 Makefile                 |  48 ++++
 README.md                |  12 +
 include/barev.h          |  92 +++++++
 include/barev_avatar.h   |  19 ++
 include/barev_config.h   |  29 ++
 include/barev_ft.h       |  57 ++++
 include/barev_internal.h |  79 ++++++
 include/barev_net.h      |  15 ++
 include/barev_sha1.h     |   8 +
 include/barev_xml.h      |  21 ++
 include/sha1.h           |  52 ++++
 src/barev_avatar.c       | 101 +++++++
 src/barev_client.c       | 672 +++++++++++++++++++++++++++++++++++++++++++++++
 src/barev_config.c       | 111 ++++++++
 src/barev_ft.c           | 335 +++++++++++++++++++++++
 src/barev_net.c          | 127 +++++++++
 src/barev_sha1.c         |  19 ++
 src/barev_xml.c          | 259 ++++++++++++++++++
 src/sha1.c               | 302 +++++++++++++++++++++
 19 files changed, 2358 insertions(+)

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..08cb6cf
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,48 @@
+CC      = cc
+AR      = ar
+RANLIB  = ranlib
+CFLAGS  = -std=c89 -pedantic -Wall -Wextra -O2
+PREFIX  = /usr/local
+DESTDIR =
+
+LIB = libbarevc.a
+
+LIB_SRCS = \
+	src/barev_client.c \
+	src/barev_xml.c \
+	src/barev_net.c \
+	src/barev_config.c \
+	src/barev_avatar.c \
+	src/sha1.c \
+	src/barev_sha1.c \
+	src/barev_ft.c
+
+LIB_OBJS = \
+	src/barev_client.o \
+	src/barev_xml.o \
+	src/barev_net.o \
+	src/barev_config.o \
+	src/barev_avatar.o \
+	src/sha1.o \
+	src/barev_sha1.o \
+	src/barev_ft.o
+
+all: library
+
+library: $(LIB)
+
+$(LIB): $(LIB_OBJS)
+	$(AR) rcs $(LIB) $(LIB_OBJS)
+	$(RANLIB) $(LIB)
+
+.c.o:
+	$(CC) $(CFLAGS) -Iinclude -c $< -o $@
+
+install: library
+	mkdir -p $(DESTDIR)$(PREFIX)/lib
+	mkdir -p $(DESTDIR)$(PREFIX)/include
+	cp $(LIB) $(DESTDIR)$(PREFIX)/lib/$(LIB)
+	cp include/*.h $(DESTDIR)$(PREFIX)/include/
+
+clean:
+	rm -f $(LIB_OBJS) $(LIB)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9b533f6
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+barev implementation in C.
+
+build with make
+
+that will give you `libbarevc.a`
+
+install it with `doas make install`
+
+installs to 
+
+- `$(PREFIX)/lib/libbarevc.a`
+- `$(PREFIX)/include/*.h`
diff --git a/include/barev.h b/include/barev.h
new file mode 100644
index 0000000..5714566
--- /dev/null
+++ b/include/barev.h
@@ -0,0 +1,92 @@
+#ifndef BAREV_H
+#define BAREV_H
+
+/*
+ * barev.h - Pure C89 Barev client public API
+ * Compatibility target: Pascal TBarevClient + barev_c_api exports.
+ */
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define BAREV_VERSION "0.1"
+#define BAREV_DEFAULT_PORT 1337
+
+typedef struct barev_client barev_client_t;
+typedef struct barev_buddy barev_buddy_t;
+
+typedef enum {
+  BAREV_STATUS_OFFLINE = 0,
+  BAREV_STATUS_AVAILABLE = 1,
+  BAREV_STATUS_AWAY = 2,
+  BAREV_STATUS_XA = 3,
+  BAREV_STATUS_DND = 4
+} barev_status_t;
+
+typedef enum {
+  BAREV_CONN_DISCONNECTED = 0,
+  BAREV_CONN_CONNECTING = 1,
+  BAREV_CONN_STREAM_INIT = 2,
+  BAREV_CONN_AUTHENTICATED = 3,
+  BAREV_CONN_ONLINE = 4
+} barev_conn_state_t;
+
+typedef void (*barev_log_cb)(const char *level, const char *msg, void *userdata);
+typedef void (*barev_typing_cb)(barev_buddy_t *buddy, int is_typing, void *userdata);
+typedef void (*barev_buddy_status_cb)(barev_buddy_t *buddy, int old_status, int new_status, void *userdata);
+typedef void (*barev_message_cb)(barev_buddy_t *buddy, const char *msg, void *userdata);
+typedef void (*barev_conn_state_cb)(barev_buddy_t *buddy, int state, void *userdata);
+typedef void (*barev_ft_offer_cb)(barev_buddy_t *buddy, const char *sid, const char *file_name, long file_size, void *userdata);
+typedef void (*barev_ft_progress_cb)(barev_buddy_t *buddy, const char *sid, long done, long total, void *userdata);
+typedef void (*barev_ft_complete_cb)(barev_buddy_t *buddy, const char *sid, const char *path, void *userdata);
+typedef void (*barev_ft_error_cb)(barev_buddy_t *buddy, const char *sid, const char *err, void *userdata);
+
+barev_client_t *barev_client_new(const char *nick, const char *my_ipv6, unsigned short port);
+void barev_client_free(barev_client_t *client);
+int barev_client_start(barev_client_t *client);
+void barev_client_stop(barev_client_t *client);
+void barev_client_process(barev_client_t *client);
+
+const char *barev_client_myjid(const barev_client_t *client);
+
+barev_buddy_t *barev_client_add_buddy(barev_client_t *client, const char *buddy_nick, const char *buddy_ipv6, unsigned short port);
+int barev_client_remove_buddy(barev_client_t *client, const char *buddy_jid);
+barev_buddy_t *barev_client_find_buddy(barev_client_t *client, const char *buddy_jid);
+
+int barev_client_connect(barev_client_t *client, const char *buddy_jid);
+int barev_client_send_message(barev_client_t *client, const char *buddy_jid, const char *msg);
+int barev_client_send_presence(barev_client_t *client, int status, const char *status_msg);
+int barev_client_send_typing(barev_client_t *client, const char *buddy_jid);
+int barev_client_send_paused(barev_client_t *client, const char *buddy_jid);
+int barev_client_send_file_offer(barev_client_t *client, const char *buddy_jid,
+                                 const char *file_name, long file_size);
+int barev_client_accept_file_offer(barev_client_t *client, const char *sid);
+int barev_client_accept_file_offer_as(barev_client_t *client, const char *sid, const char *save_path);
+int barev_client_reject_file_offer(barev_client_t *client, const char *sid);
+
+void barev_set_userdata(barev_client_t *client, void *userdata);
+void barev_set_log_cb(barev_client_t *client, barev_log_cb cb);
+void barev_set_typing_cb(barev_client_t *client, barev_typing_cb cb);
+void barev_set_buddy_status_cb(barev_client_t *client, barev_buddy_status_cb cb);
+void barev_set_message_cb(barev_client_t *client, barev_message_cb cb);
+void barev_set_conn_state_cb(barev_client_t *client, barev_conn_state_cb cb);
+void barev_set_ft_offer_cb(barev_client_t *client, barev_ft_offer_cb cb);
+void barev_set_ft_progress_cb(barev_client_t *client, barev_ft_progress_cb cb);
+void barev_set_ft_complete_cb(barev_client_t *client, barev_ft_complete_cb cb);
+void barev_set_ft_error_cb(barev_client_t *client, barev_ft_error_cb cb);
+
+const char *barev_buddy_get_jid(const barev_buddy_t *buddy);
+const char *barev_buddy_get_nick(const barev_buddy_t *buddy);
+const char *barev_buddy_get_ipv6(const barev_buddy_t *buddy);
+unsigned short barev_buddy_get_port(const barev_buddy_t *buddy);
+int barev_buddy_get_status(const barev_buddy_t *buddy);
+const char *barev_buddy_get_status_message(const barev_buddy_t *buddy);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/barev_avatar.h b/include/barev_avatar.h
new file mode 100644
index 0000000..84abc8c
--- /dev/null
+++ b/include/barev_avatar.h
@@ -0,0 +1,19 @@
+#ifndef BAREV_AVATAR_H
+#define BAREV_AVATAR_H
+
+#include <stddef.h>
+
+typedef struct {
+  char my_avatar_path[256];
+  char my_avatar_hash[41];
+  char my_avatar_mime[64];
+  char *my_avatar_b64;
+} barev_avatar_mgr_t;
+
+void barev_avatar_init(barev_avatar_mgr_t *m);
+void barev_avatar_free(barev_avatar_mgr_t *m);
+int barev_avatar_load_my(barev_avatar_mgr_t *m, const char *path);
+void barev_avatar_clear_my(barev_avatar_mgr_t *m);
+int barev_avatar_generate_vcard(const barev_avatar_mgr_t *m, char *out, size_t out_n);
+
+#endif
diff --git a/include/barev_config.h b/include/barev_config.h
new file mode 100644
index 0000000..37b122f
--- /dev/null
+++ b/include/barev_config.h
@@ -0,0 +1,29 @@
+#ifndef BAREV_CONFIG_H
+#define BAREV_CONFIG_H
+
+#include <stddef.h>
+
+#define BAREV_CFG_MAX_CONTACTS 256
+
+typedef struct {
+  char nick[64];
+  char ipv6[128];
+  unsigned short port;
+  char avatar_path[256];
+} barev_config_contact_t;
+
+typedef struct {
+  char user_nick[64];
+  char user_ipv6[128];
+  unsigned short user_port;
+  char user_avatar_path[256];
+  barev_config_contact_t contacts[BAREV_CFG_MAX_CONTACTS];
+  size_t contact_count;
+} barev_config_t;
+
+void barev_config_init(barev_config_t *cfg);
+int barev_config_load(const char *path, barev_config_t *cfg);
+int barev_config_save(const char *path, const barev_config_t *cfg);
+int barev_config_has_user(const barev_config_t *cfg);
+
+#endif
diff --git a/include/barev_ft.h b/include/barev_ft.h
new file mode 100644
index 0000000..b64869d
--- /dev/null
+++ b/include/barev_ft.h
@@ -0,0 +1,57 @@
+#ifndef BAREV_FT_H
+#define BAREV_FT_H
+
+/* File transfer foundations (C89), compatible direction/state model. */
+
+enum barev_ft_direction {
+  BAREV_FT_SEND = 0,
+  BAREV_FT_RECV = 1
+};
+
+enum barev_ft_state {
+  BAREV_FTS_IDLE = 0,
+  BAREV_FTS_OFFER_SENT,
+  BAREV_FTS_OFFER_RECV,
+  BAREV_FTS_ACCEPTED,
+  BAREV_FTS_STREAMHOST_SENT,
+  BAREV_FTS_STREAMHOST_RECV,
+  BAREV_FTS_TRANSFERRING,
+  BAREV_FTS_DONE,
+  BAREV_FTS_ERROR
+};
+
+int barev_ft_socks5_server_handshake(int sockfd);
+int barev_ft_socks5_client_handshake(int sockfd, const char *dstaddr40);
+int barev_ft_build_si_offer(const char *from_jid, const char *to_jid, const char *id,
+                            const char *sid, const char *file_name, long file_size,
+                            char *out, unsigned long out_n);
+int barev_ft_build_si_accept(const char *from_jid, const char *to_jid, const char *id,
+                             char *out, unsigned long out_n);
+int barev_ft_build_si_reject(const char *from_jid, const char *to_jid, const char *id,
+                             const char *code, char *out, unsigned long out_n);
+int barev_ft_build_bytestreams_query(const char *from_jid, const char *to_jid, const char *id,
+                                     const char *sid, const char *host, unsigned short port,
+                                     char *out, unsigned long out_n);
+int barev_ft_build_bytestreams_used(const char *from_jid, const char *to_jid, const char *id,
+                                    const char *sid, const char *host_jid,
+                                    char *out, unsigned long out_n);
+int barev_ft_is_si_offer(const char *xml);
+int barev_ft_is_bytestreams_query(const char *xml);
+
+int barev_ft_extract_attr(const char *xml, const char *attr, char *out, unsigned long out_n);
+int barev_ft_parse_si_offer(const char *xml,
+                            char *sid, unsigned long sid_n,
+                            char *name, unsigned long name_n,
+                            long *size_out);
+int barev_ft_parse_streamhost(const char *xml,
+                              char *sid, unsigned long sid_n,
+                              char *host, unsigned long host_n,
+                              unsigned short *port_out,
+                              char *jid, unsigned long jid_n);
+int barev_ft_compute_dstaddr40(const char *sid, const char *initiator_jid, const char *target_jid,
+                               char out40[41]);
+int barev_ft_recv_file_blocking(int sockfd, const char *save_path, long expected_size, long *bytes_done);
+int barev_ft_listen_range(unsigned short min_port, unsigned short max_port, unsigned short *bound_port);
+int barev_ft_send_file_blocking(int sockfd, const char *path, long *bytes_done);
+
+#endif
diff --git a/include/barev_internal.h b/include/barev_internal.h
new file mode 100644
index 0000000..90aa7cd
--- /dev/null
+++ b/include/barev_internal.h
@@ -0,0 +1,79 @@
+#ifndef BAREV_INTERNAL_H
+#define BAREV_INTERNAL_H
+
+#include "barev.h"
+#include "barev_avatar.h"
+#include <time.h>
+
+#define BAREV_MAX_NICK 63
+#define BAREV_MAX_IPV6 127
+#define BAREV_MAX_JID 255
+#define BAREV_MAX_STATUS_MSG 255
+#define BAREV_RECV_BUF 8192
+#define BAREV_MAX_PENDING_OFFERS 32
+#define BAREV_PING_INTERVAL 30
+#define BAREV_PING_TIMEOUT 10
+#define BAREV_MAX_PING_FAILURES 3
+
+typedef struct barev_buddy {
+  char jid[BAREV_MAX_JID + 1];
+  char nick[BAREV_MAX_NICK + 1];
+  char ipv6[BAREV_MAX_IPV6 + 1];
+  unsigned short port;
+  int status;
+  char status_msg[BAREV_MAX_STATUS_MSG + 1];
+
+  int sock;
+  int we_initiated;
+  int stream_sent;
+  int stream_recv;
+  int conn_state;
+  int ping_failures;
+  time_t last_activity;
+  time_t last_ping_sent;
+  unsigned int ping_seq;
+  char last_ping_id[32];
+  char recv_buf[BAREV_RECV_BUF];
+  size_t recv_len;
+} barev_buddy_impl_t;
+
+typedef struct barev_ft_offer {
+  int used;
+  int accepted;
+  int outgoing;
+  char sid[128];
+  char iq_id[128];
+  size_t buddy_index;
+  char file_name[256];
+  char save_path[512];
+  long file_size;
+} barev_ft_offer_t;
+
+struct barev_client {
+  char nick[BAREV_MAX_NICK + 1];
+  char my_ipv6[BAREV_MAX_IPV6 + 1];
+  unsigned short port;
+  char my_jid[BAREV_MAX_JID + 1];
+  int running;
+  int listen_fd;
+  barev_avatar_mgr_t avatar_mgr;
+  unsigned int ft_seq;
+  barev_ft_offer_t pending_offers[BAREV_MAX_PENDING_OFFERS];
+
+  barev_buddy_impl_t *buddies;
+  size_t buddy_count;
+  size_t buddy_cap;
+
+  void *userdata;
+  barev_log_cb on_log;
+  barev_typing_cb on_typing;
+  barev_buddy_status_cb on_buddy_status;
+  barev_message_cb on_message;
+  barev_conn_state_cb on_conn_state;
+  barev_ft_offer_cb on_ft_offer;
+  barev_ft_progress_cb on_ft_progress;
+  barev_ft_complete_cb on_ft_complete;
+  barev_ft_error_cb on_ft_error;
+};
+
+#endif
diff --git a/include/barev_net.h b/include/barev_net.h
new file mode 100644
index 0000000..1ad6b21
--- /dev/null
+++ b/include/barev_net.h
@@ -0,0 +1,15 @@
+#ifndef BAREV_NET_H
+#define BAREV_NET_H
+
+#include <stddef.h>
+
+int barev_net_set_nonblocking(int fd);
+int barev_net_listen_v6(unsigned short port);
+int barev_net_accept_v6(int listen_fd, char *addr_out, size_t addr_out_n, unsigned short *port_out);
+int barev_net_connect_v6(const char *ipv6, unsigned short port);
+int barev_net_is_readable(int fd, int timeout_ms);
+int barev_net_send_all(int fd, const char *buf, size_t n);
+int barev_net_recv_some(int fd, char *buf, size_t n);
+void barev_net_close(int fd);
+
+#endif
diff --git a/include/barev_sha1.h b/include/barev_sha1.h
new file mode 100644
index 0000000..2b03ca4
--- /dev/null
+++ b/include/barev_sha1.h
@@ -0,0 +1,8 @@
+#ifndef BAREV_SHA1_H
+#define BAREV_SHA1_H
+
+#include <stddef.h>
+
+void barev_sha1_hex(const unsigned char *data, size_t len, char out_hex40[41]);
+
+#endif
diff --git a/include/barev_xml.h b/include/barev_xml.h
new file mode 100644
index 0000000..1584575
--- /dev/null
+++ b/include/barev_xml.h
@@ -0,0 +1,21 @@
+#ifndef BAREV_XML_H
+#define BAREV_XML_H
+
+/* String-based XML helpers with zero external dependencies. */
+
+#include <stddef.h>
+
+int barev_xml_escape(const char *in, char *out, size_t out_size);
+int barev_build_stream_header(const char *from_jid, const char *to_jid, char *out, size_t out_size);
+int barev_build_stream_end(char *out, size_t out_size);
+int barev_build_presence(const char *to_jid, int status, const char *status_msg, char *out, size_t out_size);
+int barev_build_message(const char *from_jid, const char *to_jid, const char *body, char *out, size_t out_size);
+int barev_build_ping(const char *from_jid, const char *to_jid, const char *ping_id, char *out, size_t out_size);
+int barev_build_pong(const char *to_jid, const char *ping_id, char *out, size_t out_size);
+int barev_build_chatstate(const char *to_jid, const char *state_name, char *out, size_t out_size);
+int barev_build_vcard_get(const char *to_jid, const char *id, char *out, size_t out_size);
+int barev_build_vcard_result(const char *to_jid, const char *id, const char *vcard_inner, char *out, size_t out_size);
+int barev_extract_attribute(const char *xml, const char *attr_name, char *out, size_t out_size);
+int barev_extract_element_content(const char *xml, const char *element, char *out, size_t out_size);
+
+#endif
diff --git a/include/sha1.h b/include/sha1.h
new file mode 100644
index 0000000..f492009
--- /dev/null
+++ b/include/sha1.h
@@ -0,0 +1,52 @@
+#ifndef SHA1_H
+#define SHA1_H
+
+/*
+   SHA-1 in C
+   By Steve Reid <steve@edmweb.com>
+   100% Public Domain
+ */
+
+#include "stdint.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+typedef struct
+{
+    uint32_t state[5];
+    uint32_t count[2];
+    unsigned char buffer[64];
+} SHA1_CTX;
+
+void SHA1Transform(
+    uint32_t state[5],
+    const unsigned char buffer[64]
+    );
+
+void SHA1Init(
+    SHA1_CTX * context
+    );
+
+void SHA1Update(
+    SHA1_CTX * context,
+    const unsigned char *data,
+    uint32_t len
+    );
+
+void SHA1Final(
+    unsigned char digest[20],
+    SHA1_CTX * context
+    );
+
+void SHA1(
+    char *hash_out,
+    const char *str,
+    uint32_t len);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* SHA1_H */
diff --git a/src/barev_avatar.c b/src/barev_avatar.c
new file mode 100644
index 0000000..292bd67
--- /dev/null
+++ b/src/barev_avatar.c
@@ -0,0 +1,101 @@
+#include "barev_avatar.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "barev_sha1.h"
+
+static const char b64tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+static void scpy(char *d, size_t n, const char *s) {
+  size_t i;
+  if (!d || n == 0) return;
+  if (!s) { d[0] = '\0'; return; }
+  for (i = 0; i + 1 < n && s[i]; ++i) d[i] = s[i];
+  d[i] = '\0';
+}
+
+static int detect_mime(const char *p, char *out, size_t n) {
+  const char *ext = strrchr(p, '.');
+  if (!ext) { scpy(out, n, "application/octet-stream"); return 1; }
+  if (strcmp(ext, ".png") == 0) scpy(out, n, "image/png");
+  else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) scpy(out, n, "image/jpeg");
+  else if (strcmp(ext, ".gif") == 0) scpy(out, n, "image/gif");
+  else scpy(out, n, "application/octet-stream");
+  return 1;
+}
+
+static char *b64_encode(const unsigned char *in, size_t n) {
+  size_t out_n = ((n + 2) / 3) * 4;
+  size_t i, o = 0;
+  char *out = (char *)malloc(out_n + 1);
+  if (!out) return 0;
+  for (i = 0; i < n; i += 3) {
+    unsigned a = in[i];
+    unsigned b = (i + 1 < n) ? in[i + 1] : 0;
+    unsigned c = (i + 2 < n) ? in[i + 2] : 0;
+    unsigned t = (a << 16) | (b << 8) | c;
+    out[o++] = b64tab[(t >> 18) & 63];
+    out[o++] = b64tab[(t >> 12) & 63];
+    out[o++] = (i + 1 < n) ? b64tab[(t >> 6) & 63] : '=';
+    out[o++] = (i + 2 < n) ? b64tab[t & 63] : '=';
+  }
+  out[o] = '\0';
+  return out;
+}
+
+void barev_avatar_init(barev_avatar_mgr_t *m) { if (m) memset(m, 0, sizeof(*m)); }
+
+void barev_avatar_free(barev_avatar_mgr_t *m) {
+  if (!m) return;
+  free(m->my_avatar_b64);
+  m->my_avatar_b64 = 0;
+}
+
+void barev_avatar_clear_my(barev_avatar_mgr_t *m) {
+  if (!m) return;
+  free(m->my_avatar_b64);
+  m->my_avatar_b64 = 0;
+  m->my_avatar_path[0] = '\0';
+  m->my_avatar_hash[0] = '\0';
+  m->my_avatar_mime[0] = '\0';
+}
+
+int barev_avatar_load_my(barev_avatar_mgr_t *m, const char *path) {
+  FILE *f;
+  unsigned char *buf;
+  long n;
+  if (!m || !path) return 0;
+  f = fopen(path, "rb");
+  if (!f) return 0;
+  if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return 0; }
+  n = ftell(f);
+  if (n < 0) { fclose(f); return 0; }
+  if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return 0; }
+  buf = (unsigned char *)malloc((size_t)n);
+  if (!buf) { fclose(f); return 0; }
+  if ((long)fread(buf, 1, (size_t)n, f) != n) { free(buf); fclose(f); return 0; }
+  fclose(f);
+
+  barev_avatar_clear_my(m);
+  m->my_avatar_b64 = b64_encode(buf, (size_t)n);
+  if (!m->my_avatar_b64) { free(buf); return 0; }
+  detect_mime(path, m->my_avatar_mime, sizeof(m->my_avatar_mime));
+  scpy(m->my_avatar_path, sizeof(m->my_avatar_path), path);
+  barev_sha1_hex((const unsigned char *)m->my_avatar_b64, strlen(m->my_avatar_b64), m->my_avatar_hash);
+  free(buf);
+  return 1;
+}
+
+int barev_avatar_generate_vcard(const barev_avatar_mgr_t *m, char *out, size_t out_n) {
+  int w;
+  if (!m || !out || out_n == 0) return 0;
+  if (!m->my_avatar_b64 || !m->my_avatar_b64[0]) {
+    w = snprintf(out, out_n, "<vCard xmlns=\"vcard-temp\"></vCard>");
+  } else {
+    w = snprintf(out, out_n,
+      "<vCard xmlns=\"vcard-temp\"><PHOTO><TYPE>%s</TYPE><BINVAL>%s</BINVAL></PHOTO></vCard>",
+      m->my_avatar_mime, m->my_avatar_b64);
+  }
+  return (w > 0 && (size_t)w < out_n) ? 1 : 0;
+}
diff --git a/src/barev_client.c b/src/barev_client.c
new file mode 100644
index 0000000..2ce1729
--- /dev/null
+++ b/src/barev_client.c
@@ -0,0 +1,672 @@
+#include "barev_internal.h"
+#include "barev_net.h"
+#include "barev_xml.h"
+#include "barev_avatar.h"
+#include "barev_ft.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+static void barev_strcpy0(char *dst, size_t dst_n, const char *src) {
+  size_t i;
+  if (dst_n == 0) return;
+  if (!src) { dst[0] = '\0'; return; }
+  for (i = 0; i + 1 < dst_n && src[i] != '\0'; ++i) dst[i] = src[i];
+  dst[i] = '\0';
+}
+
+static void log_msg(struct barev_client *c, const char *lvl, const char *msg) {
+  if (c && c->on_log) c->on_log(lvl, msg, c->userdata);
+}
+
+static void emit_conn(struct barev_client *c, barev_buddy_impl_t *b, int st) {
+  b->conn_state = st;
+  if (c && c->on_conn_state) c->on_conn_state((barev_buddy_t *)b, st, c->userdata);
+}
+
+static void emit_buddy_status(struct barev_client *c, barev_buddy_impl_t *b, int old_s, int new_s) {
+  if (c && c->on_buddy_status) c->on_buddy_status((barev_buddy_t *)b, old_s, new_s, c->userdata);
+}
+
+static void barev_join_jid(char *out, size_t out_n, const char *nick, const char *ipv6) {
+  size_t nlen, ilen, i;
+  if (!out || out_n == 0) return;
+  out[0] = '\0';
+  if (!nick || !ipv6) return;
+  nlen = strlen(nick); ilen = strlen(ipv6);
+  if (nlen + 1 + ilen + 1 > out_n) return;
+  for (i = 0; i < nlen; ++i) out[i] = nick[i];
+  out[nlen] = '@';
+  for (i = 0; i < ilen; ++i) out[nlen + 1 + i] = ipv6[i];
+  out[nlen + 1 + ilen] = '\0';
+}
+
+static int barev_same_jid(const char *a, const char *b) { return (a && b && strcmp(a, b) == 0) ? 1 : 0; }
+
+static int barev_ensure_buddy_cap(struct barev_client *client) {
+  barev_buddy_impl_t *nptr;
+  size_t ncap;
+  if (client->buddy_count < client->buddy_cap) return 1;
+  ncap = client->buddy_cap == 0 ? 8 : client->buddy_cap * 2;
+  nptr = (barev_buddy_impl_t *)realloc(client->buddies, ncap * sizeof(*nptr));
+  if (!nptr) return 0;
+  client->buddies = nptr;
+  client->buddy_cap = ncap;
+  return 1;
+}
+
+static barev_buddy_impl_t *find_buddy_by_ip(struct barev_client *c, const char *ip) {
+  size_t i;
+  for (i = 0; i < c->buddy_count; ++i) if (strcmp(c->buddies[i].ipv6, ip) == 0) return &c->buddies[i];
+  return 0;
+}
+
+static size_t buddy_index_of(struct barev_client *c, barev_buddy_impl_t *b) {
+  size_t i;
+  for (i = 0; i < c->buddy_count; ++i) if (&c->buddies[i] == b) return i;
+  return (size_t)-1;
+}
+
+static barev_ft_offer_t *find_offer_by_sid(struct barev_client *c, const char *sid) {
+  size_t i;
+  for (i = 0; i < BAREV_MAX_PENDING_OFFERS; ++i) {
+    if (c->pending_offers[i].used && strcmp(c->pending_offers[i].sid, sid) == 0) return &c->pending_offers[i];
+  }
+  return 0;
+}
+
+static barev_ft_offer_t *find_offer_by_iqid(struct barev_client *c, const char *iqid) {
+  size_t i;
+  for (i = 0; i < BAREV_MAX_PENDING_OFFERS; ++i) {
+    if (c->pending_offers[i].used && strcmp(c->pending_offers[i].iq_id, iqid) == 0) return &c->pending_offers[i];
+  }
+  return 0;
+}
+
+static barev_ft_offer_t *alloc_offer_slot(struct barev_client *c) {
+  size_t i;
+  for (i = 0; i < BAREV_MAX_PENDING_OFFERS; ++i) {
+    if (!c->pending_offers[i].used) {
+      memset(&c->pending_offers[i], 0, sizeof(c->pending_offers[i]));
+      c->pending_offers[i].used = 1;
+      return &c->pending_offers[i];
+    }
+  }
+  return 0;
+}
+
+static int send_stream_header(struct barev_client *c, barev_buddy_impl_t *b) {
+  char out[2048];
+  if (!barev_build_stream_header(c->my_jid, b->jid, out, sizeof(out))) return 0;
+  if (barev_net_send_all(b->sock, out, strlen(out)) < 0) return 0;
+  b->stream_sent = 1;
+  return 1;
+}
+
+static int send_ping(struct barev_client *c, barev_buddy_impl_t *b, time_t now_ts) {
+  char out[1024];
+  unsigned int next_seq;
+  next_seq = b->ping_seq + 1;
+  b->ping_seq = next_seq;
+  b->last_ping_id[0] = '\0';
+  (void)sprintf(b->last_ping_id, "ping-%u", next_seq);
+  if (!barev_build_ping(c->my_jid, b->jid, b->last_ping_id, out, sizeof(out))) return 0;
+  if (barev_net_send_all(b->sock, out, strlen(out)) < 0) return 0;
+  b->last_ping_sent = now_ts;
+  return 1;
+}
+
+static void close_buddy_sock(barev_buddy_impl_t *b) {
+  if (b->sock >= 0) {
+    barev_net_close(b->sock);
+    b->sock = -1;
+  }
+  b->stream_sent = 0;
+  b->stream_recv = 0;
+  b->conn_state = BAREV_CONN_DISCONNECTED;
+  b->ping_failures = 0;
+  b->last_ping_sent = 0;
+  b->last_ping_id[0] = '\0';
+  b->recv_len = 0;
+  b->recv_buf[0] = '\0';
+}
+
+static void process_stanza(struct barev_client *c, barev_buddy_impl_t *b, const char *xml) {
+  char body[2048], id[256], pong[2048], vcard[1024];
+  if (strstr(xml, "<stream:stream") != 0) {
+    b->stream_recv = 1;
+    emit_conn(c, b, BAREV_CONN_STREAM_INIT);
+    if (!b->stream_sent) send_stream_header(c, b);
+    emit_conn(c, b, BAREV_CONN_AUTHENTICATED);
+    return;
+  }
+  if (strstr(xml, "<presence") != 0) {
+    int old_s = b->status;
+    b->status = BAREV_STATUS_AVAILABLE;
+    emit_buddy_status(c, b, old_s, b->status);
+    emit_conn(c, b, BAREV_CONN_ONLINE);
+    return;
+  }
+  if (strstr(xml, "<message") != 0) {
+    if (strstr(xml, "http://jabber.org/protocol/chatstates") != 0) {
+      if (c->on_typing) {
+        if (strstr(xml, "<composing ") != 0 || strstr(xml, "<composing xmlns=") != 0)
+          c->on_typing((barev_buddy_t *)b, 1, c->userdata);
+        else
+          c->on_typing((barev_buddy_t *)b, 0, c->userdata);
+      }
+      return;
+    }
+    if (barev_extract_element_content(xml, "body", body, sizeof(body))) {
+      if (c->on_message) c->on_message((barev_buddy_t *)b, body, c->userdata);
+    }
+    return;
+  }
+  if (strstr(xml, "<iq") != 0) {
+    if (strstr(xml, "urn:xmpp:ping") != 0) {
+      if (barev_extract_attribute(xml, "id", id, sizeof(id))) {
+        if (barev_build_pong(b->jid, id, pong, sizeof(pong))) {
+          (void)barev_net_send_all(b->sock, pong, strlen(pong));
+        }
+      }
+      return;
+    }
+    if (strstr(xml, "vcard-temp") != 0) {
+      if ((strstr(xml, "type=\"get\"") != 0 || strstr(xml, "type='get'") != 0) &&
+          barev_extract_attribute(xml, "id", id, sizeof(id))) {
+        if (!barev_avatar_generate_vcard(&c->avatar_mgr, vcard, sizeof(vcard))) {
+          strcpy(vcard, "<vCard xmlns=\"vcard-temp\"/>");
+        }
+        if (barev_build_vcard_result(b->jid, id, vcard, pong, sizeof(pong))) {
+          (void)barev_net_send_all(b->sock, pong, strlen(pong));
+        }
+      }
+      return;
+    }
+    if (barev_ft_is_si_offer(xml)) {
+      char sid[128], fn[256], rej[2048];
+      long fsz;
+      if (barev_ft_parse_si_offer(xml, sid, sizeof(sid), fn, sizeof(fn), &fsz)) {
+        barev_ft_offer_t *o = alloc_offer_slot(c);
+        if (o) {
+          strcpy(o->sid, sid);
+          strcpy(o->file_name, fn);
+          o->file_size = fsz;
+          o->buddy_index = buddy_index_of(c, b);
+          if (barev_extract_attribute(xml, "id", id, sizeof(id))) strcpy(o->iq_id, id);
+        }
+        if (c->on_log) {
+          char msg[512];
+          sprintf(msg, "FT offer from %s sid=%s file=%s size=%ld", b->jid, sid, fn, fsz);
+          c->on_log("INFO", msg, c->userdata);
+        }
+        if (c->on_ft_offer) c->on_ft_offer((barev_buddy_t *)b, sid, fn, fsz, c->userdata);
+      }
+      (void)rej;
+      return;
+    }
+    if (barev_ft_is_bytestreams_query(xml)) {
+      char sid[128], host[256], host_jid[256], used[2048];
+      unsigned short port;
+      sid[0] = '\0';
+      if (barev_ft_parse_streamhost(xml, sid, sizeof(sid), host, sizeof(host), &port, host_jid, sizeof(host_jid))) {
+        if (c->on_log) {
+          char msg[512];
+          sprintf(msg, "FT bytestream query sid=%s host=%s port=%u jid=%s", sid, host, (unsigned)port, host_jid);
+          c->on_log("INFO", msg, c->userdata);
+        }
+      }
+      if (sid[0]) {
+        barev_ft_offer_t *o = find_offer_by_sid(c, sid);
+        if (o && o->accepted) {
+          int dsock;
+          char dst40[41];
+          long got = 0;
+          const char *save_path = o->save_path[0] ? o->save_path : o->file_name;
+          dsock = barev_net_connect_v6(host, port);
+          if (dsock >= 0 && barev_ft_compute_dstaddr40(sid, host_jid, c->my_jid, dst40)) {
+            if (barev_ft_socks5_client_handshake(dsock, dst40)) {
+              if (barev_ft_recv_file_blocking(dsock, save_path, o->file_size, &got)) {
+                if (c->on_ft_progress) c->on_ft_progress((barev_buddy_t *)b, o->sid, got, o->file_size, c->userdata);
+                if (c->on_ft_complete) c->on_ft_complete((barev_buddy_t *)b, o->sid, save_path, c->userdata);
+                if (c->on_log) c->on_log("INFO", "FT receive complete", c->userdata);
+              } else {
+                if (c->on_ft_error) c->on_ft_error((barev_buddy_t *)b, o->sid, "receive failed", c->userdata);
+                if (c->on_log) c->on_log("ERROR", "FT receive failed", c->userdata);
+              }
+            }
+            barev_net_close(dsock);
+          }
+          o->used = 0;
+        }
+      }
+      if (barev_extract_attribute(xml, "id", id, sizeof(id))) {
+        if (barev_ft_build_bytestreams_used(c->my_jid, b->jid, id, sid[0] ? sid : "sid", c->my_jid, used, sizeof(used))) {
+          (void)barev_net_send_all(b->sock, used, strlen(used));
+        }
+      }
+      return;
+    }
+    if (strstr(xml, "type=\"result\"") != 0 || strstr(xml, "type='result'") != 0) {
+      if (barev_extract_attribute(xml, "id", id, sizeof(id))) {
+        /* FT: when our SI offer is accepted, send bytestream negotiation query. */
+        if (strncmp(id, "ft-offer-", 9) == 0) {
+          char sid[128], bsid[128];
+          char q[3072];
+          barev_ft_offer_t *o = find_offer_by_iqid(c, id);
+          strcpy(sid, id);
+          sprintf(bsid, "ft-bs-%u", ++c->ft_seq);
+          if (barev_ft_build_bytestreams_query(c->my_jid, b->jid, bsid, sid, c->my_ipv6, 50000, q, sizeof(q))) {
+            (void)barev_net_send_all(b->sock, q, strlen(q));
+            if (c->on_log) c->on_log("INFO", "FT SI accepted; sent bytestream query", c->userdata);
+            if (o) strcpy(o->iq_id, bsid);
+          }
+        } else if (strncmp(id, "ft-bs-", 6) == 0) {
+          barev_ft_offer_t *o = find_offer_by_iqid(c, id);
+          if (o && o->outgoing) {
+            unsigned short lport = 0;
+            int ls = barev_ft_listen_range(50000, 50049, &lport);
+            if (ls >= 0) {
+              int ds = accept(ls, 0, 0);
+              if (ds >= 0) {
+                long sent = 0;
+                if (barev_ft_socks5_server_handshake(ds)) {
+                  if (barev_ft_send_file_blocking(ds, o->file_name, &sent)) {
+                    if (c->on_ft_progress) c->on_ft_progress((barev_buddy_t *)b, o->sid, sent, o->file_size, c->userdata);
+                    if (c->on_ft_complete) c->on_ft_complete((barev_buddy_t *)b, o->sid, o->file_name, c->userdata);
+                    if (c->on_log) c->on_log("INFO", "FT send complete", c->userdata);
+                  } else {
+                    if (c->on_ft_error) c->on_ft_error((barev_buddy_t *)b, o->sid, "send failed", c->userdata);
+                    if (c->on_log) c->on_log("ERROR", "FT send failed", c->userdata);
+                  }
+                }
+                close(ds);
+              }
+              close(ls);
+            }
+            o->used = 0;
+          }
+        }
+        if (b->last_ping_id[0] != '\0' && strcmp(id, b->last_ping_id) == 0) {
+          b->last_ping_sent = 0;
+          b->ping_failures = 0;
+        }
+      }
+      return;
+    }
+  }
+}
+
+static void process_recv_buffer(struct barev_client *c, barev_buddy_impl_t *b) {
+  char *start;
+  char *end;
+  size_t len;
+  char stanza[4096];
+
+  if (b->recv_len == 0) return;
+  b->recv_buf[b->recv_len] = '\0';
+
+  if (strstr(b->recv_buf, "<stream:stream") != 0) {
+    process_stanza(c, b, "<stream:stream>");
+  }
+
+  for (;;) {
+    start = strchr(b->recv_buf, '<');
+    if (!start) break;
+
+    if (strncmp(start, "<message", 8) == 0) {
+      end = strstr(start, "</message>");
+      if (!end) break;
+      end += 10;
+    } else if (strncmp(start, "<presence", 9) == 0) {
+      end = strstr(start, "</presence>");
+      if (end) end += 11;
+      else {
+        end = strstr(start, "/>");
+        if (!end) break;
+        end += 2;
+      }
+    } else if (strncmp(start, "<iq", 3) == 0) {
+      end = strstr(start, "</iq>");
+      if (end) end += 5;
+      else {
+        end = strstr(start, "/>");
+        if (!end) break;
+        end += 2;
+      }
+    } else {
+      /* Drop unknown prefix byte to avoid deadlock on unparsed stanzas. */
+      memmove(b->recv_buf, b->recv_buf + 1, b->recv_len - 1);
+      b->recv_len--;
+      b->recv_buf[b->recv_len] = '\0';
+      continue;
+    }
+
+    len = (size_t)(end - start);
+    if (len >= sizeof(stanza)) len = sizeof(stanza) - 1;
+    memcpy(stanza, start, len);
+    stanza[len] = '\0';
+    process_stanza(c, b, stanza);
+
+    len = (size_t)(end - b->recv_buf);
+    if (len < b->recv_len) {
+      memmove(b->recv_buf, b->recv_buf + len, b->recv_len - len);
+      b->recv_len -= len;
+      b->recv_buf[b->recv_len] = '\0';
+    } else {
+      b->recv_len = 0;
+      b->recv_buf[0] = '\0';
+      break;
+    }
+  }
+}
+
+barev_client_t *barev_client_new(const char *nick, const char *my_ipv6, unsigned short port) {
+  struct barev_client *c;
+  c = (struct barev_client *)calloc(1, sizeof(*c));
+  if (!c) return 0;
+  c->port = port ? port : BAREV_DEFAULT_PORT;
+  c->listen_fd = -1;
+  barev_avatar_init(&c->avatar_mgr);
+  barev_strcpy0(c->nick, sizeof(c->nick), nick);
+  barev_strcpy0(c->my_ipv6, sizeof(c->my_ipv6), my_ipv6);
+  barev_join_jid(c->my_jid, sizeof(c->my_jid), c->nick, c->my_ipv6);
+  return c;
+}
+
+void barev_client_free(barev_client_t *client) {
+  size_t i;
+  if (!client) return;
+  barev_avatar_free(&client->avatar_mgr);
+  for (i = 0; i < client->buddy_count; ++i) close_buddy_sock(&client->buddies[i]);
+  if (client->listen_fd >= 0) barev_net_close(client->listen_fd);
+  free(client->buddies);
+  free(client);
+}
+
+int barev_client_start(barev_client_t *client) {
+  if (!client) return 0;
+  if (client->running) return 1;
+  client->listen_fd = barev_net_listen_v6(client->port);
+  if (client->listen_fd < 0) return 0;
+  client->running = 1;
+  log_msg(client, "INFO", "Barev C client started");
+  return 1;
+}
+
+void barev_client_stop(barev_client_t *client) {
+  size_t i;
+  if (!client) return;
+  for (i = 0; i < client->buddy_count; ++i) close_buddy_sock(&client->buddies[i]);
+  if (client->listen_fd >= 0) { barev_net_close(client->listen_fd); client->listen_fd = -1; }
+  client->running = 0;
+}
+
+void barev_client_process(barev_client_t *client) {
+  size_t i;
+  int fd;
+  char ip[128];
+  unsigned short port;
+  barev_buddy_impl_t *b;
+  time_t now_ts;
+
+  if (!client || !client->running) return;
+  now_ts = time((time_t *)0);
+
+  if (client->listen_fd >= 0 && barev_net_is_readable(client->listen_fd, 0)) {
+    fd = barev_net_accept_v6(client->listen_fd, ip, sizeof(ip), &port);
+      if (fd >= 0) {
+        b = find_buddy_by_ip(client, ip);
+        if (b) {
+          close_buddy_sock(b);
+          b->sock = fd;
+          b->we_initiated = 0;
+          b->last_activity = now_ts;
+          emit_conn(client, b, BAREV_CONN_CONNECTING);
+        } else {
+          barev_net_close(fd);
+      }
+    }
+  }
+
+  for (i = 0; i < client->buddy_count; ++i) {
+    b = &client->buddies[i];
+    if (b->sock < 0) continue;
+    if (barev_net_is_readable(b->sock, 0)) {
+      char tmp[2048];
+      int r = barev_net_recv_some(b->sock, tmp, sizeof(tmp) - 1);
+      if (r > 0) {
+        size_t can = sizeof(b->recv_buf) - 1 - b->recv_len;
+        size_t cp = (size_t)r;
+        if (cp > can) cp = can;
+        memcpy(b->recv_buf + b->recv_len, tmp, cp);
+        b->recv_len += cp;
+        b->recv_buf[b->recv_len] = '\0';
+        b->last_activity = now_ts;
+        b->ping_failures = 0;
+        process_recv_buffer(client, b);
+      } else if (r == 0 || r == -1) {
+        int old_s = b->status;
+        close_buddy_sock(b);
+        b->status = BAREV_STATUS_OFFLINE;
+        emit_buddy_status(client, b, old_s, b->status);
+      }
+    }
+
+    if (b->sock >= 0 && b->stream_recv && b->stream_sent) {
+      if (b->last_ping_sent == 0) {
+        if ((now_ts - b->last_activity) >= BAREV_PING_INTERVAL) {
+          (void)send_ping(client, b, now_ts);
+        }
+      } else {
+        if ((now_ts - b->last_ping_sent) > BAREV_PING_TIMEOUT) {
+          b->ping_failures++;
+          b->last_ping_sent = 0;
+          if (b->ping_failures >= BAREV_MAX_PING_FAILURES) {
+            int old_s = b->status;
+            close_buddy_sock(b);
+            b->status = BAREV_STATUS_OFFLINE;
+            emit_buddy_status(client, b, old_s, b->status);
+          }
+        }
+      }
+    }
+  }
+}
+
+const char *barev_client_myjid(const barev_client_t *client) { return client ? client->my_jid : ""; }
+
+barev_buddy_t *barev_client_add_buddy(barev_client_t *client, const char *buddy_nick, const char *buddy_ipv6, unsigned short port) {
+  barev_buddy_impl_t *b;
+  char jid[BAREV_MAX_JID + 1];
+  size_t i;
+
+  if (!client || !buddy_nick || !buddy_ipv6) return 0;
+  barev_join_jid(jid, sizeof(jid), buddy_nick, buddy_ipv6);
+
+  for (i = 0; i < client->buddy_count; ++i)
+    if (barev_same_jid(client->buddies[i].jid, jid)) return (barev_buddy_t *)&client->buddies[i];
+
+  if (!barev_ensure_buddy_cap(client)) return 0;
+
+  b = &client->buddies[client->buddy_count++];
+  memset(b, 0, sizeof(*b));
+  barev_strcpy0(b->nick, sizeof(b->nick), buddy_nick);
+  barev_strcpy0(b->ipv6, sizeof(b->ipv6), buddy_ipv6);
+  barev_join_jid(b->jid, sizeof(b->jid), b->nick, b->ipv6);
+  b->port = port ? port : BAREV_DEFAULT_PORT;
+  b->status = BAREV_STATUS_OFFLINE;
+  b->sock = -1;
+  return (barev_buddy_t *)b;
+}
+
+int barev_client_remove_buddy(barev_client_t *client, const char *buddy_jid) {
+  size_t i;
+  if (!client || !buddy_jid) return 0;
+  for (i = 0; i < client->buddy_count; ++i) {
+    if (barev_same_jid(client->buddies[i].jid, buddy_jid)) {
+      close_buddy_sock(&client->buddies[i]);
+      if (i + 1 < client->buddy_count)
+        memmove(&client->buddies[i], &client->buddies[i + 1], (client->buddy_count - (i + 1)) * sizeof(client->buddies[0]));
+      client->buddy_count--;
+      return 1;
+    }
+  }
+  return 0;
+}
+
+barev_buddy_t *barev_client_find_buddy(barev_client_t *client, const char *buddy_jid) {
+  size_t i;
+  if (!client || !buddy_jid) return 0;
+  for (i = 0; i < client->buddy_count; ++i) if (barev_same_jid(client->buddies[i].jid, buddy_jid)) return (barev_buddy_t *)&client->buddies[i];
+  return 0;
+}
+
+int barev_client_connect(barev_client_t *client, const char *buddy_jid) {
+  barev_buddy_impl_t *b;
+  int fd;
+  if (!client) return 0;
+  b = (barev_buddy_impl_t *)barev_client_find_buddy(client, buddy_jid);
+  if (!b) return 0;
+  if (b->sock >= 0) return 1;
+
+  fd = barev_net_connect_v6(b->ipv6, b->port);
+  if (fd < 0) return 0;
+  b->sock = fd;
+  b->we_initiated = 1;
+  b->last_activity = time((time_t *)0);
+  emit_conn(client, b, BAREV_CONN_CONNECTING);
+  if (!send_stream_header(client, b)) return 0;
+  emit_conn(client, b, BAREV_CONN_STREAM_INIT);
+  return 1;
+}
+
+int barev_client_send_message(barev_client_t *client, const char *buddy_jid, const char *msg) {
+  barev_buddy_impl_t *b;
+  char out[4096];
+  if (!client || !buddy_jid || !msg) return 0;
+  b = (barev_buddy_impl_t *)barev_client_find_buddy(client, buddy_jid);
+  if (!b || b->sock < 0) return 0;
+  if (!barev_build_message(client->my_jid, b->jid, msg, out, sizeof(out))) return 0;
+  return barev_net_send_all(b->sock, out, strlen(out)) >= 0 ? 1 : 0;
+}
+
+int barev_client_send_presence(barev_client_t *client, int status, const char *status_msg) {
+  size_t i;
+  char out[2048];
+  if (!client) return 0;
+  for (i = 0; i < client->buddy_count; ++i) {
+    barev_buddy_impl_t *b = &client->buddies[i];
+    if (b->sock < 0) continue;
+    if (!barev_build_presence(b->jid, status, status_msg ? status_msg : "", out, sizeof(out))) return 0;
+    if (barev_net_send_all(b->sock, out, strlen(out)) < 0) return 0;
+  }
+  return 1;
+}
+
+int barev_client_send_typing(barev_client_t *client, const char *buddy_jid) {
+  barev_buddy_impl_t *b;
+  char out[1024];
+  if (!client || !buddy_jid) return 0;
+  b = (barev_buddy_impl_t *)barev_client_find_buddy(client, buddy_jid);
+  if (!b || b->sock < 0) return 0;
+  if (!barev_build_chatstate(b->jid, "composing", out, sizeof(out))) return 0;
+  return barev_net_send_all(b->sock, out, strlen(out)) >= 0 ? 1 : 0;
+}
+
+int barev_client_send_paused(barev_client_t *client, const char *buddy_jid) {
+  barev_buddy_impl_t *b;
+  char out[1024];
+  if (!client || !buddy_jid) return 0;
+  b = (barev_buddy_impl_t *)barev_client_find_buddy(client, buddy_jid);
+  if (!b || b->sock < 0) return 0;
+  /* Pascal version sends 'active' for Bonjour interoperability. */
+  if (!barev_build_chatstate(b->jid, "active", out, sizeof(out))) return 0;
+  return barev_net_send_all(b->sock, out, strlen(out)) >= 0 ? 1 : 0;
+}
+
+int barev_client_send_file_offer(barev_client_t *client, const char *buddy_jid,
+                                 const char *file_name, long file_size) {
+  barev_buddy_impl_t *b;
+  size_t bi;
+  barev_ft_offer_t *o;
+  char id[128];
+  char offer[4096];
+  if (!client || !buddy_jid || !file_name || file_size < 0) return 0;
+  b = (barev_buddy_impl_t *)barev_client_find_buddy(client, buddy_jid);
+  if (!b || b->sock < 0) return 0;
+  bi = buddy_index_of(client, b);
+  o = alloc_offer_slot(client);
+  if (!o) return 0;
+  sprintf(id, "ft-offer-%u", ++client->ft_seq);
+  if (!barev_ft_build_si_offer(client->my_jid, b->jid, id, id, file_name, file_size, offer, sizeof(offer))) return 0;
+  if (barev_net_send_all(b->sock, offer, strlen(offer)) < 0) return 0;
+  o->outgoing = 1;
+  strcpy(o->sid, id);
+  strcpy(o->iq_id, id);
+  o->buddy_index = bi;
+  strcpy(o->file_name, file_name);
+  o->file_size = file_size;
+  return 1;
+}
+
+int barev_client_accept_file_offer(barev_client_t *client, const char *sid) {
+  return barev_client_accept_file_offer_as(client, sid, 0);
+}
+
+int barev_client_accept_file_offer_as(barev_client_t *client, const char *sid, const char *save_path) {
+  barev_ft_offer_t *o;
+  barev_buddy_impl_t *b;
+  char iq[3072];
+  if (!client || !sid) return 0;
+  o = find_offer_by_sid(client, sid);
+  if (!o) return 0;
+  if (o->buddy_index >= client->buddy_count) return 0;
+  b = &client->buddies[o->buddy_index];
+  if (b->sock < 0) return 0;
+  if (!barev_ft_build_si_accept(client->my_jid, b->jid, o->iq_id[0] ? o->iq_id : o->sid, iq, sizeof(iq))) return 0;
+  if (barev_net_send_all(b->sock, iq, strlen(iq)) < 0) return 0;
+  o->accepted = 1;
+  if (save_path && save_path[0]) strcpy(o->save_path, save_path);
+  else if (!o->save_path[0]) strcpy(o->save_path, o->file_name);
+  return 1;
+}
+
+int barev_client_reject_file_offer(barev_client_t *client, const char *sid) {
+  barev_ft_offer_t *o;
+  barev_buddy_impl_t *b;
+  char iq[3072];
+  if (!client || !sid) return 0;
+  o = find_offer_by_sid(client, sid);
+  if (!o) return 0;
+  if (o->buddy_index >= client->buddy_count) return 0;
+  b = &client->buddies[o->buddy_index];
+  if (b->sock < 0) return 0;
+  if (!barev_ft_build_si_reject(client->my_jid, b->jid, o->iq_id[0] ? o->iq_id : o->sid, "403", iq, sizeof(iq))) return 0;
+  if (barev_net_send_all(b->sock, iq, strlen(iq)) < 0) return 0;
+  o->used = 0;
+  return 1;
+}
+
+void barev_set_userdata(barev_client_t *client, void *userdata) { if (client) client->userdata = userdata; }
+void barev_set_log_cb(barev_client_t *client, barev_log_cb cb) { if (client) client->on_log = cb; }
+void barev_set_typing_cb(barev_client_t *client, barev_typing_cb cb) { if (client) client->on_typing = cb; }
+void barev_set_buddy_status_cb(barev_client_t *client, barev_buddy_status_cb cb) { if (client) client->on_buddy_status = cb; }
+void barev_set_message_cb(barev_client_t *client, barev_message_cb cb) { if (client) client->on_message = cb; }
+void barev_set_conn_state_cb(barev_client_t *client, barev_conn_state_cb cb) { if (client) client->on_conn_state = cb; }
+void barev_set_ft_offer_cb(barev_client_t *client, barev_ft_offer_cb cb) { if (client) client->on_ft_offer = cb; }
+void barev_set_ft_progress_cb(barev_client_t *client, barev_ft_progress_cb cb) { if (client) client->on_ft_progress = cb; }
+void barev_set_ft_complete_cb(barev_client_t *client, barev_ft_complete_cb cb) { if (client) client->on_ft_complete = cb; }
+void barev_set_ft_error_cb(barev_client_t *client, barev_ft_error_cb cb) { if (client) client->on_ft_error = cb; }
+
+const char *barev_buddy_get_jid(const barev_buddy_t *buddy) { const barev_buddy_impl_t *b = (const barev_buddy_impl_t *)buddy; return b ? b->jid : ""; }
+const char *barev_buddy_get_nick(const barev_buddy_t *buddy) { const barev_buddy_impl_t *b = (const barev_buddy_impl_t *)buddy; return b ? b->nick : ""; }
+const char *barev_buddy_get_ipv6(const barev_buddy_t *buddy) { const barev_buddy_impl_t *b = (const barev_buddy_impl_t *)buddy; return b ? b->ipv6 : ""; }
+unsigned short barev_buddy_get_port(const barev_buddy_t *buddy) { const barev_buddy_impl_t *b = (const barev_buddy_impl_t *)buddy; return b ? b->port : 0; }
+int barev_buddy_get_status(const barev_buddy_t *buddy) { const barev_buddy_impl_t *b = (const barev_buddy_impl_t *)buddy; return b ? b->status : -1; }
+const char *barev_buddy_get_status_message(const barev_buddy_t *buddy) { const barev_buddy_impl_t *b = (const barev_buddy_impl_t *)buddy; return b ? b->status_msg : ""; }
diff --git a/src/barev_config.c b/src/barev_config.c
new file mode 100644
index 0000000..e9f3846
--- /dev/null
+++ b/src/barev_config.c
@@ -0,0 +1,111 @@
+#include "barev_config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static void scpy(char *d, size_t n, const char *s) {
+  size_t i;
+  if (!d || n == 0) return;
+  if (!s) { d[0] = '\0'; return; }
+  for (i = 0; i + 1 < n && s[i]; ++i) d[i] = s[i];
+  d[i] = '\0';
+}
+
+void barev_config_init(barev_config_t *cfg) {
+  if (!cfg) return;
+  memset(cfg, 0, sizeof(*cfg));
+  cfg->user_port = 1337;
+}
+
+int barev_config_has_user(const barev_config_t *cfg) {
+  if (!cfg) return 0;
+  return (cfg->user_nick[0] && cfg->user_ipv6[0]) ? 1 : 0;
+}
+
+static void trim(char *s) {
+  size_t i, j, n;
+  if (!s) return;
+  n = strlen(s);
+  i = 0;
+  while (i < n && (s[i] == ' ' || s[i] == '\t' || s[i] == '\r' || s[i] == '\n')) i++;
+  j = n;
+  while (j > i && (s[j - 1] == ' ' || s[j - 1] == '\t' || s[j - 1] == '\r' || s[j - 1] == '\n')) j--;
+  if (i > 0 || j < n) {
+    memmove(s, s + i, j - i);
+    s[j - i] = '\0';
+  }
+}
+
+int barev_config_load(const char *path, barev_config_t *cfg) {
+  FILE *f;
+  char line[1024], sec[128];
+  int cur_contact;
+  barev_config_init(cfg);
+  if (!path || !cfg) return 0;
+  f = fopen(path, "r");
+  if (!f) return 0;
+  sec[0] = '\0';
+  cur_contact = -1;
+
+  while (fgets(line, sizeof(line), f)) {
+    char *eq;
+    trim(line);
+    if (!line[0] || line[0] == '#' || line[0] == ';') continue;
+    if (line[0] == '[') {
+      char *r = strchr(line, ']');
+      if (r) { *r = '\0'; scpy(sec, sizeof(sec), line + 1); }
+      if (strncmp(sec, "Contact-", 8) == 0) {
+        if (cfg->contact_count < BAREV_CFG_MAX_CONTACTS) {
+          cur_contact = (int)cfg->contact_count;
+          cfg->contact_count++;
+          memset(&cfg->contacts[cur_contact], 0, sizeof(cfg->contacts[cur_contact]));
+          cfg->contacts[cur_contact].port = 1337;
+        }
+      } else cur_contact = -1;
+      continue;
+    }
+    eq = strchr(line, '=');
+    if (!eq) continue;
+    *eq = '\0';
+    trim(line);
+    trim(eq + 1);
+
+    if (strcmp(sec, "User") == 0) {
+      if (strcmp(line, "Nick") == 0) scpy(cfg->user_nick, sizeof(cfg->user_nick), eq + 1);
+      else if (strcmp(line, "IPv6") == 0) scpy(cfg->user_ipv6, sizeof(cfg->user_ipv6), eq + 1);
+      else if (strcmp(line, "Port") == 0) cfg->user_port = (unsigned short)atoi(eq + 1);
+      else if (strcmp(line, "AvatarPath") == 0) scpy(cfg->user_avatar_path, sizeof(cfg->user_avatar_path), eq + 1);
+    } else if (cur_contact >= 0) {
+      barev_config_contact_t *c = &cfg->contacts[cur_contact];
+      if (strcmp(line, "Nick") == 0) scpy(c->nick, sizeof(c->nick), eq + 1);
+      else if (strcmp(line, "IPv6") == 0) scpy(c->ipv6, sizeof(c->ipv6), eq + 1);
+      else if (strcmp(line, "Port") == 0) c->port = (unsigned short)atoi(eq + 1);
+      else if (strcmp(line, "AvatarPath") == 0) scpy(c->avatar_path, sizeof(c->avatar_path), eq + 1);
+    }
+  }
+  fclose(f);
+  return 1;
+}
+
+int barev_config_save(const char *path, const barev_config_t *cfg) {
+  FILE *f;
+  size_t i;
+  if (!path || !cfg) return 0;
+  f = fopen(path, "w");
+  if (!f) return 0;
+  fprintf(f, "[User]\n");
+  fprintf(f, "Nick=%s\n", cfg->user_nick);
+  fprintf(f, "IPv6=%s\n", cfg->user_ipv6);
+  fprintf(f, "Port=%u\n", (unsigned)cfg->user_port);
+  fprintf(f, "AvatarPath=%s\n\n", cfg->user_avatar_path);
+  for (i = 0; i < cfg->contact_count; ++i) {
+    fprintf(f, "[Contact-%u]\n", (unsigned)i);
+    fprintf(f, "Nick=%s\n", cfg->contacts[i].nick);
+    fprintf(f, "IPv6=%s\n", cfg->contacts[i].ipv6);
+    fprintf(f, "Port=%u\n", (unsigned)cfg->contacts[i].port);
+    fprintf(f, "AvatarPath=%s\n\n", cfg->contacts[i].avatar_path);
+  }
+  fclose(f);
+  return 1;
+}
diff --git a/src/barev_ft.c b/src/barev_ft.c
new file mode 100644
index 0000000..c5d84c1
--- /dev/null
+++ b/src/barev_ft.c
@@ -0,0 +1,335 @@
+#include "barev_ft.h"
+#include "barev_sha1.h"
+
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+static int has_ns(const char *xml, const char *ns) {
+  char a[256], b[256];
+  if (!xml || !ns) return 0;
+  (void)snprintf(a, sizeof(a), "xmlns=\"%s\"", ns);
+  (void)snprintf(b, sizeof(b), "xmlns='%s'", ns);
+  return (strstr(xml, a) != 0 || strstr(xml, b) != 0) ? 1 : 0;
+}
+
+int barev_ft_extract_attr(const char *xml, const char *attr, char *out, unsigned long out_n) {
+  char p1[128], p2[128];
+  const char *p, *q;
+  unsigned long n;
+  if (!xml || !attr || !out || out_n == 0) return 0;
+  out[0] = '\0';
+  snprintf(p1, sizeof(p1), "%s=\"", attr);
+  snprintf(p2, sizeof(p2), "%s='", attr);
+  p = strstr(xml, p1);
+  if (!p) p = strstr(xml, p2);
+  if (!p) return 0;
+  p += (p[strlen(attr) + 1] == '"') ? strlen(p1) : strlen(p2);
+  q = p;
+  while (*q && *q != '"' && *q != '\'') q++;
+  n = (unsigned long)(q - p);
+  if (n + 1 > out_n) return 0;
+  memcpy(out, p, (size_t)n);
+  out[n] = '\0';
+  return 1;
+}
+
+static int rd_exact(int fd, unsigned char *buf, int n) {
+  int got = 0, r;
+  while (got < n) {
+    r = (int)read(fd, buf + got, (size_t)(n - got));
+    if (r <= 0) return 0;
+    got += r;
+  }
+  return 1;
+}
+
+static int wr_exact(int fd, const unsigned char *buf, int n) {
+  int done = 0, w;
+  while (done < n) {
+    w = (int)write(fd, buf + done, (size_t)(n - done));
+    if (w <= 0) return 0;
+    done += w;
+  }
+  return 1;
+}
+
+int barev_ft_socks5_server_handshake(int sockfd) {
+  unsigned char b[512];
+  int nmethods;
+
+  if (!rd_exact(sockfd, b, 2)) return 0;
+  if (b[0] != 5) return 0;
+  nmethods = b[1];
+  if (nmethods > 0) {
+    if (nmethods > 255) return 0;
+    if (!rd_exact(sockfd, b, nmethods)) return 0;
+  }
+
+  b[0] = 5; b[1] = 0;
+  if (!wr_exact(sockfd, b, 2)) return 0;
+
+  /* Tolerant: just consume some request bytes, as Pascal does. */
+  nmethods = (int)read(sockfd, b, sizeof(b));
+  if (nmethods <= 0) return 0;
+
+  b[0] = 5; b[1] = 0; b[2] = 0; b[3] = 3; b[4] = 20;
+  memset(b + 5, 0, 20);
+  b[25] = 0; b[26] = 0;
+  if (!wr_exact(sockfd, b, 27)) return 0;
+  return 1;
+}
+
+int barev_ft_socks5_client_handshake(int sockfd, const char *dstaddr40) {
+  unsigned char b[512];
+  int i, r;
+  unsigned char l;
+
+  if (!dstaddr40) return 0;
+
+  b[0] = 5; b[1] = 1; b[2] = 0;
+  if (!wr_exact(sockfd, b, 3)) return 0;
+  if (!rd_exact(sockfd, b, 2)) return 0;
+  if (b[0] != 5 || b[1] != 0) return 0;
+
+  b[0] = 5; b[1] = 1; b[2] = 0; b[3] = 3;
+  l = 40;
+  b[4] = l;
+  for (i = 0; i < 40; ++i) b[5 + i] = (unsigned char)dstaddr40[i];
+  b[45] = 0; b[46] = 0;
+  if (!wr_exact(sockfd, b, 47)) return 0;
+
+  if (!rd_exact(sockfd, b, 5)) return 0;
+  if (b[0] != 5 || b[1] != 0) return 0;
+
+  if (b[3] == 1) r = 4 + 2;
+  else if (b[3] == 3) r = b[4] + 2;
+  else if (b[3] == 4) r = 16 + 2;
+  else return 0;
+
+  if (r > 0) {
+    if (r > (int)sizeof(b)) return 0;
+    if (!rd_exact(sockfd, b, r)) return 0;
+  }
+
+  return 1;
+}
+
+int barev_ft_build_si_offer(const char *from_jid, const char *to_jid, const char *id,
+                            const char *sid, const char *file_name, long file_size,
+                            char *out, unsigned long out_n) {
+  int w;
+  if (!from_jid || !to_jid || !id || !sid || !file_name || !out || out_n == 0) return 0;
+  w = snprintf(out, (size_t)out_n,
+    "<iq type=\"set\" id=\"%s\" from=\"%s\" to=\"%s\">"
+      "<si xmlns=\"http://jabber.org/protocol/si\" id=\"%s\" profile=\"http://jabber.org/protocol/si/profile/file-transfer\">"
+        "<file xmlns=\"http://jabber.org/protocol/si/profile/file-transfer\" name=\"%s\" size=\"%ld\"/>"
+        "<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"
+          "<x xmlns=\"jabber:x:data\" type=\"form\">"
+            "<field var=\"stream-method\" type=\"list-single\">"
+              "<option><value>http://jabber.org/protocol/bytestreams</value></option>"
+            "</field>"
+          "</x>"
+        "</feature>"
+      "</si>"
+    "</iq>", id, from_jid, to_jid, sid, file_name, file_size);
+  return (w > 0 && (unsigned long)w < out_n) ? 1 : 0;
+}
+
+int barev_ft_build_si_accept(const char *from_jid, const char *to_jid, const char *id,
+                             char *out, unsigned long out_n) {
+  int w;
+  if (!from_jid || !to_jid || !id || !out || out_n == 0) return 0;
+  w = snprintf(out, (size_t)out_n,
+    "<iq type=\"result\" id=\"%s\" from=\"%s\" to=\"%s\">"
+      "<si xmlns=\"http://jabber.org/protocol/si\">"
+        "<feature xmlns=\"http://jabber.org/protocol/feature-neg\">"
+          "<x xmlns=\"jabber:x:data\" type=\"submit\">"
+            "<field var=\"stream-method\">"
+              "<value>http://jabber.org/protocol/bytestreams</value>"
+            "</field>"
+          "</x>"
+        "</feature>"
+      "</si>"
+    "</iq>", id, from_jid, to_jid);
+  return (w > 0 && (unsigned long)w < out_n) ? 1 : 0;
+}
+
+int barev_ft_build_si_reject(const char *from_jid, const char *to_jid, const char *id,
+                             const char *code, char *out, unsigned long out_n) {
+  int w;
+  if (!from_jid || !to_jid || !id || !out || out_n == 0) return 0;
+  if (!code || !code[0]) code = "403";
+  w = snprintf(out, (size_t)out_n,
+    "<iq type=\"error\" id=\"%s\" from=\"%s\" to=\"%s\">"
+      "<error type=\"cancel\" code=\"%s\"><forbidden xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\"/></error>"
+    "</iq>", id, from_jid, to_jid, code);
+  return (w > 0 && (unsigned long)w < out_n) ? 1 : 0;
+}
+
+int barev_ft_build_bytestreams_query(const char *from_jid, const char *to_jid, const char *id,
+                                     const char *sid, const char *host, unsigned short port,
+                                     char *out, unsigned long out_n) {
+  int w;
+  if (!from_jid || !to_jid || !id || !sid || !host || !out || out_n == 0) return 0;
+  w = snprintf(out, (size_t)out_n,
+    "<iq type=\"set\" id=\"%s\" from=\"%s\" to=\"%s\">"
+      "<query xmlns=\"http://jabber.org/protocol/bytestreams\" sid=\"%s\" mode=\"tcp\">"
+        "<streamhost jid=\"%s\" host=\"%s\" port=\"%u\"/>"
+      "</query>"
+    "</iq>", id, from_jid, to_jid, sid, from_jid, host, (unsigned)port);
+  return (w > 0 && (unsigned long)w < out_n) ? 1 : 0;
+}
+
+int barev_ft_build_bytestreams_used(const char *from_jid, const char *to_jid, const char *id,
+                                    const char *sid, const char *host_jid,
+                                    char *out, unsigned long out_n) {
+  int w;
+  if (!from_jid || !to_jid || !id || !sid || !host_jid || !out || out_n == 0) return 0;
+  w = snprintf(out, (size_t)out_n,
+    "<iq type=\"result\" id=\"%s\" from=\"%s\" to=\"%s\">"
+      "<query xmlns=\"http://jabber.org/protocol/bytestreams\" sid=\"%s\">"
+        "<streamhost-used jid=\"%s\"/>"
+      "</query>"
+    "</iq>", id, from_jid, to_jid, sid, host_jid);
+  return (w > 0 && (unsigned long)w < out_n) ? 1 : 0;
+}
+
+int barev_ft_is_si_offer(const char *xml) {
+  if (!xml) return 0;
+  if (strstr(xml, "<si ") == 0) return 0;
+  return has_ns(xml, "http://jabber.org/protocol/si");
+}
+
+int barev_ft_is_bytestreams_query(const char *xml) {
+  if (!xml) return 0;
+  if (strstr(xml, "<query") == 0) return 0;
+  return has_ns(xml, "http://jabber.org/protocol/bytestreams");
+}
+
+int barev_ft_parse_si_offer(const char *xml,
+                            char *sid, unsigned long sid_n,
+                            char *name, unsigned long name_n,
+                            long *size_out) {
+  const char *p;
+  if (!xml || !sid || !name || !size_out) return 0;
+  if (!barev_ft_is_si_offer(xml)) return 0;
+  p = strstr(xml, "<si ");
+  if (!p) return 0;
+  if (!barev_ft_extract_attr(p, "id", sid, sid_n)) return 0;
+  p = strstr(xml, "<file ");
+  if (!p) return 0;
+  if (!barev_ft_extract_attr(p, "name", name, name_n)) return 0;
+  {
+    char sz[64];
+    if (!barev_ft_extract_attr(p, "size", sz, sizeof(sz))) return 0;
+    *size_out = atol(sz);
+  }
+  return 1;
+}
+
+int barev_ft_parse_streamhost(const char *xml,
+                              char *sid, unsigned long sid_n,
+                              char *host, unsigned long host_n,
+                              unsigned short *port_out,
+                              char *jid, unsigned long jid_n) {
+  const char *q;
+  char pstr[32];
+  if (!xml || !sid || !host || !port_out || !jid) return 0;
+  if (!barev_ft_is_bytestreams_query(xml)) return 0;
+  q = strstr(xml, "<query");
+  if (!q) return 0;
+  if (!barev_ft_extract_attr(q, "sid", sid, sid_n)) return 0;
+  q = strstr(xml, "<streamhost ");
+  if (!q) return 0;
+  if (!barev_ft_extract_attr(q, "host", host, host_n)) return 0;
+  if (!barev_ft_extract_attr(q, "jid", jid, jid_n)) return 0;
+  if (!barev_ft_extract_attr(q, "port", pstr, sizeof(pstr))) return 0;
+  *port_out = (unsigned short)atoi(pstr);
+  return 1;
+}
+
+int barev_ft_compute_dstaddr40(const char *sid, const char *initiator_jid, const char *target_jid,
+                               char out40[41]) {
+  char tmp[1024];
+  size_t n1, n2, n3;
+  if (!sid || !initiator_jid || !target_jid || !out40) return 0;
+  n1 = strlen(sid); n2 = strlen(initiator_jid); n3 = strlen(target_jid);
+  if (n1 + n2 + n3 + 1 > sizeof(tmp)) return 0;
+  memcpy(tmp, sid, n1);
+  memcpy(tmp + n1, initiator_jid, n2);
+  memcpy(tmp + n1 + n2, target_jid, n3);
+  tmp[n1 + n2 + n3] = '\0';
+  barev_sha1_hex((const unsigned char *)tmp, n1 + n2 + n3, out40);
+  return 1;
+}
+
+int barev_ft_recv_file_blocking(int sockfd, const char *save_path, long expected_size, long *bytes_done) {
+  int fd;
+  unsigned char buf[8192];
+  ssize_t r;
+  long done = 0;
+  if (!save_path || expected_size < 0) return 0;
+  fd = open(save_path, O_CREAT | O_TRUNC | O_WRONLY, 0644);
+  if (fd < 0) return 0;
+  while (done < expected_size) {
+    r = read(sockfd, buf, sizeof(buf));
+    if (r <= 0) break;
+    if (write(fd, buf, (size_t)r) != r) { close(fd); return 0; }
+    done += (long)r;
+  }
+  close(fd);
+  if (bytes_done) *bytes_done = done;
+  return done >= expected_size ? 1 : 0;
+}
+
+int barev_ft_listen_range(unsigned short min_port, unsigned short max_port, unsigned short *bound_port) {
+  int s, on;
+  struct sockaddr_in6 sa;
+  unsigned short p;
+  s = socket(AF_INET6, SOCK_STREAM, 0);
+  if (s < 0) return -1;
+  on = 1;
+  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+  memset(&sa, 0, sizeof(sa));
+  sa.sin6_family = AF_INET6;
+  sa.sin6_addr = in6addr_any;
+  for (p = min_port; p <= max_port; ++p) {
+    sa.sin6_port = htons(p);
+    if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == 0) {
+      if (listen(s, 1) == 0) {
+        if (bound_port) *bound_port = p;
+        return s;
+      }
+    }
+  }
+  close(s);
+  return -1;
+}
+
+int barev_ft_send_file_blocking(int sockfd, const char *path, long *bytes_done) {
+  int fd;
+  unsigned char buf[8192];
+  ssize_t r, w;
+  long done = 0;
+  if (!path) return 0;
+  fd = open(path, O_RDONLY);
+  if (fd < 0) return 0;
+  for (;;) {
+    r = read(fd, buf, sizeof(buf));
+    if (r < 0) { close(fd); return 0; }
+    if (r == 0) break;
+    w = write(sockfd, buf, (size_t)r);
+    if (w != r) { close(fd); return 0; }
+    done += (long)w;
+  }
+  close(fd);
+  if (bytes_done) *bytes_done = done;
+  return 1;
+}
diff --git a/src/barev_net.c b/src/barev_net.c
new file mode 100644
index 0000000..61ce9be
--- /dev/null
+++ b/src/barev_net.c
@@ -0,0 +1,127 @@
+#include "barev_net.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+int barev_net_set_nonblocking(int fd) {
+  int flags;
+  flags = fcntl(fd, F_GETFL, 0);
+  if (flags < 0) return 0;
+  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) return 0;
+  return 1;
+}
+
+int barev_net_listen_v6(unsigned short port) {
+  int fd;
+  int on;
+  struct sockaddr_in6 sa;
+
+  fd = socket(AF_INET6, SOCK_STREAM, 0);
+  if (fd < 0) return -1;
+
+  on = 1;
+  setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+
+  memset(&sa, 0, sizeof(sa));
+  sa.sin6_family = AF_INET6;
+  sa.sin6_port = htons(port);
+  sa.sin6_addr = in6addr_any;
+
+  if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { close(fd); return -1; }
+  if (listen(fd, 16) < 0) { close(fd); return -1; }
+  if (!barev_net_set_nonblocking(fd)) { close(fd); return -1; }
+  return fd;
+}
+
+int barev_net_accept_v6(int listen_fd, char *addr_out, size_t addr_out_n, unsigned short *port_out) {
+  int fd;
+  struct sockaddr_in6 sa;
+  socklen_t sl;
+  const char *r;
+
+  sl = (socklen_t)sizeof(sa);
+  memset(&sa, 0, sizeof(sa));
+  fd = accept(listen_fd, (struct sockaddr *)&sa, &sl);
+  if (fd < 0) return -1;
+
+  if (addr_out && addr_out_n > 0) {
+    r = inet_ntop(AF_INET6, &sa.sin6_addr, addr_out, (socklen_t)addr_out_n);
+    if (!r) addr_out[0] = '\0';
+  }
+  if (port_out) *port_out = ntohs(sa.sin6_port);
+  barev_net_set_nonblocking(fd);
+  return fd;
+}
+
+int barev_net_connect_v6(const char *ipv6, unsigned short port) {
+  int fd;
+  int rc;
+  struct sockaddr_in6 sa;
+
+  fd = socket(AF_INET6, SOCK_STREAM, 0);
+  if (fd < 0) return -1;
+  if (!barev_net_set_nonblocking(fd)) { close(fd); return -1; }
+
+  memset(&sa, 0, sizeof(sa));
+  sa.sin6_family = AF_INET6;
+  sa.sin6_port = htons(port);
+  if (inet_pton(AF_INET6, ipv6, &sa.sin6_addr) != 1) { close(fd); return -1; }
+
+  rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa));
+  if (rc == 0) return fd;
+  if (rc < 0 && (errno == EINPROGRESS || errno == EWOULDBLOCK)) return fd;
+  close(fd);
+  return -1;
+}
+
+int barev_net_is_readable(int fd, int timeout_ms) {
+  fd_set rfds;
+  struct timeval tv;
+  int rc;
+
+  FD_ZERO(&rfds);
+  FD_SET(fd, &rfds);
+  tv.tv_sec = timeout_ms / 1000;
+  tv.tv_usec = (timeout_ms % 1000) * 1000;
+  rc = select(fd + 1, &rfds, 0, 0, &tv);
+  if (rc <= 0) return 0;
+  return FD_ISSET(fd, &rfds) ? 1 : 0;
+}
+
+int barev_net_send_all(int fd, const char *buf, size_t n) {
+  ssize_t w;
+  size_t done;
+  done = 0;
+  while (done < n) {
+    w = send(fd, buf + done, n - done, 0);
+    if (w > 0) {
+      done += (size_t)w;
+      continue;
+    }
+    if (w < 0 && (errno == EINTR)) continue;
+    if (w < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) break;
+    return -1;
+  }
+  return (int)done;
+}
+
+int barev_net_recv_some(int fd, char *buf, size_t n) {
+  ssize_t r;
+  r = recv(fd, buf, n, 0);
+  if (r > 0) return (int)r;
+  if (r == 0) return 0;
+  if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return -2;
+  return -1;
+}
+
+void barev_net_close(int fd) {
+  if (fd >= 0) close(fd);
+}
diff --git a/src/barev_sha1.c b/src/barev_sha1.c
new file mode 100644
index 0000000..0daf5e5
--- /dev/null
+++ b/src/barev_sha1.c
@@ -0,0 +1,19 @@
+#include "barev_sha1.h"
+#include "sha1.h"
+
+void barev_sha1_hex(const unsigned char *data, size_t len, char out_hex40[41]) {
+  unsigned char digest[20];
+  SHA1_CTX ctx;
+  static const char hex[] = "0123456789abcdef";
+  size_t i;
+
+  SHA1Init(&ctx);
+  SHA1Update(&ctx, data, (uint32_t)len);
+  SHA1Final(digest, &ctx);
+
+  for (i = 0; i < 20; ++i) {
+    out_hex40[i * 2] = hex[(digest[i] >> 4) & 0x0F];
+    out_hex40[(i * 2) + 1] = hex[digest[i] & 0x0F];
+  }
+  out_hex40[40] = '\0';
+}
diff --git a/src/barev_xml.c b/src/barev_xml.c
new file mode 100644
index 0000000..7b9350c
--- /dev/null
+++ b/src/barev_xml.c
@@ -0,0 +1,259 @@
+#include "barev_xml.h"
+
+#include <string.h>
+
+static int append_str(char *out, size_t out_size, size_t *pos, const char *s) {
+  size_t i;
+  if (!out || !s || !pos || out_size == 0) return 0;
+  for (i = 0; s[i] != '\0'; ++i) {
+    if (*pos + 1 >= out_size) return 0;
+    out[*pos] = s[i];
+    *pos = *pos + 1;
+  }
+  out[*pos] = '\0';
+  return 1;
+}
+
+int barev_xml_escape(const char *in, char *out, size_t out_size) {
+  size_t i;
+  size_t pos;
+  const char *rep;
+  if (!in || !out || out_size == 0) return 0;
+  pos = 0;
+  out[0] = '\0';
+  for (i = 0; in[i] != '\0'; ++i) {
+    rep = 0;
+    if (in[i] == '&') rep = "&amp;";
+    else if (in[i] == '<') rep = "&lt;";
+    else if (in[i] == '>') rep = "&gt;";
+    else if (in[i] == '"') rep = "&quot;";
+    else if (in[i] == '\'') rep = "&apos;";
+
+    if (rep) {
+      if (!append_str(out, out_size, &pos, rep)) return 0;
+    } else {
+      if (pos + 1 >= out_size) return 0;
+      out[pos++] = in[i];
+      out[pos] = '\0';
+    }
+  }
+  return 1;
+}
+
+int barev_build_stream_header(const char *from_jid, const char *to_jid, char *out, size_t out_size) {
+  size_t pos;
+  char from_e[512], to_e[512];
+  if (!from_jid || !to_jid || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(from_jid, from_e, sizeof(from_e))) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")) return 0;
+  if (!append_str(out, out_size, &pos, "<stream:stream xmlns=\"jabber:client\" xmlns:stream=\"http://etherx.jabber.org/streams\" from=\"")) return 0;
+  if (!append_str(out, out_size, &pos, from_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\">")) return 0;
+  return 1;
+}
+
+int barev_build_stream_end(char *out, size_t out_size) {
+  size_t pos;
+  if (!out || out_size == 0) return 0;
+  pos = 0;
+  out[0] = '\0';
+  return append_str(out, out_size, &pos, "</stream:stream>");
+}
+
+int barev_build_presence(const char *to_jid, int status, const char *status_msg, char *out, size_t out_size) {
+  size_t pos;
+  char to_e[512], msg_e[1024];
+  const char *show = 0;
+  if (!out || out_size == 0) return 0;
+  pos = 0;
+  out[0] = '\0';
+
+  if (!append_str(out, out_size, &pos, "<presence")) return 0;
+  if (to_jid && to_jid[0] != '\0') {
+    if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+    if (!append_str(out, out_size, &pos, " to=\"")) return 0;
+    if (!append_str(out, out_size, &pos, to_e)) return 0;
+    if (!append_str(out, out_size, &pos, "\"")) return 0;
+  }
+  if (!append_str(out, out_size, &pos, ">")) return 0;
+
+  if (status == 2) show = "away";
+  else if (status == 3) show = "xa";
+  else if (status == 4) show = "dnd";
+  else if (status == 0) show = "offline";
+
+  if (show) {
+    if (!append_str(out, out_size, &pos, "<show>")) return 0;
+    if (!append_str(out, out_size, &pos, show)) return 0;
+    if (!append_str(out, out_size, &pos, "</show>")) return 0;
+  }
+
+  if (status_msg && status_msg[0] != '\0') {
+    if (!barev_xml_escape(status_msg, msg_e, sizeof(msg_e))) return 0;
+    if (!append_str(out, out_size, &pos, "<status>")) return 0;
+    if (!append_str(out, out_size, &pos, msg_e)) return 0;
+    if (!append_str(out, out_size, &pos, "</status>")) return 0;
+  }
+
+  if (!append_str(out, out_size, &pos, "</presence>")) return 0;
+  return 1;
+}
+
+int barev_build_message(const char *from_jid, const char *to_jid, const char *body, char *out, size_t out_size) {
+  size_t pos;
+  char from_e[512], to_e[512], body_e[2048];
+  if (!from_jid || !to_jid || !body || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(from_jid, from_e, sizeof(from_e))) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  if (!barev_xml_escape(body, body_e, sizeof(body_e))) return 0;
+
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<message to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" from=\"")) return 0;
+  if (!append_str(out, out_size, &pos, from_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" type=\"chat\"><body>")) return 0;
+  if (!append_str(out, out_size, &pos, body_e)) return 0;
+  if (!append_str(out, out_size, &pos, "</body></message>")) return 0;
+  return 1;
+}
+
+int barev_build_ping(const char *from_jid, const char *to_jid, const char *ping_id, char *out, size_t out_size) {
+  size_t pos;
+  char from_e[512], to_e[512], id_e[256];
+  if (!from_jid || !to_jid || !ping_id || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(from_jid, from_e, sizeof(from_e))) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  if (!barev_xml_escape(ping_id, id_e, sizeof(id_e))) return 0;
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<iq type=\"get\" id=\"")) return 0;
+  if (!append_str(out, out_size, &pos, id_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" from=\"")) return 0;
+  if (!append_str(out, out_size, &pos, from_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\"><ping xmlns=\"urn:xmpp:ping\"/></iq>")) return 0;
+  return 1;
+}
+
+int barev_build_pong(const char *to_jid, const char *ping_id, char *out, size_t out_size) {
+  size_t pos;
+  char to_e[512], id_e[256];
+  if (!to_jid || !ping_id || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  if (!barev_xml_escape(ping_id, id_e, sizeof(id_e))) return 0;
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<iq type=\"result\" to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" id=\"")) return 0;
+  if (!append_str(out, out_size, &pos, id_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\"/>")) return 0;
+  return 1;
+}
+
+int barev_build_chatstate(const char *to_jid, const char *state_name, char *out, size_t out_size) {
+  size_t pos;
+  char to_e[512], st_e[64];
+  if (!to_jid || !state_name || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  if (!barev_xml_escape(state_name, st_e, sizeof(st_e))) return 0;
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<message type=\"chat\" to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" id=\"chat-1\"><")) return 0;
+  if (!append_str(out, out_size, &pos, st_e)) return 0;
+  if (!append_str(out, out_size, &pos, " xmlns=\"http://jabber.org/protocol/chatstates\"/></message>")) return 0;
+  return 1;
+}
+
+int barev_build_vcard_get(const char *to_jid, const char *id, char *out, size_t out_size) {
+  size_t pos;
+  char to_e[512], id_e[128];
+  if (!to_jid || !id || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  if (!barev_xml_escape(id, id_e, sizeof(id_e))) return 0;
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<iq type=\"get\" to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" id=\"")) return 0;
+  if (!append_str(out, out_size, &pos, id_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\"><vCard xmlns=\"vcard-temp\"/></iq>")) return 0;
+  return 1;
+}
+
+int barev_build_vcard_result(const char *to_jid, const char *id, const char *vcard_inner, char *out, size_t out_size) {
+  size_t pos;
+  char to_e[512], id_e[128];
+  if (!to_jid || !id || !vcard_inner || !out || out_size == 0) return 0;
+  if (!barev_xml_escape(to_jid, to_e, sizeof(to_e))) return 0;
+  if (!barev_xml_escape(id, id_e, sizeof(id_e))) return 0;
+  pos = 0;
+  out[0] = '\0';
+  if (!append_str(out, out_size, &pos, "<iq type=\"result\" to=\"")) return 0;
+  if (!append_str(out, out_size, &pos, to_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\" id=\"")) return 0;
+  if (!append_str(out, out_size, &pos, id_e)) return 0;
+  if (!append_str(out, out_size, &pos, "\">")) return 0;
+  if (!append_str(out, out_size, &pos, vcard_inner)) return 0;
+  if (!append_str(out, out_size, &pos, "</iq>")) return 0;
+  return 1;
+}
+
+int barev_extract_attribute(const char *xml, const char *attr_name, char *out, size_t out_size) {
+  const char *p;
+  const char *q;
+  size_t n;
+  char pat[128];
+  if (!xml || !attr_name || !out || out_size == 0) return 0;
+  out[0] = '\0';
+  if (strlen(attr_name) + 3 >= sizeof(pat)) return 0;
+  strcpy(pat, attr_name);
+  strcat(pat, "='");
+  p = strstr(xml, pat);
+  if (!p) {
+    strcpy(pat, attr_name);
+    strcat(pat, "=\"");
+    p = strstr(xml, pat);
+    if (!p) return 0;
+  }
+  p += strlen(pat);
+  q = p;
+  while (*q && *q != '\'' && *q != '"') q++;
+  n = (size_t)(q - p);
+  if (n + 1 > out_size) return 0;
+  memcpy(out, p, n);
+  out[n] = '\0';
+  return 1;
+}
+
+int barev_extract_element_content(const char *xml, const char *element, char *out, size_t out_size) {
+  char open[128], close[128];
+  const char *p;
+  const char *q;
+  size_t n;
+  if (!xml || !element || !out || out_size == 0) return 0;
+  out[0] = '\0';
+  if (strlen(element) + 3 >= sizeof(open)) return 0;
+  strcpy(open, "<"); strcat(open, element); strcat(open, ">");
+  strcpy(close, "</"); strcat(close, element); strcat(close, ">");
+  p = strstr(xml, open);
+  if (!p) return 0;
+  p += strlen(open);
+  q = strstr(p, close);
+  if (!q) return 0;
+  n = (size_t)(q - p);
+  if (n + 1 > out_size) return 0;
+  memcpy(out, p, n);
+  out[n] = '\0';
+  return 1;
+}
diff --git a/src/sha1.c b/src/sha1.c
new file mode 100644
index 0000000..8c5aa89
--- /dev/null
+++ b/src/sha1.c
@@ -0,0 +1,302 @@
+/*
+SHA-1 in C
+By Steve Reid <steve@edmweb.com>
+100% Public Domain
+
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+  A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+  84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+  34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */
+/* #define SHA1HANDSOFF * Copies data before messing with it. */
+
+#define SHA1HANDSOFF
+
+#include <string.h>
+
+/* for uint32_t */
+#include <stdint.h>
+
+#include "sha1.h"
+
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#define blk0_le(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
+    |(rol(block->l[i],8)&0x00FF00FF))
+#define blk0_be(i) block->l[i]
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define blk0(i) blk0_le(i)
+#elif BYTE_ORDER == BIG_ENDIAN
+#define blk0(i) blk0_be(i)
+#else
+/* Fall back to a runtime endian check */
+const union {
+    long l;
+    char c;
+} sha1_endian = { 1 };
+#define blk0(i) (sha1_endian.c == 0 ? blk0_be(i) : blk0_le(i))
+#endif
+
+#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
+    ^block->l[(i+2)&15]^block->l[i&15],1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
+#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
+#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
+#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
+
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(
+    uint32_t state[5],
+    const unsigned char buffer[64]
+)
+{
+    uint32_t a, b, c, d, e;
+
+    typedef union
+    {
+        unsigned char c[64];
+        uint32_t l[16];
+    } CHAR64LONG16;
+
+#ifdef SHA1HANDSOFF
+    CHAR64LONG16 block[1];      /* use array to appear as a pointer */
+
+    memcpy(block, buffer, 64);
+#else
+    /* The following had better never be used because it causes the
+     * pointer-to-const buffer to be cast into a pointer to non-const.
+     * And the result is written through.  I threw a "const" in, hoping
+     * this will cause a diagnostic.
+     */
+    CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer;
+#endif
+    /* Copy context->state[] to working vars */
+    a = state[0];
+    b = state[1];
+    c = state[2];
+    d = state[3];
+    e = state[4];
+    /* 4 rounds of 20 operations each. Loop unrolled. */
+    R0(a, b, c, d, e, 0);
+    R0(e, a, b, c, d, 1);
+    R0(d, e, a, b, c, 2);
+    R0(c, d, e, a, b, 3);
+    R0(b, c, d, e, a, 4);
+    R0(a, b, c, d, e, 5);
+    R0(e, a, b, c, d, 6);
+    R0(d, e, a, b, c, 7);
+    R0(c, d, e, a, b, 8);
+    R0(b, c, d, e, a, 9);
+    R0(a, b, c, d, e, 10);
+    R0(e, a, b, c, d, 11);
+    R0(d, e, a, b, c, 12);
+    R0(c, d, e, a, b, 13);
+    R0(b, c, d, e, a, 14);
+    R0(a, b, c, d, e, 15);
+    R1(e, a, b, c, d, 16);
+    R1(d, e, a, b, c, 17);
+    R1(c, d, e, a, b, 18);
+    R1(b, c, d, e, a, 19);
+    R2(a, b, c, d, e, 20);
+    R2(e, a, b, c, d, 21);
+    R2(d, e, a, b, c, 22);
+    R2(c, d, e, a, b, 23);
+    R2(b, c, d, e, a, 24);
+    R2(a, b, c, d, e, 25);
+    R2(e, a, b, c, d, 26);
+    R2(d, e, a, b, c, 27);
+    R2(c, d, e, a, b, 28);
+    R2(b, c, d, e, a, 29);
+    R2(a, b, c, d, e, 30);
+    R2(e, a, b, c, d, 31);
+    R2(d, e, a, b, c, 32);
+    R2(c, d, e, a, b, 33);
+    R2(b, c, d, e, a, 34);
+    R2(a, b, c, d, e, 35);
+    R2(e, a, b, c, d, 36);
+    R2(d, e, a, b, c, 37);
+    R2(c, d, e, a, b, 38);
+    R2(b, c, d, e, a, 39);
+    R3(a, b, c, d, e, 40);
+    R3(e, a, b, c, d, 41);
+    R3(d, e, a, b, c, 42);
+    R3(c, d, e, a, b, 43);
+    R3(b, c, d, e, a, 44);
+    R3(a, b, c, d, e, 45);
+    R3(e, a, b, c, d, 46);
+    R3(d, e, a, b, c, 47);
+    R3(c, d, e, a, b, 48);
+    R3(b, c, d, e, a, 49);
+    R3(a, b, c, d, e, 50);
+    R3(e, a, b, c, d, 51);
+    R3(d, e, a, b, c, 52);
+    R3(c, d, e, a, b, 53);
+    R3(b, c, d, e, a, 54);
+    R3(a, b, c, d, e, 55);
+    R3(e, a, b, c, d, 56);
+    R3(d, e, a, b, c, 57);
+    R3(c, d, e, a, b, 58);
+    R3(b, c, d, e, a, 59);
+    R4(a, b, c, d, e, 60);
+    R4(e, a, b, c, d, 61);
+    R4(d, e, a, b, c, 62);
+    R4(c, d, e, a, b, 63);
+    R4(b, c, d, e, a, 64);
+    R4(a, b, c, d, e, 65);
+    R4(e, a, b, c, d, 66);
+    R4(d, e, a, b, c, 67);
+    R4(c, d, e, a, b, 68);
+    R4(b, c, d, e, a, 69);
+    R4(a, b, c, d, e, 70);
+    R4(e, a, b, c, d, 71);
+    R4(d, e, a, b, c, 72);
+    R4(c, d, e, a, b, 73);
+    R4(b, c, d, e, a, 74);
+    R4(a, b, c, d, e, 75);
+    R4(e, a, b, c, d, 76);
+    R4(d, e, a, b, c, 77);
+    R4(c, d, e, a, b, 78);
+    R4(b, c, d, e, a, 79);
+    /* Add the working vars back into context.state[] */
+    state[0] += a;
+    state[1] += b;
+    state[2] += c;
+    state[3] += d;
+    state[4] += e;
+    /* Wipe variables */
+    a = b = c = d = e = 0;
+#ifdef SHA1HANDSOFF
+    memset(block, '\0', sizeof(block));
+#endif
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(
+    SHA1_CTX * context
+)
+{
+    /* SHA1 initialization constants */
+    context->state[0] = 0x67452301;
+    context->state[1] = 0xEFCDAB89;
+    context->state[2] = 0x98BADCFE;
+    context->state[3] = 0x10325476;
+    context->state[4] = 0xC3D2E1F0;
+    context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(
+    SHA1_CTX * context,
+    const unsigned char *data,
+    uint32_t len
+)
+{
+    uint32_t i;
+
+    uint32_t j;
+
+    j = context->count[0];
+    if ((context->count[0] += len << 3) < j)
+        context->count[1]++;
+    context->count[1] += (len >> 29);
+    j = (j >> 3) & 63;
+    if ((j + len) > 63)
+    {
+        memcpy(&context->buffer[j], data, (i = 64 - j));
+        SHA1Transform(context->state, context->buffer);
+        for (; i + 63 < len; i += 64)
+        {
+            SHA1Transform(context->state, &data[i]);
+        }
+        j = 0;
+    }
+    else
+        i = 0;
+    memcpy(&context->buffer[j], &data[i], len - i);
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(
+    unsigned char digest[20],
+    SHA1_CTX * context
+)
+{
+    unsigned i;
+
+    unsigned char finalcount[8];
+
+    unsigned char c;
+
+#if 0    /* untested "improvement" by DHR */
+    /* Convert context->count to a sequence of bytes
+     * in finalcount.  Second element first, but
+     * big-endian order within element.
+     * But we do it all backwards.
+     */
+    unsigned char *fcp = &finalcount[8];
+
+    for (i = 0; i < 2; i++)
+    {
+        uint32_t t = context->count[i];
+
+        int j;
+
+        for (j = 0; j < 4; t >>= 8, j++)
+            *--fcp = (unsigned char) t}
+#else
+    for (i = 0; i < 8; i++)
+    {
+        finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255);      /* Endian independent */
+    }
+#endif
+    c = 0200;
+    SHA1Update(context, &c, 1);
+    while ((context->count[0] & 504) != 448)
+    {
+        c = 0000;
+        SHA1Update(context, &c, 1);
+    }
+    SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
+    for (i = 0; i < 20; i++)
+    {
+        digest[i] = (unsigned char)
+            ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
+    }
+    /* Wipe variables */
+    memset(context, '\0', sizeof(*context));
+    memset(&finalcount, '\0', sizeof(finalcount));
+}
+
+void SHA1(
+    char *hash_out,
+    const char *str,
+    uint32_t len)
+{
+    SHA1_CTX ctx;
+    unsigned int ii;
+
+    SHA1Init(&ctx);
+    for (ii=0; ii<len; ii+=1)
+        SHA1Update(&ctx, (const unsigned char*)str + ii, 1);
+    SHA1Final((unsigned char *)hash_out, &ctx);
+}
+