#include "barev_internal.h" #include "barev_net.h" #include "barev_xml.h" #include "barev_avatar.h" #include "barev_ft.h" #include #include #include #include #include #include #include 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_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, "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, "on_typing) { if (strstr(xml, "on_message) c->on_message((barev_buddy_t *)b, body, c->userdata); } return; } if (strstr(xml, "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, ""); } 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, ""); } for (;;) { start = strchr(b->recv_buf, '<'); if (!start) break; if (strncmp(start, ""); if (!end) break; end += 10; } else if (strncmp(start, ""); if (end) end += 11; else { end = strstr(start, "/>"); if (!end) break; end += 2; } } else if (strncmp(start, ""); 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 : ""; }