Logo Search packages:      
Sourcecode: kdenetwork version File versions

libgadu.c

/* $Id: libgadu.c,v 1.1.4.1 2004/10/29 13:41:21 gj Exp $ */

/*
 *  (C) Copyright 2001-2003 Wojtek Kaniewski <wojtekka@irc.pl>
 *                          Robert J. Woźny <speedy@ziew.org>
 *                          Arkadiusz Miśkiewicz <arekm@pld-linux.org>
 *                          Tomasz Chiliński <chilek@chilan.com>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License Version
 *  2.1 as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef sun
#  include <sys/filio.h>
#endif

#include "libgadu-config.h"

#include <errno.h>
#include <netdb.h>
#ifdef __GG_LIBGADU_HAVE_PTHREAD
#  include <pthread.h>
#endif
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __GG_LIBGADU_HAVE_OPENSSL
#  include <openssl/err.h>
#  include <openssl/rand.h>
#endif

#include "compat.h"
#include "libgadu.h"

int gg_debug_level = 0;
void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL;

int gg_dcc_port = 0;
unsigned long gg_dcc_ip = 0;

unsigned long gg_local_ip = 0;
/*
 * zmienne opisujące parametry proxy http.
 */
char *gg_proxy_host = NULL;
int gg_proxy_port = 0;
int gg_proxy_enabled = 0;
int gg_proxy_http_only = 0;
char *gg_proxy_username = NULL;
char *gg_proxy_password = NULL;

#ifndef lint 
static char rcsid[]
#ifdef __GNUC__
__attribute__ ((unused))
#endif
= "$Id: libgadu.c,v 1.1.4.1 2004/10/29 13:41:21 gj Exp $";
#endif 

/*
 * gg_libgadu_version()
 *
 * zwraca wersję libgadu.
 *
 *  - brak
 *
 * wersja libgadu.
 */
const char *gg_libgadu_version()
{
      return GG_LIBGADU_VERSION;
}

/*
 * gg_fix32()
 *
 * zamienia kolejność bajtów w liczbie 32-bitowej tak, by odpowiadała
 * kolejności bajtów w protokole GG. ze względu na LE-owość serwera,
 * zamienia tylko na maszynach BE-wych.
 *
 *  - x - liczba do zamiany
 *
 * liczba z odpowiednią kolejnością bajtów.
 */
uint32_t gg_fix32(uint32_t x)
{
#ifndef __GG_LIBGADU_BIGENDIAN
      return x;
#else
      return (uint32_t)
            (((x & (uint32_t) 0x000000ffU) << 24) |
                 ((x & (uint32_t) 0x0000ff00U) << 8) |
                 ((x & (uint32_t) 0x00ff0000U) >> 8) |
                 ((x & (uint32_t) 0xff000000U) >> 24));
#endif            
}

/*
 * gg_fix16()
 *
 * zamienia kolejność bajtów w liczbie 16-bitowej tak, by odpowiadała
 * kolejności bajtów w protokole GG. ze względu na LE-owość serwera,
 * zamienia tylko na maszynach BE-wych.
 *
 *  - x - liczba do zamiany
 *
 * liczba z odpowiednią kolejnością bajtów.
 */
uint16_t gg_fix16(uint16_t x)
{
#ifndef __GG_LIBGADU_BIGENDIAN
      return x;
#else
      return (uint16_t)
            (((x & (uint16_t) 0x00ffU) << 8) |
                 ((x & (uint16_t) 0xff00U) >> 8));
#endif
}

/* 
 * gg_login_hash() // funkcja wewnętrzna
 * 
 * liczy hash z hasła i danego seeda.
 * 
 *  - password - hasło do hashowania
 *  - seed - wartość podana przez serwer
 *
 * hash.
 */
unsigned int gg_login_hash(const unsigned char *password, unsigned int seed)
{
      unsigned int x, y, z;

      y = seed;

      for (x = 0; *password; password++) {
            x = (x & 0xffffff00) | *password;
            y ^= x;
            y += x;
            x <<= 8;
            y ^= x;
            x <<= 8;
            y -= x;
            x <<= 8;
            y ^= x;

            z = y & 0x1F;
            y = (y << z) | (y >> (32 - z));
      }

      return y;
}

/*
 * gg_resolve() // funkcja wewnętrzna
 *
 * tworzy potok, forkuje się i w drugim procesie zaczyna resolvować 
 * podanego hosta. zapisuje w sesji deskryptor potoku. jeśli coś tam
 * będzie gotowego, znaczy, że można wczytać struct in_addr. jeśli
 * nie znajdzie, zwraca INADDR_NONE.
 *
 *  - fd - wskaźnik gdzie wrzucić deskryptor
 *  - pid - gdzie wrzucić pid procesu potomnego
 *  - hostname - nazwa hosta do zresolvowania
 *
 * 0, -1.
 */
int gg_resolve(int *fd, int *pid, const char *hostname)
{
      int pipes[2], res;
      struct in_addr a;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve(%p, %p, \"%s\");\n", fd, pid, hostname);
      
      if (!fd || !pid) {
            errno = EFAULT;
            return -1;
      }

      if (pipe(pipes) == -1)
            return -1;

      if ((res = fork()) == -1) {
            close(pipes[0]);
            close(pipes[1]);
            return -1;
      }

      if (!res) {
            if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) {
                  struct in_addr *hn;
            
                  if (!(hn = gg_gethostbyname(hostname)))
                        a.s_addr = INADDR_NONE;
                  else {
                        a.s_addr = hn->s_addr;
                        free(hn);
                  }
            }

            write(pipes[1], &a, sizeof(a));

            exit(0);
      }

      close(pipes[1]);

      *fd = pipes[0];
      *pid = res;

      return 0;
}

#ifdef __GG_LIBGADU_HAVE_PTHREAD

struct gg_resolve_pthread_data {
      char *hostname;
      int fd;
};

static void *gg_resolve_pthread_thread(void *arg)
{
      struct gg_resolve_pthread_data *d = arg;
      struct in_addr a;

      if ((a.s_addr = inet_addr(d->hostname)) == INADDR_NONE) {
            struct in_addr *hn;
            
            if (!(hn = gg_gethostbyname(d->hostname)))
                  a.s_addr = INADDR_NONE;
            else {
                  a.s_addr = hn->s_addr;
                  free(hn);
            }
      }

      write(d->fd, &a, sizeof(a));
      close(d->fd);

      free(d->hostname);
      d->hostname = NULL;

      free(d);

      pthread_exit(NULL);

      return NULL;      /* żeby kompilator nie marudził */
}

/*
 * gg_resolve_pthread() // funkcja wewnętrzna
 *
 * tworzy potok, nowy wątek i w nim zaczyna resolvować podanego hosta.
 * zapisuje w sesji deskryptor potoku. jeśli coś tam będzie gotowego,
 * znaczy, że można wczytać struct in_addr. jeśli nie znajdzie, zwraca
 * INADDR_NONE.
 *
 *  - fd - wskaźnik do zmiennej przechowującej desktyptor resolvera
 *  - resolver - wskaźnik do wskaźnika resolvera
 *  - hostname - nazwa hosta do zresolvowania
 *
 * 0, -1.
 */
int gg_resolve_pthread(int *fd, void **resolver, const char *hostname)
{
      struct gg_resolve_pthread_data *d = NULL;
      pthread_t *tmp;
      int pipes[2], new_errno;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_resolve_pthread(%p, %p, \"%s\");\n", fd, resolver, hostname);
      
      if (!resolver || !fd || !hostname) {
            gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() invalid arguments\n");
            errno = EFAULT;
            return -1;
      }

      if (!(tmp = malloc(sizeof(pthread_t)))) {
            gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory for pthread id\n");
            errno = ENOMEM;
            return -1;
      }
      
      if (pipe(pipes) == -1) {
            gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno));
            free(tmp);
            return -1;
      }

      if (!(d = malloc(sizeof(*d)))) {
            gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n");
            new_errno = ENOMEM;
            goto cleanup;
      }
      
      d->hostname = NULL;

      if (!(d->hostname = strdup(hostname))) {
            gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() out of memory\n");
            new_errno = ENOMEM;
            goto cleanup;
      }

      d->fd = pipes[1];

      if (pthread_create(tmp, NULL, gg_resolve_pthread_thread, d)) {
            gg_debug(GG_DEBUG_MISC, "// gg_resolve_phread() unable to create thread\n");
            new_errno = errno;
            goto cleanup;
      }

      gg_debug(GG_DEBUG_MISC, "// gg_resolve_pthread() %p\n", tmp);

      *resolver = tmp;

      *fd = pipes[0];

      return 0;

cleanup:
      if (d) {
            free(d->hostname);
            free(d);
      }

      close(pipes[0]);
      close(pipes[1]);

      free(tmp);

      errno = new_errno;

      return -1;
}

#endif

/*
 * gg_read() // funkcja pomocnicza
 *
 * czyta z gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy
 * połączenie zwykłe czy TLS.
 *
 *  - sess - sesja,
 *  - buf - bufor,
 *  - length - ilość bajtów,
 *
 * takie same wartości jak read().
 */
int gg_read(struct gg_session *sess, char *buf, int length)
{
      int res;

#ifdef __GG_LIBGADU_HAVE_OPENSSL
      if (sess->ssl) {
            int err;

            res = SSL_read(sess->ssl, buf, length);

            if (res < 0) {
                  err = SSL_get_error(sess->ssl, res);

                  if (err == SSL_ERROR_WANT_READ)
                        errno = EAGAIN;

                  return -1;
            }
      } else
#endif
            res = read(sess->fd, buf, length);

      return res;
}

/*
 * gg_write() // funkcja pomocnicza
 *
 * zapisuje do gniazda określoną ilość bajtów. bierze pod uwagę, czy mamy
 * połączenie zwykłe czy TLS.
 *
 *  - sess - sesja,
 *  - buf - bufor,
 *  - length - ilość bajtów,
 *
 * takie same wartości jak write().
 */
int gg_write(struct gg_session *sess, const char *buf, int length)
{
      int res;

#ifdef __GG_LIBGADU_HAVE_OPENSSL
      if (sess->ssl) {
            int err;

            res = SSL_write(sess->ssl, buf, length);

            if (res < 0) {
                  err = SSL_get_error(sess->ssl, res);

                  if (err == SSL_ERROR_WANT_WRITE)
                        errno = EAGAIN;

                  return -1;
            }
      } else
#endif
      {
            int written = 0;
            
            while (written < length) {
                  res = write(sess->fd, buf + written, length - written);

                  if (res == -1) {
                        if (errno == EAGAIN)
                              continue;
                        else
                              break;
                  } else {
                        written += res;
                        res = written;
                  }
            }
      }

      return res;
}

/*
 * gg_recv_packet() // funkcja wewnętrzna
 *
 * odbiera jeden pakiet i zwraca wskaźnik do niego. pamięć po nim
 * należy zwolnić za pomocą free().
 *
 *  - sess - opis sesji
 *
 * w przypadku błędu NULL, kod błędu w errno.
 */
void *gg_recv_packet(struct gg_session *sess)
{
      struct gg_header h;
      char *buf = NULL;
      int ret = 0, offset, size = 0;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess);
      
      if (!sess) {
            errno = EFAULT;
            return NULL;
      }

      if (sess->recv_left < 1) {
            if (sess->header_buf) {
                  memcpy(&h, sess->header_buf, sess->header_done);
                  gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done);
                  free(sess->header_buf);
                  sess->header_buf = NULL;
            } else
                  sess->header_done = 0;

            while (sess->header_done < sizeof(h)) {
                  ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done);

                  gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret);

                  if (!ret) {
                        errno = 0;
                        gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n");
                        return NULL;
                  }

                  if (ret == -1) {
                        if (errno == EINTR) {
                              gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() interrupted system call, resuming\n");
                              continue;
                        }

                        if (errno == EAGAIN) {
                              gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n");

                              if (!(sess->header_buf = malloc(sess->header_done))) {
                                    gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n");
                                    return NULL;
                              }

                              memcpy(sess->header_buf, &h, sess->header_done);

                              return NULL;
                        }

                        gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno));

                        return NULL;
                  }

                  sess->header_done += ret;

            }

            h.type = gg_fix32(h.type);
            h.length = gg_fix32(h.length);
      } else
            memcpy(&h, sess->recv_buf, sizeof(h));
      
      /* jakieś sensowne limity na rozmiar pakietu */
      if (h.length < 0 || h.length > 65535) {
            gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length);
            errno = ERANGE;
            return NULL;
      }

      if (sess->recv_left > 0) {
            gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n");
            size = sess->recv_left;
            offset = sess->recv_done;
            buf = sess->recv_buf;
      } else {
            if (!(buf = malloc(sizeof(h) + h.length + 1))) {
                  gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n");
                  return NULL;
            }

            memcpy(buf, &h, sizeof(h));

            offset = 0;
            size = h.length;
      }

      while (size > 0) {
            ret = gg_read(sess, buf + sizeof(h) + offset, size);
            gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret);
            if (ret > -1 && ret <= size) {
                  offset += ret;
                  size -= ret;
            } else if (ret == -1) { 
                  gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno));
                  if (errno == EAGAIN) {
                        gg_debug(GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size);
                        sess->recv_buf = buf;
                        sess->recv_left = size;
                        sess->recv_done = offset;
                        return NULL;
                  }
                  if (errno != EINTR) {
                        free(buf);
                        return NULL;
                  }
            }
      }

      sess->recv_left = 0;

      if ((gg_debug_level & GG_DEBUG_DUMP)) {
            unsigned int i;

            gg_debug(GG_DEBUG_DUMP, "// gg_recv_packet(%.2x)", h.type);
            for (i = 0; i < sizeof(h) + h.length; i++) 
                  gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) buf[i]);
            gg_debug(GG_DEBUG_DUMP, "\n");
      }

      return buf;
}

/*
 * gg_send_packet() // funkcja wewnętrzna
 *
 * konstruuje pakiet i wysyła go do serwera.
 *
 *  - sock - deskryptor gniazda
 *  - type - typ pakietu
 *  - payload_1 - pierwsza część pakietu
 *  - payload_length_1 - długość pierwszej części
 *  - payload_2 - druga część pakietu
 *  - payload_length_2 - długość drugiej części
 *  - ... - kolejne części pakietu i ich długości
 *  - NULL - końcowym parametr (konieczny!)
 *
 * jeśli się powiodło, zwraca 0, w przypadku błędu -1. jeśli errno == ENOMEM,
 * zabrakło pamięci. inaczej był błąd przy wysyłaniu pakietu. dla errno == 0
 * nie wysłano całego pakietu.
 */
int gg_send_packet(struct gg_session *sess, int type, ...)
{
      struct gg_header *h;
      char *tmp;
      int tmp_length;
      void *payload;
      int payload_length;
      va_list ap;
      int res;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...)\n", sess, type);

      tmp_length = 0;

      if (!(tmp = malloc(sizeof(struct gg_header)))) {
            gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n");
            return -1;
      }

      h = (struct gg_header*) tmp;
      h->type = gg_fix32(type);
      h->length = gg_fix32(0);

      va_start(ap, type);

      payload = va_arg(ap, void *);

      while (payload) {
            char *tmp2;

            payload_length = va_arg(ap, int);

            if (payload_length < 0)
                  gg_debug(GG_DEBUG_MISC, "// gg_send_packet() invalid payload length (%d)\n", payload_length);
      
            if (!(tmp2 = realloc(tmp, sizeof(struct gg_header) + tmp_length + payload_length))) {
                        gg_debug(GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n");
                  free(tmp);
                  va_end(ap);
                        return -1;
                }

            tmp = tmp2;
            
            memcpy(tmp + sizeof(struct gg_header) + tmp_length, payload, payload_length);
            tmp_length += payload_length;

            payload = va_arg(ap, void *);
      }

      va_end(ap);

      h = (struct gg_header*) tmp;
      h->length = gg_fix32(tmp_length);

      if ((gg_debug_level & GG_DEBUG_DUMP)) {
                unsigned int i;
            
                gg_debug(GG_DEBUG_DUMP, "// gg_send_packet(0x%.2x)", gg_fix32(h->type));
                for (i = 0; i < sizeof(struct gg_header) + gg_fix32(h->length); i++)
                        gg_debug(GG_DEBUG_DUMP, " %.2x", (unsigned char) tmp[i]);
                gg_debug(GG_DEBUG_DUMP, "\n");
        }
      
      tmp_length += sizeof(struct gg_header);
      
      if ((res = gg_write(sess, tmp, tmp_length)) < tmp_length) {
            gg_debug(GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno));
            free(tmp);
            return -1;
      }
      
      free(tmp);  
      return 0;
}

/*
 * gg_session_callback() // funkcja wewnętrzna
 *
 * wywoływany z gg_session->callback, wykonuje gg_watch_fd() i pakuje
 * do gg_session->event jego wynik.
 */
static int gg_session_callback(struct gg_session *s)
{
      if (!s) {
            errno = EINVAL;
            return -1;
      }

      return ((s->event = gg_watch_fd(s)) != NULL) ? 0 : -1;
}

/*
 * gg_login()
 *
 * rozpoczyna procedurę łączenia się z serwerem. resztę obsługuje się przez
 * gg_watch_fd().
 *
 * UWAGA! program musi obsłużyć SIGCHLD, jeśli łączy się asynchronicznie,
 * żeby poprawnie zamknąć proces resolvera.
 *
 *  - p - struktura opisująca początkowy stan. wymagane pola: uin, 
 *    password
 *
 * w przypadku błędu NULL, jeśli idzie dobrze (async) albo poszło
 * dobrze (sync), zwróci wskaźnik do zaalokowanej struct gg_session.
 */
struct gg_session *gg_login(const struct gg_login_params *p)
{
      struct gg_session *sess = NULL;
      char *hostname;
      int port;

      if (!p) {
            gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p);
            errno = EINVAL;
            return NULL;
      }

      gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async);

      if (!(sess = malloc(sizeof(struct gg_session)))) {
            gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n");
            goto fail;
      }

      memset(sess, 0, sizeof(struct gg_session));

      if (!p->password || !p->uin) {
            gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n");
            errno = EINVAL;
            goto fail;
      }

      if (!(sess->password = strdup(p->password))) {
            gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n");
            goto fail;
      }

      if (p->status_descr && !(sess->initial_descr = strdup(p->status_descr))) {
            gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n");
            goto fail;
      }

      sess->uin = p->uin;
      sess->state = GG_STATE_RESOLVING;
      sess->check = GG_CHECK_READ;
      sess->timeout = GG_DEFAULT_TIMEOUT;
      sess->async = p->async;
        sess->type = GG_SESSION_GG;
      sess->initial_status = p->status;
      sess->callback = gg_session_callback;
      sess->destroy = gg_free_session;
      sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT);
      sess->server_addr = p->server_addr;
      sess->external_port = p->external_port;
      sess->external_addr = p->external_addr;
      sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION;
      if (p->has_audio)
            sess->protocol_version |= GG_HAS_AUDIO_MASK;
      sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL;
      sess->last_sysmsg = p->last_sysmsg;
      sess->image_size = p->image_size;
      sess->pid = -1;

      if (p->tls == 1) {
#ifdef __GG_LIBGADU_HAVE_OPENSSL
            char buf[1024];

            OpenSSL_add_ssl_algorithms();

            if (!RAND_status()) {
                  char rdata[1024];
                  struct {
                        time_t time;
                        void *ptr;
                  } rstruct;

                  time(&rstruct.time);
                  rstruct.ptr = (void *) &rstruct;                

                  RAND_seed((void *) rdata, sizeof(rdata));
                  RAND_seed((void *) &rstruct, sizeof(rstruct));
            }

            sess->ssl_ctx = SSL_CTX_new(TLSv1_client_method());

            if (!sess->ssl_ctx) {
                  ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
                  gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf);
                  goto fail;
            }

            SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL);

            sess->ssl = SSL_new(sess->ssl_ctx);

            if (!sess->ssl) {
                  ERR_error_string_n(ERR_get_error(), buf, sizeof(buf));
                  gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf);
                  goto fail;
            }
#else
            gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n");
#endif
      }
      
      if (gg_proxy_enabled) {
            hostname = gg_proxy_host;
            sess->proxy_port = port = gg_proxy_port;
      } else {
            hostname = GG_APPMSG_HOST;
            port = GG_APPMSG_PORT;
      }

      if (!p->async) {
            struct in_addr a;

            if (!p->server_addr || !p->server_port) {
                  if ((a.s_addr = inet_addr(hostname)) == INADDR_NONE) {
                        struct in_addr *hn;
      
                        if (!(hn = gg_gethostbyname(hostname))) {
                              gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname);
                              goto fail;
                        } else {
                              a.s_addr = hn->s_addr;
                              free(hn);
                        }
                  }
            } else {
                  a.s_addr = p->server_addr;
                  port = p->server_port;
            }

            sess->hub_addr = a.s_addr;

            if (gg_proxy_enabled)
                  sess->proxy_addr = a.s_addr;

            if ((sess->fd = gg_connect(&a, port, 0)) == -1) {
                  gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno));
                  goto fail;
            }

            if (p->server_addr && p->server_port)
                  sess->state = GG_STATE_CONNECTING_GG;
            else
                  sess->state = GG_STATE_CONNECTING_HUB;

            while (sess->state != GG_STATE_CONNECTED) {
                  struct gg_event *e;

                  if (!(e = gg_watch_fd(sess))) {
                        gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n");
                        goto fail;
                  }

                  if (e->type == GG_EVENT_CONN_FAILED) {
                        errno = EACCES;
                        gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n");
                        gg_event_free(e);
                        goto fail;
                  }

                  gg_event_free(e);
            }

            return sess;
      }
      
      if (!sess->server_addr || gg_proxy_enabled) {
#ifndef __GG_LIBGADU_HAVE_PTHREAD
            if (gg_resolve(&sess->fd, &sess->pid, hostname)) {
#else
            if (gg_resolve_pthread(&sess->fd, &sess->resolver, hostname)) {
#endif
                  gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno));
                  goto fail;
            }
      } else {
            if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) {
                  gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno));
                  goto fail;
            }
            sess->state = GG_STATE_CONNECTING_GG;
            sess->check = GG_CHECK_WRITE;
      }

      return sess;

fail:
      if (sess) {
            if (sess->password)
                  free(sess->password);
            if (sess->initial_descr)
                  free(sess->initial_descr);
            free(sess);
      }
      
      return NULL;
}

/* 
 * gg_free_session()
 *
 * próbuje zamknąć połączenia i zwalnia pamięć zajmowaną przez sesję.
 *
 *  - sess - opis sesji
 */
void gg_free_session(struct gg_session *sess)
{
      if (!sess)
            return;

      /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogło zostać */

      if (sess->password)
            free(sess->password);
      
      if (sess->initial_descr)
            free(sess->initial_descr);

      if (sess->client_version)
            free(sess->client_version);

      if (sess->header_buf)
            free(sess->header_buf);

#ifdef __GG_LIBGADU_HAVE_OPENSSL
      if (sess->ssl)
            SSL_free(sess->ssl);

      if (sess->ssl_ctx)
            SSL_CTX_free(sess->ssl_ctx);
#endif

#ifdef __GG_LIBGADU_HAVE_PTHREAD
      if (sess->resolver) {
            pthread_cancel(*((pthread_t*) sess->resolver));
            free(sess->resolver);
            sess->resolver = NULL;
      }
#else
      if (sess->pid != -1)
            waitpid(sess->pid, NULL, WNOHANG);
#endif

      if (sess->fd != -1)
            close(sess->fd);

      while (sess->images)
            gg_image_queue_remove(sess, sess->images, 1);

      free(sess);
}

/*
 * gg_change_status()
 *
 * zmienia status użytkownika. przydatne do /away i /busy oraz /quit.
 *
 *  - sess - opis sesji
 *  - status - nowy status użytkownika
 *
 * 0, -1.
 */
int gg_change_status(struct gg_session *sess, int status)
{
      struct gg_new_status p;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status);

      if (!sess) {
            errno = EFAULT;
            return -1;
      }

      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      p.status = gg_fix32(status);

      sess->status = status;

      return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), NULL);
}

/*
 * gg_change_status_descr()
 *
 * zmienia status użytkownika na opisowy.
 *
 *  - sess - opis sesji
 *  - status - nowy status użytkownika
 *  - descr - opis statusu
 *
 * 0, -1.
 */
int gg_change_status_descr(struct gg_session *sess, int status, const char *descr)
{
      struct gg_new_status p;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr);

      if (!sess || !descr) {
            errno = EFAULT;
            return -1;
      }

      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      p.status = gg_fix32(status);

      sess->status = status;

      return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), NULL);
}

/*
 * gg_change_status_descr_time()
 *
 * zmienia status użytkownika na opisowy z godziną powrotu.
 *
 *  - sess - opis sesji
 *  - status - nowy status użytkownika
 *  - descr - opis statusu
 *  - time - czas w formacie uniksowym
 *
 * 0, -1.
 */
int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time)
{
      struct gg_new_status p;
      uint32_t newtime;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time);

      if (!sess || !descr || !time) {
            errno = EFAULT;
            return -1;
      }

      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      p.status = gg_fix32(status);

      sess->status = status;

      newtime = gg_fix32(time);

      return gg_send_packet(sess, GG_NEW_STATUS, &p, sizeof(p), descr, (strlen(descr) > GG_STATUS_DESCR_MAXSIZE) ? GG_STATUS_DESCR_MAXSIZE : strlen(descr), &newtime, sizeof(newtime), NULL);
}

/*
 * gg_logoff()
 *
 * wylogowuje użytkownika i zamyka połączenie, ale nie zwalnia pamięci.
 *
 *  - sess - opis sesji
 */
void gg_logoff(struct gg_session *sess)
{
      if (!sess)
            return;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess);

      if (GG_S_NA(sess->status & ~GG_STATUS_FRIENDS_MASK))
            gg_change_status(sess, GG_STATUS_NOT_AVAIL);

#ifdef __GG_LIBGADU_HAVE_OPENSSL
      if (sess->ssl)
            SSL_shutdown(sess->ssl);
#endif

#ifdef __GG_LIBGADU_HAVE_PTHREAD
      if (sess->resolver) {
            pthread_cancel(*((pthread_t*) sess->resolver));
            free(sess->resolver);
            sess->resolver = NULL;
      }
#else
      if (sess->pid != -1) {
            waitpid(sess->pid, NULL, WNOHANG);
            sess->pid = -1;
      }
#endif
      
      if (sess->fd != -1) {
            shutdown(sess->fd, 2);
            close(sess->fd);
            sess->fd = -1;
      }
}

/*
 * gg_image_request()
 *
 * wysyła żądanie wysłania obrazka o podanych parametrach.
 *
 *  - sess - opis sesji
 *  - recipient - numer adresata
 *  - size - rozmiar obrazka
 *  - crc32 - suma kontrolna obrazka
 *
 * 0/-1
 */
int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32)
{
      struct gg_send_msg s;
      struct gg_msg_image_request r;
      char dummy = 0;
      int res;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32);

      if (!sess) {
            errno = EFAULT;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      s.recipient = gg_fix32(recipient);
      s.seq = gg_fix32(0);
      s.msgclass = gg_fix32(GG_CLASS_MSG);

      r.flag = 0x04;
      r.size = gg_fix32(size);
      r.crc32 = gg_fix32(crc32);
      
      res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL);

      if (!res) {
            struct gg_image_queue *q = malloc(sizeof(*q));
            char *buf = malloc(size);

            if (!q) {
                  gg_debug(GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n");
                  free(buf);
                  errno = ENOMEM;
                  return -1;
            }

            memset(q, 0, sizeof(*q));

            q->sender = recipient;
            q->size = size;
            q->crc32 = crc32;
            q->image = buf;

            if (!sess->images)
                  sess->images = q;
            else {
                  struct gg_image_queue *qq;

                  for (qq = sess->images; qq->next; qq = qq->next)
                        ;

                  qq->next = q;
            }
      }

      return res;
}

/*
 * gg_image_reply()
 *
 * wysyła żądany obrazek.
 *
 *  - sess - opis sesji
 *  - recipient - numer adresata
 *  - filename - nazwa pliku
 *  - image - bufor z obrazkiem
 *  - size - rozmiar obrazka
 *
 * 0/-1
 */
int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size)
{
      struct gg_msg_image_reply *r;
      struct gg_send_msg s;
      const char *tmp;
      char buf[1910];
      int res;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size);

      if (!sess || !filename || !image) {
            errno = EFAULT;
            return -1;
      }

      /* wytnij ścieżki, zostaw tylko nazwę pliku */
      while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\')))
            filename = tmp + 1;

      if (strlen(filename) < 1 || strlen(filename) > 1024) {
            errno = EINVAL;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      s.recipient = gg_fix32(recipient);
      s.seq = gg_fix32(0);
      s.msgclass = gg_fix32(GG_CLASS_MSG);

      buf[0] = 0;
      r = (void*) &buf[1];

      r->flag = 0x05;
      r->size = gg_fix32(size);
      r->crc32 = gg_fix32(gg_crc32(0, image, size));

      while (size > 0) {
            int buflen, chunklen;
            
            /* \0 + struct gg_msg_image_reply */
            buflen = sizeof(struct gg_msg_image_reply) + 1;

            /* w pierwszym kawałku jest nazwa pliku */
            if (r->flag == 0x05) {
                  strcpy(buf + buflen, filename);
                  buflen += strlen(filename) + 1;
            }

            chunklen = (size >= sizeof(buf) - buflen) ? (sizeof(buf) - buflen) : size;

            memcpy(buf + buflen, image, chunklen);
            size -= chunklen;
            image += chunklen;
            
            res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL);

            if (res == -1)
                  break;

            r->flag = 0x06;
      }

      return res;
}

/*
 * gg_send_message_ctcp()
 *
 * wysyła wiadomość do innego użytkownika. zwraca losowy numer
 * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia.
 *
 *  - sess - opis sesji
 *  - msgclass - rodzaj wiadomości
 *  - recipient - numer adresata
 *  - message - treść wiadomości
 *  - message_len - długość
 *
 * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
 */
int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len)
{
      struct gg_send_msg s;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient);

      if (!sess) {
            errno = EFAULT;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      s.recipient = gg_fix32(recipient);
      s.seq = gg_fix32(0);
      s.msgclass = gg_fix32(msgclass);
      
      return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL);
}

/*
 * gg_send_message()
 *
 * wysyła wiadomość do innego użytkownika. zwraca losowy numer
 * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia.
 *
 *  - sess - opis sesji
 *  - msgclass - rodzaj wiadomości
 *  - recipient - numer adresata
 *  - message - treść wiadomości
 *
 * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
 */
int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message)
{
      gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message);

      return gg_send_message_richtext(sess, msgclass, recipient, message, NULL, 0);
}

/*
 * gg_send_message_richtext()
 *
 * wysyła kolorową wiadomość do innego użytkownika. zwraca losowy numer
 * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia.
 *
 *  - sess - opis sesji
 *  - msgclass - rodzaj wiadomości
 *  - recipient - numer adresata
 *  - message - treść wiadomości
 *  - format - informacje o formatowaniu
 *  - formatlen - długość informacji o formatowaniu
 *
 * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
 */
int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen)
{
      struct gg_send_msg s;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen);

      if (!sess) {
            errno = EFAULT;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      s.recipient = gg_fix32(recipient);
      if (!sess->seq)
            sess->seq = 0x01740000 | (rand() & 0xffff);
      s.seq = gg_fix32(sess->seq);
      s.msgclass = gg_fix32(msgclass);
      sess->seq += (rand() % 0x300) + 0x300;
      
      if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, format, formatlen, NULL) == -1)
            return -1;

      return gg_fix32(s.seq);
}

/*
 * gg_send_message_confer()
 *
 * wysyła wiadomość do kilku użytkownikow (konferencja). zwraca losowy numer
 * sekwencyjny, który można zignorować albo wykorzystać do potwierdzenia.
 *
 *  - sess - opis sesji
 *  - msgclass - rodzaj wiadomości
 *  - recipients_count - ilość adresatów
 *  - recipients - numerki adresatów
 *  - message - treść wiadomości
 *
 * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
 */
int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message)
{
      gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message);

      return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0);
}

/*
 * gg_send_message_confer_richtext()
 *
 * wysyła kolorową wiadomość do kilku użytkownikow (konferencja). zwraca
 * losowy numer sekwencyjny, który można zignorować albo wykorzystać do
 * potwierdzenia.
 *
 *  - sess - opis sesji
 *  - msgclass - rodzaj wiadomości
 *  - recipients_count - ilość adresatów
 *  - recipients - numerki adresatów
 *  - message - treść wiadomości
 *  - format - informacje o formatowaniu
 *  - formatlen - długość informacji o formatowaniu
 *
 * numer sekwencyjny wiadomości lub -1 w przypadku błędu.
 */
int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen)
{
      struct gg_send_msg s;
      struct gg_msg_recipients r;
      int i, j, k;
      uin_t *recps;
            
      gg_debug(GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen);

      if (!sess) {
            errno = EFAULT;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      r.flag = 0x01;
      r.count = gg_fix32(recipients_count - 1);
      
      if (!sess->seq)
            sess->seq = 0x01740000 | (rand() & 0xffff);
      s.seq = gg_fix32(sess->seq);
      s.msgclass = gg_fix32(msgclass);

      recps = malloc(sizeof(uin_t) * recipients_count);
      for (i = 0; i < recipients_count; i++) {
       
            s.recipient = gg_fix32(recipients[i]);
            
            for (j = 0, k = 0; j < recipients_count; j++)
                  if (recipients[j] != recipients[i]) {
                        recps[k] = gg_fix32(recipients[j]);
                        k++;
                  }
                        
            if (!i)
                  sess->seq += (rand() % 0x300) + 0x300;
            
            if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, strlen(message) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) {
                  free(recps);
                  return -1;
            }
      }

      free(recps);
      
      return gg_fix32(s.seq);
}

/*
 * gg_ping()
 *
 * wysyła do serwera pakiet ping.
 *
 *  - sess - opis sesji
 *
 * 0, -1.
 */
int gg_ping(struct gg_session *sess)
{
      gg_debug(GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess);

      if (!sess) {
            errno = EFAULT;
            return -1;
      }

      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      return gg_send_packet(sess, GG_PING, NULL);
}

/*
 * gg_notify_ex()
 *
 * wysyła serwerowi listę kontaktów (wraz z odpowiadającymi im typami userów),
 * dzięki czemu wie, czyj stan nas interesuje.
 *
 *  - sess - opis sesji
 *  - userlist - wskaźnik do tablicy numerów
 *  - types - wskaźnik do tablicy typów użytkowników
 *  - count - ilość numerków
 *
 * 0, -1.
 */
int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count)
{
      struct gg_notify *n;
      uin_t *u;
      char *t;
      int i, res = 0;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count);
      
      if (!sess) {
            errno = EFAULT;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      if (!userlist || !count)
            return gg_send_packet(sess, GG_LIST_EMPTY, NULL);
      
      while (count > 0) {
            int part_count, packet_type;
            
            if (count > 400) {
                  part_count = 400;
                  packet_type = GG_NOTIFY_FIRST;
            } else {
                  part_count = count;
                  packet_type = GG_NOTIFY_LAST;
            }

            if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count)))
                  return -1;
      
            for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { 
                  n[i].uin = gg_fix32(*u);
                  n[i].dunno1 = *t;
            }
      
            if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) {
                  free(n);
                  res = -1;
                  break;
            }

            count -= part_count;
            userlist += part_count;
            types += part_count;

            free(n);
      }

      return res;
}

/*
 * gg_notify()
 *
 * wysyła serwerowi listę kontaktów, dzięki czemu wie, czyj stan nas
 * interesuje.
 *
 *  - sess - opis sesji
 *  - userlist - wskaźnik do tablicy numerów
 *  - count - ilość numerków
 *
 * 0, -1.
 */
int gg_notify(struct gg_session *sess, uin_t *userlist, int count)
{
      struct gg_notify *n;
      uin_t *u;
      int i, res = 0;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count);
      
      if (!sess) {
            errno = EFAULT;
            return -1;
      }
      
      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      if (!userlist || !count)
            return gg_send_packet(sess, GG_LIST_EMPTY, NULL);
      
      while (count > 0) {
            int part_count, packet_type;
            
            if (count > 400) {
                  part_count = 400;
                  packet_type = GG_NOTIFY_FIRST;
            } else {
                  part_count = count;
                  packet_type = GG_NOTIFY_LAST;
            }
                  
            if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count)))
                  return -1;
      
            for (u = userlist, i = 0; i < part_count; u++, i++) { 
                  n[i].uin = gg_fix32(*u);
                  n[i].dunno1 = GG_USER_NORMAL;
            }
      
            if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) {
                  res = -1;
                  free(n);
                  break;
            }

            free(n);

            userlist += part_count;
            count -= part_count;
      }

      return res;
}

/*
 * gg_add_notify_ex()
 *
 * dodaje do listy kontaktów dany numer w trakcie połączenia.
 * dodawanemu użytkownikowi określamy jego typ (patrz protocol.html)
 *
 *  - sess - opis sesji
 *  - uin - numer
 *  - type - typ
 *
 * 0, -1.
 */
int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type)
{
      struct gg_add_remove a;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type);
      
      if (!sess) {
            errno = EFAULT;
            return -1;
      }

      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }
      
      a.uin = gg_fix32(uin);
      a.dunno1 = type;
      
      return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL);
}

/*
 * gg_add_notify()
 *
 * dodaje do listy kontaktów dany numer w trakcie połączenia.
 *
 *  - sess - opis sesji
 *  - uin - numer
 *
 * 0, -1.
 */
int gg_add_notify(struct gg_session *sess, uin_t uin)
{
      return gg_add_notify_ex(sess, uin, GG_USER_NORMAL);
}

/*
 * gg_remove_notify_ex()
 *
 * usuwa z listy kontaktów w trakcie połączenia.
 * usuwanemu użytkownikowi określamy jego typ (patrz protocol.html)
 *
 *  - sess - opis sesji
 *  - uin - numer
 *  - type - typ
 *
 * 0, -1.
 */
int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type)
{
      struct gg_add_remove a;

      gg_debug(GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type);
      
      if (!sess) {
            errno = EFAULT;
            return -1;
      }

      if (sess->state != GG_STATE_CONNECTED) {
            errno = ENOTCONN;
            return -1;
      }

      a.uin = gg_fix32(uin);
      a.dunno1 = type;
      
      return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL);
}

/*
 * gg_remove_notify()
 *
 * usuwa z listy kontaktów w trakcie połączenia.
 *
 *  - sess - opis sesji
 *  - uin - numer
 *
 * 0, -1.
 */
int gg_remove_notify(struct gg_session *sess, uin_t uin)
{
      return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL);
}

/*
 * gg_userlist_request()
 *
 * wysyła żądanie/zapytanie listy kontaktów na serwerze.
 *
 *  - sess - opis sesji
 *  - type - rodzaj zapytania/żądania
 *  - request - treść zapytania/żądania (może być NULL)
 *
 * 0, -1
 */
int gg_userlist_request(struct gg_session *sess, char type, const char *request)
{
      int len;

      if (!sess) {
            errno = EINVAL;
            return -1;
      }
      
      if (!request) {
            sess->userlist_blocks = 1;
            return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL);
      }
      
      len = strlen(request);

      sess->userlist_blocks = 0;

      while (len > 2047) {
            sess->userlist_blocks++;

            if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1)
                  return -1;

            if (type == GG_USERLIST_PUT)
                  type = GG_USERLIST_PUT_MORE;

            request += 2047;
            len -= 2047;
      }

      sess->userlist_blocks++;

      return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL);
}

/*
 * Local variables:
 * c-indentation-style: k&r
 * c-basic-offset: 8
 * indent-tabs-mode: notnil
 * End:
 *
 * vim: shiftwidth=8:
 */

Generated by  Doxygen 1.6.0   Back to index